From 1e37adacfe5b44a71ed7d7dfd785c6c2d0808eae Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 31 Mar 2024 08:43:38 -0400
Subject: fixed divider column for notetaking view.  fixed scrolling of text
 docs.  fixed keeping user docs in system button bars.

---
 .../views/collections/CollectionNoteTakingViewDivider.tsx    | 12 ++++++++++++
 1 file changed, 12 insertions(+)

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

diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index 5e4bce19d..50a97b978 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -51,6 +51,18 @@ export class CollectionNoteTakingViewDivider extends ObservableReactComponent<Di
                     cursor: 'col-resize',
                     pointerEvents: this._props.isContentActive() ? 'all' : 'none',
                 }}>
+                <div
+                    className="columnResizer-handler"
+                    onPointerDown={e => this.registerResizing(e)}
+                    style={{
+                        height: '95%',
+                        width: 12,
+                        borderRight: '4px solid #282828',
+                        borderLeft: '4px solid #282828',
+                        position: 'fixed',
+                        pointerEvents: 'none',
+                    }}
+                />
                 <div
                     className="columnResizer-handler"
                     onPointerDown={e => this.registerResizing(e)}
-- 
cgit v1.2.3-70-g09d2


From 2a313f28fcb8675223708b0657de7517a3281095 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 17 Apr 2024 12:27:21 -0400
Subject: restoring eslint - updates not complete yet

---
 .eslintrc.json                                     |   69 +-
 package-lock.json                                  |  838 +++++++++++++--
 package.json                                       |   17 +-
 src/ServerUtils.ts                                 |   27 +
 src/Utils.ts                                       |  865 ++--------------
 src/client/DocServer.ts                            |  228 ++---
 src/client/Network.ts                              |   11 +-
 .../apis/google_docs/GooglePhotosClientUtils.ts    |   10 +-
 src/client/apis/gpt/GPT.ts                         |    5 +
 src/client/apis/youtube/YoutubeBox.scss            |  126 ---
 src/client/apis/youtube/YoutubeBox.tsx             |  369 -------
 src/client/cognitive_services/CognitiveServices.ts |   24 +-
 src/client/documents/DocumentTypes.ts              |    6 +-
 src/client/documents/Documents.ts                  |  317 ++++--
 src/client/util/CaptureManager.tsx                 |    2 +-
 src/client/util/CurrentUserUtils.ts                |  250 ++---
 src/client/util/DictationManager.ts                |    6 +-
 src/client/util/DocumentManager.ts                 |   80 +-
 src/client/util/DragManager.ts                     |  175 ++--
 src/client/util/DropActionTypes.ts                 |    9 +
 src/client/util/GroupManager.tsx                   |   20 +-
 src/client/util/History.ts                         |    4 +-
 src/client/util/HypothesisUtils.ts                 |    4 +-
 src/client/util/Import & Export/ImageUtils.ts      |   10 +-
 src/client/util/InteractionUtils.tsx               |  317 ++----
 src/client/util/LinkFollower.ts                    |    4 +-
 src/client/util/LinkManager.ts                     |   49 +-
 src/client/util/Scripting.ts                       |   52 +-
 src/client/util/ScriptingGlobals.ts                |   47 +-
 src/client/util/SearchUtil.ts                      |    6 +-
 src/client/util/SerializationHelper.ts             |   29 +-
 src/client/util/SettingsManager.tsx                |   86 +-
 src/client/util/SharingManager.tsx                 |   27 +-
 src/client/util/SnappingManager.ts                 |   31 +-
 src/client/util/UndoManager.ts                     |   13 +-
 src/client/util/bezierFit.ts                       |  395 ++++----
 src/client/util/reportManager/ReportManager.tsx    |   31 +-
 src/client/views/ContextMenu.tsx                   |   30 +-
 src/client/views/ContextMenuItem.tsx               |   27 +-
 src/client/views/DashboardView.tsx                 |   62 +-
 src/client/views/DocComponent.tsx                  |   51 +-
 src/client/views/DocumentButtonBar.tsx             |   18 +-
 src/client/views/DocumentDecorations.tsx           |   98 +-
 src/client/views/FilterPanel.tsx                   |  162 +--
 src/client/views/GestureOverlay.tsx                |  214 ++--
 src/client/views/GlobalKeyHandler.ts               |   73 +-
 src/client/views/InkControlPtHandles.tsx           |    2 +-
 src/client/views/InkTangentHandles.tsx             |    3 +-
 src/client/views/InkingStroke.tsx                  |   15 +-
 src/client/views/LightboxView.tsx                  |    9 +-
 src/client/views/MainView.tsx                      |  132 ++-
 src/client/views/MainViewModal.tsx                 |    4 +-
 src/client/views/MarqueeAnnotator.tsx              |    2 +-
 src/client/views/MetadataEntryMenu.tsx             |    9 +-
 src/client/views/ObservableReactComponent.tsx      |    7 +-
 src/client/views/OverlayView.tsx                   |   37 +-
 src/client/views/PreviewCursor.tsx                 |    2 +-
 src/client/views/PropertiesButtons.tsx             |    8 +-
 src/client/views/PropertiesView.tsx                | 1062 +++++++++++---------
 src/client/views/ScriptBox.tsx                     |    2 +-
 src/client/views/SidebarAnnos.tsx                  |   13 +-
 src/client/views/StyleProvider.tsx                 |   23 +-
 src/client/views/TemplateMenu.tsx                  |   70 +-
 src/client/views/Touchable.tsx                     |  213 ----
 src/client/views/animationtimeline/Timeline.tsx    |    3 +-
 .../views/animationtimeline/TimelineMenu.tsx       |   25 +-
 .../views/collections/CollectionCalendarView.tsx   |    3 +-
 .../views/collections/CollectionCarousel3DView.tsx |   97 +-
 .../views/collections/CollectionCarouselView.tsx   |    5 +-
 .../views/collections/CollectionDockingView.tsx    |  156 +--
 .../collections/CollectionMasonryViewFieldRow.tsx  |    3 +-
 src/client/views/collections/CollectionMenu.tsx    |   38 +-
 .../views/collections/CollectionNoteTakingView.tsx |   15 +-
 .../collections/CollectionNoteTakingViewColumn.tsx |    2 +-
 .../CollectionNoteTakingViewDivider.tsx            |    3 +-
 .../views/collections/CollectionPileView.tsx       |    5 +-
 .../collections/CollectionStackedTimeline.tsx      |    3 +-
 .../views/collections/CollectionStackingView.tsx   |   79 +-
 .../CollectionStackingViewFieldColumn.tsx          |   80 +-
 src/client/views/collections/CollectionSubView.tsx |  173 ++--
 .../views/collections/CollectionTimeView.tsx       |    5 +-
 .../views/collections/CollectionTreeView.tsx       |    6 +-
 src/client/views/collections/CollectionView.tsx    |   75 +-
 src/client/views/collections/TabDocView.tsx        |   69 +-
 src/client/views/collections/TreeView.tsx          |  307 +++---
 .../CollectionFreeFormInfoUI.tsx                   |   31 +-
 .../CollectionFreeFormLayoutEngines.tsx            |   18 +-
 .../CollectionFreeFormPannableContents.tsx         |   18 +-
 .../CollectionFreeFormRemoteCursors.tsx            |    6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  122 ++-
 .../collections/collectionFreeForm/MarqueeView.tsx |   50 +-
 .../collectionGrid/CollectionGridView.tsx          |    3 +-
 .../collectionLinear/CollectionLinearView.tsx      |   66 +-
 .../collectionSchema/CollectionSchemaView.tsx      |   24 +-
 .../collectionSchema/SchemaColumnHeader.tsx        |    3 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |    3 +-
 .../collectionSchema/SchemaTableCell.tsx           |   12 +-
 src/client/views/global/globalScripts.ts           |  137 +--
 src/client/views/linking/LinkMenuItem.tsx          |    6 +-
 src/client/views/linking/LinkPopup.tsx             |    3 +-
 src/client/views/newlightbox/NewLightboxView.tsx   |    3 +-
 src/client/views/nodes/AudioBox.tsx                |  106 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |    9 +-
 src/client/views/nodes/ComparisonBox.tsx           |   54 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   10 +-
 .../views/nodes/DataVizBox/SchemaCSVPopUp.tsx      |    5 +-
 .../nodes/DataVizBox/components/Histogram.tsx      |   26 +-
 .../nodes/DataVizBox/components/LineChart.tsx      |   80 +-
 .../views/nodes/DataVizBox/components/PieChart.tsx |   24 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |    5 +-
 src/client/views/nodes/DocumentContentsView.tsx    |    5 +-
 src/client/views/nodes/DocumentIcon.tsx            |   63 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |    9 +-
 src/client/views/nodes/DocumentView.tsx            |  282 +++---
 src/client/views/nodes/EquationBox.tsx             |    2 +-
 src/client/views/nodes/FieldView.tsx               |   10 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |    5 +-
 src/client/views/nodes/FunctionPlotBox.tsx         |   10 +-
 src/client/views/nodes/ImageBox.tsx                |  117 ++-
 src/client/views/nodes/KeyValueBox.tsx             |   10 +-
 src/client/views/nodes/KeyValuePair.tsx            |    3 +-
 src/client/views/nodes/LabelBox.tsx                |   10 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |   15 +-
 src/client/views/nodes/LinkBox.tsx                 |    3 +-
 src/client/views/nodes/LinkDocPreview.tsx          |    3 +-
 .../views/nodes/MapBox/DirectionsAnchorMenu.tsx    |    3 +-
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx    |    3 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   11 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |   76 +-
 src/client/views/nodes/PDFBox.tsx                  |   31 +-
 .../views/nodes/RecordingBox/RecordingBox.tsx      |   15 +-
 .../views/nodes/RecordingBox/RecordingView.tsx     |    2 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   17 +-
 src/client/views/nodes/ScriptingBox.tsx            |    2 +-
 src/client/views/nodes/VideoBox.tsx                |   21 +-
 src/client/views/nodes/WebBox.tsx                  |   38 +-
 src/client/views/nodes/calendarBox/CalendarBox.tsx |    2 +-
 .../views/nodes/formattedText/DashDocView.tsx      |    3 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |    8 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  137 ++-
 .../formattedText/FormattedTextBoxComment.tsx      |    7 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    |   60 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |    2 +-
 .../views/nodes/formattedText/RichTextRules.ts     |  121 ++-
 src/client/views/nodes/formattedText/marks_rts.ts  |    7 +-
 src/client/views/nodes/formattedText/nodes_rts.ts  |    4 +-
 .../views/nodes/generativeFill/GenerativeFill.tsx  |   91 +-
 .../views/nodes/importBox/ImportElementBox.tsx     |    2 +-
 src/client/views/nodes/trails/PresBox.tsx          |  176 ++--
 src/client/views/nodes/trails/PresElementBox.tsx   |    3 +-
 src/client/views/pdf/AnchorMenu.tsx                |   22 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  100 +-
 src/client/views/pdf/PDFViewer.tsx                 |   21 +-
 src/client/views/search/SearchBox.tsx              |    4 +-
 src/client/views/topbar/TopBar.tsx                 |    5 +-
 src/debug/Viewer.tsx                               |    6 +-
 src/fields/DateField.ts                            |    3 +-
 src/fields/Doc.ts                                  |  182 +---
 src/fields/InkField.ts                             |   11 +-
 src/fields/List.ts                                 |   61 +-
 src/fields/ObjectField.ts                          |   25 +-
 src/fields/RefField.ts                             |    4 +-
 src/fields/RichTextUtils.ts                        |   17 +-
 src/fields/Schema.ts                               |    6 +-
 src/fields/SchemaHeaderField.ts                    |   25 +-
 src/fields/ScriptField.ts                          |   96 +-
 src/fields/Types.ts                                |   73 +-
 src/fields/URLField.ts                             |   32 +-
 src/fields/util.ts                                 |  204 ++--
 src/mobile/ImageUpload.tsx                         |   32 +-
 src/mobile/MobileInkOverlay.tsx                    |    6 +-
 src/mobile/MobileInterface.tsx                     |    2 +-
 src/pen-gestures/GestureTypes.ts                   |   16 +
 src/pen-gestures/GestureUtils.ts                   |   26 +-
 src/pen-gestures/ndollar.ts                        |   10 +-
 src/server/ActionUtilities.ts                      |   71 +-
 src/server/ApiManagers/DataVizManager.ts           |   15 +-
 src/server/ApiManagers/DeleteManager.ts            |    2 +-
 src/server/ApiManagers/DownloadManager.ts          |  385 ++++---
 src/server/ApiManagers/GeneralGoogleManager.ts     |   39 +-
 src/server/ApiManagers/MongoStore.js               |  414 ++++++++
 src/server/ApiManagers/SearchManager.ts            |    8 +-
 src/server/ApiManagers/SessionManager.ts           |   61 +-
 src/server/ApiManagers/UploadManager.ts            |  346 +++----
 src/server/ApiManagers/UserManager.ts              |   60 +-
 src/server/Client.ts                               |    8 +-
 src/server/DashSession/DashSessionAgent.ts         |   28 +-
 src/server/DashStats.ts                            |  241 +++--
 src/server/DashUploadUtils.ts                      |  748 +++++++-------
 src/server/Message.ts                              |   91 +-
 src/server/RouteManager.ts                         |   92 +-
 src/server/SocketData.ts                           |   35 +
 src/server/apis/google/GoogleApiServerUtils.ts     |    8 +-
 src/server/authentication/AuthenticationManager.ts |    2 +-
 src/server/authentication/DashUserModel.ts         |   10 +-
 src/server/database.ts                             |  166 +--
 src/server/index.ts                                |   16 +-
 src/server/remapUrl.ts                             |   69 +-
 src/server/server_Initialization.ts                |  251 ++---
 src/server/websocket.ts                            |  380 +++----
 tsconfig.json                                      |    2 +-
 webpack.config.js                                  |    7 +-
 202 files changed, 7525 insertions(+), 7291 deletions(-)
 create mode 100644 src/ServerUtils.ts
 delete mode 100644 src/client/apis/youtube/YoutubeBox.scss
 delete mode 100644 src/client/apis/youtube/YoutubeBox.tsx
 create mode 100644 src/client/util/DropActionTypes.ts
 delete mode 100644 src/client/views/Touchable.tsx
 create mode 100644 src/pen-gestures/GestureTypes.ts
 create mode 100644 src/server/ApiManagers/MongoStore.js
 create mode 100644 src/server/SocketData.ts

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

diff --git a/.eslintrc.json b/.eslintrc.json
index 43bb53566..0c4e375a9 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,18 +1,77 @@
 {
-    "extends": ["airbnb", "prettier", "plugin:node/recommended"],
-    "plugins": ["prettier"],
+    "extends": ["airbnb", "prettier", "plugin:node/recommended", "plugin:import/react"],
+    "plugins": ["prettier", "import", "@typescript-eslint"],
+    "settings": {
+        "import/parsers": {
+            "@typescript-eslint/parser": [".ts", ".tsx"]
+        },
+        "import/resolver": {
+            "typescript": {
+                "alwaysTryTypes": true // always try to resolve types under `<root>@types` directory even it doesn't contain any source code, like `@types/unist`
+            },
+            "node": {
+                "extensions": [".js", ".ts", ".tsx"]
+            }
+        },
+        "import/extensions": [".js", ".jsx", ".ts", ".tsx"]
+    },
     "rules": {
+        "import/extensions": [
+            "warn",
+            "ignorePackages",
+            {
+                "": "never",
+                "ts": "never",
+                "tsx": "never"
+            }
+        ],
+        "node/no-missing-import": 0,
         "prettier/prettier": "error",
-        "no-unused-vars": "warn",
         "no-console": "off",
         "func-names": "off",
         "no-process-exit": "off",
         "object-shorthand": "off",
         "class-methods-use-this": "off",
-        "single-quote": "off"
+        "single-quote": "off",
+        "max-classes-per-file": 0,
+        "node/no-unsupported-features/es-syntax": ["error", { "ignores": ["modules"] }],
+        "react/jsx-filename-extension": [2, { "extensions": [".js", ".jsx", ".ts", ".tsx"] }],
+        "import/prefer-default-export": "off",
+        "no-unused-expressions": "off",
+        "prefer-template": "off",
+        "no-inner-declarations": "off",
+        "no-plusplus": "off",
+        "no-multi-assign": "off",
+        "no-underscore-dangle": "off",
+        "no-nested-ternary": "off",
+        "lines-between-class-members": "off",
+        // Note: you must disable the base rule as it can report incorrect errors
+        "no-shadow": "off",
+        "@typescript-eslint/no-shadow": "warn",
+        "no-unused-vars": "off",
+        "@typescript-eslint/no-unused-vars": "error",
+        "react/destructuring-assignment": 0,
+        "no-restricted-globals": ["error", "event"],
+        "no-param-reassign": ["error", { "props": false }],
+        "no-alert": 0,
+        "radix": "off"
     },
+    "overrides": [
+        {
+            "files": ["*.ts", "*.tsx"],
+            "rules": {
+                "no-undef": "off"
+            }
+        }
+    ],
+    "parser": "@typescript-eslint/parser",
     "parserOptions": {
         "ecmaVersion": 11,
-        "sourceType": "module"
+        "sourceType": "module",
+        "ecmaFeatures": {
+            "jsx": true,
+            "modules": true
+        },
+        "useJSXTextNode": false
     }
 }
diff --git a/package-lock.json b/package-lock.json
index 6d0df30e8..d366bb717 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -48,6 +48,8 @@
                 "@types/reveal": "^4.2.0",
                 "@types/supercluster": "^7.1.3",
                 "@types/web": "^0.0.143",
+                "@types/webpack-hot-middleware": "^2.25.9",
+                "@typescript-eslint/parser": "^7.6.0",
                 "@webscopeio/react-textarea-autocomplete": "^4.9.2",
                 "adm-zip": "^0.5.10",
                 "archiver": "^7.0.1",
@@ -84,6 +86,7 @@
                 "D": "^1.0.0",
                 "d3": "^7.8.5",
                 "depcheck": "^1.4.7",
+                "eslint-webpack-plugin": "^4.1.0",
                 "exif": "^0.6.0",
                 "exifr": "^7.1.3",
                 "express": "^4.18.2",
@@ -203,7 +206,9 @@
                 "tough-cookie": "^4.1.3",
                 "tslint": "^6.1.3",
                 "tslint-loader": "^3.5.4",
+                "typescript": "^5.3.3",
                 "typescript-collections": "^1.3.3",
+                "typescript-eslint": "^7.6.0",
                 "typescript-language-server": "^4.1.3",
                 "uninstall": "^0.0.0",
                 "url": "^0.11.3",
@@ -215,6 +220,7 @@
                 "webpack": "^5.89.0",
                 "webpack-cli": "^5.1.4",
                 "webpack-dev-middleware": "^7.0.0",
+                "webpack-hot-middleware": "^2.25.4",
                 "wikijs": "^6.4.1",
                 "words-to-numbers": "^1.5.1",
                 "xoauth2": "^1.2.0",
@@ -263,7 +269,6 @@
                 "@types/uuid": "^9.0.7",
                 "@types/valid-url": "^1.0.7",
                 "@types/webpack": "^5.28.5",
-                "@types/webpack-hot-middleware": "^2.25.9",
                 "@types/youtube": "0.0.50",
                 "chai": "^5.0.0",
                 "cross-env": "^7.0.3",
@@ -272,7 +277,8 @@
                 "eslint-config-airbnb": "^19.0.4",
                 "eslint-config-node": "^4.1.0",
                 "eslint-config-prettier": "^9.1.0",
-                "eslint-plugin-import": "^2.29.0",
+                "eslint-import-resolver-typescript": "^3.6.1",
+                "eslint-plugin-import": "^2.29.1",
                 "eslint-plugin-jsx-a11y": "^6.8.0",
                 "eslint-plugin-node": "^11.1.0",
                 "eslint-plugin-prettier": "^5.0.1",
@@ -287,16 +293,16 @@
                 "ts-loader": "^9.5.1",
                 "ts-node": "^10.9.1",
                 "ts-node-dev": "^2.0.0",
-                "typescript": "^5.3.3",
-                "webpack-dev-server": "^5.0.4",
-                "webpack-hot-middleware": "^2.25.4"
+                "webpack-dev-server": "^5.0.4"
+            },
+            "engines": {
+                "node": ">=10.0.0"
             }
         },
         "node_modules/@aashutoshrathi/word-wrap": {
             "version": "1.2.6",
             "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
             "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
-            "dev": true,
             "engines": {
                 "node": ">=0.10.0"
             }
@@ -2415,7 +2421,6 @@
             "version": "4.4.0",
             "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
             "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
-            "dev": true,
             "dependencies": {
                 "eslint-visitor-keys": "^3.3.0"
             },
@@ -2430,7 +2435,6 @@
             "version": "4.10.0",
             "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
             "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
-            "dev": true,
             "engines": {
                 "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
             }
@@ -2439,7 +2443,6 @@
             "version": "2.1.4",
             "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
             "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
-            "dev": true,
             "dependencies": {
                 "ajv": "^6.12.4",
                 "debug": "^4.3.2",
@@ -2461,14 +2464,12 @@
         "node_modules/@eslint/eslintrc/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==",
-            "dev": true
+            "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
         },
         "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
             "version": "1.1.11",
             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
             "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-            "dev": true,
             "dependencies": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -2478,7 +2479,6 @@
             "version": "13.24.0",
             "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
             "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
-            "dev": true,
             "dependencies": {
                 "type-fest": "^0.20.2"
             },
@@ -2493,7 +2493,6 @@
             "version": "4.1.0",
             "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
             "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
-            "dev": true,
             "dependencies": {
                 "argparse": "^2.0.1"
             },
@@ -2505,7 +2504,6 @@
             "version": "3.1.2",
             "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
             "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-            "dev": true,
             "dependencies": {
                 "brace-expansion": "^1.1.7"
             },
@@ -2517,7 +2515,6 @@
             "version": "8.57.0",
             "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
             "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
-            "dev": true,
             "engines": {
                 "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
             }
@@ -2758,7 +2755,6 @@
             "version": "0.11.14",
             "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
             "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
-            "dev": true,
             "dependencies": {
                 "@humanwhocodes/object-schema": "^2.0.2",
                 "debug": "^4.3.1",
@@ -2772,7 +2768,6 @@
             "version": "1.1.11",
             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
             "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-            "dev": true,
             "dependencies": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -2782,7 +2777,6 @@
             "version": "3.1.2",
             "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
             "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-            "dev": true,
             "dependencies": {
                 "brace-expansion": "^1.1.7"
             },
@@ -2794,7 +2788,6 @@
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
             "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
-            "dev": true,
             "engines": {
                 "node": ">=12.22"
             },
@@ -2806,8 +2799,7 @@
         "node_modules/@humanwhocodes/object-schema": {
             "version": "2.0.3",
             "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
-            "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
-            "dev": true
+            "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="
         },
         "node_modules/@icons/material": {
             "version": "0.2.4",
@@ -2891,6 +2883,97 @@
                 "url": "https://github.com/chalk/strip-ansi?sponsor=1"
             }
         },
+        "node_modules/@jest/schemas": {
+            "version": "29.6.3",
+            "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+            "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+            "dependencies": {
+                "@sinclair/typebox": "^0.27.8"
+            },
+            "engines": {
+                "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+            }
+        },
+        "node_modules/@jest/types": {
+            "version": "29.6.3",
+            "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+            "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+            "dependencies": {
+                "@jest/schemas": "^29.6.3",
+                "@types/istanbul-lib-coverage": "^2.0.0",
+                "@types/istanbul-reports": "^3.0.0",
+                "@types/node": "*",
+                "@types/yargs": "^17.0.8",
+                "chalk": "^4.0.0"
+            },
+            "engines": {
+                "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+            }
+        },
+        "node_modules/@jest/types/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": {
+                "color-convert": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+            }
+        },
+        "node_modules/@jest/types/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": {
+                "ansi-styles": "^4.1.0",
+                "supports-color": "^7.1.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/chalk?sponsor=1"
+            }
+        },
+        "node_modules/@jest/types/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": {
+                "color-name": "~1.1.4"
+            },
+            "engines": {
+                "node": ">=7.0.0"
+            }
+        },
+        "node_modules/@jest/types/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/@jest/types/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": ">=8"
+            }
+        },
+        "node_modules/@jest/types/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": {
+                "has-flag": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/@jimp/bmp": {
             "version": "0.22.12",
             "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz",
@@ -3817,7 +3900,6 @@
             "version": "2.1.5",
             "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
             "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
-            "dev": true,
             "dependencies": {
                 "@nodelib/fs.stat": "2.0.5",
                 "run-parallel": "^1.1.9"
@@ -3830,7 +3912,6 @@
             "version": "2.0.5",
             "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
             "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
-            "dev": true,
             "engines": {
                 "node": ">= 8"
             }
@@ -3839,7 +3920,6 @@
             "version": "1.2.8",
             "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
             "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
-            "dev": true,
             "dependencies": {
                 "@nodelib/fs.scandir": "2.1.5",
                 "fastq": "^1.6.0"
@@ -6733,6 +6813,11 @@
                 "url": "https://ko-fi.com/killymxi"
             }
         },
+        "node_modules/@sinclair/typebox": {
+            "version": "0.27.8",
+            "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+            "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="
+        },
         "node_modules/@sindresorhus/is": {
             "version": "6.2.0",
             "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.2.0.tgz",
@@ -8503,7 +8588,6 @@
             "version": "3.4.38",
             "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
             "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
-            "dev": true,
             "dependencies": {
                 "@types/node": "*"
             }
@@ -8978,6 +9062,27 @@
                 "@types/node": "*"
             }
         },
+        "node_modules/@types/istanbul-lib-coverage": {
+            "version": "2.0.6",
+            "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+            "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="
+        },
+        "node_modules/@types/istanbul-lib-report": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+            "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+            "dependencies": {
+                "@types/istanbul-lib-coverage": "*"
+            }
+        },
+        "node_modules/@types/istanbul-reports": {
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+            "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+            "dependencies": {
+                "@types/istanbul-lib-report": "*"
+            }
+        },
         "node_modules/@types/jquery": {
             "version": "3.5.29",
             "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.29.tgz",
@@ -9359,6 +9464,11 @@
             "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
             "optional": true
         },
+        "node_modules/@types/semver": {
+            "version": "7.5.8",
+            "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
+            "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ=="
+        },
         "node_modules/@types/send": {
             "version": "0.17.4",
             "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
@@ -9494,7 +9604,6 @@
             "version": "2.25.9",
             "resolved": "https://registry.npmjs.org/@types/webpack-hot-middleware/-/webpack-hot-middleware-2.25.9.tgz",
             "integrity": "sha512-fad4T9VfocBjS2fZxlqkGoXoVUAjVp0EEnKBRqPwnhEEDN/FqJoFkSP5t9O1gPH75qsyG2kkT/GSUqSNTn1ZPg==",
-            "dev": true,
             "dependencies": {
                 "@types/connect": "*",
                 "tapable": "^2.2.0",
@@ -9518,12 +9627,311 @@
                 "@types/node": "*"
             }
         },
+        "node_modules/@types/yargs": {
+            "version": "17.0.32",
+            "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
+            "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
+            "dependencies": {
+                "@types/yargs-parser": "*"
+            }
+        },
+        "node_modules/@types/yargs-parser": {
+            "version": "21.0.3",
+            "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+            "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="
+        },
         "node_modules/@types/youtube": {
             "version": "0.0.50",
             "resolved": "https://registry.npmjs.org/@types/youtube/-/youtube-0.0.50.tgz",
             "integrity": "sha512-d4GpH4uPYp9W07kc487tiq6V/EUHl18vZWFMbQoe4Sk9LXEWzFi/BMf9x7TI4m7/j7gU3KeX8H6M8aPBgykeLw==",
             "dev": true
         },
+        "node_modules/@typescript-eslint/eslint-plugin": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz",
+            "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==",
+            "dependencies": {
+                "@eslint-community/regexpp": "^4.10.0",
+                "@typescript-eslint/scope-manager": "7.6.0",
+                "@typescript-eslint/type-utils": "7.6.0",
+                "@typescript-eslint/utils": "7.6.0",
+                "@typescript-eslint/visitor-keys": "7.6.0",
+                "debug": "^4.3.4",
+                "graphemer": "^1.4.0",
+                "ignore": "^5.3.1",
+                "natural-compare": "^1.4.0",
+                "semver": "^7.6.0",
+                "ts-api-utils": "^1.3.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || >=20.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "@typescript-eslint/parser": "^7.0.0",
+                "eslint": "^8.56.0"
+            },
+            "peerDependenciesMeta": {
+                "typescript": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+            "dependencies": {
+                "yallist": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+            "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
+            "dependencies": {
+                "lru-cache": "^6.0.0"
+            },
+            "bin": {
+                "semver": "bin/semver.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+            "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+        },
+        "node_modules/@typescript-eslint/parser": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz",
+            "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==",
+            "dependencies": {
+                "@typescript-eslint/scope-manager": "7.6.0",
+                "@typescript-eslint/types": "7.6.0",
+                "@typescript-eslint/typescript-estree": "7.6.0",
+                "@typescript-eslint/visitor-keys": "7.6.0",
+                "debug": "^4.3.4"
+            },
+            "engines": {
+                "node": "^18.18.0 || >=20.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "eslint": "^8.56.0"
+            },
+            "peerDependenciesMeta": {
+                "typescript": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@typescript-eslint/scope-manager": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz",
+            "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==",
+            "dependencies": {
+                "@typescript-eslint/types": "7.6.0",
+                "@typescript-eslint/visitor-keys": "7.6.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || >=20.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            }
+        },
+        "node_modules/@typescript-eslint/type-utils": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz",
+            "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==",
+            "dependencies": {
+                "@typescript-eslint/typescript-estree": "7.6.0",
+                "@typescript-eslint/utils": "7.6.0",
+                "debug": "^4.3.4",
+                "ts-api-utils": "^1.3.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || >=20.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "eslint": "^8.56.0"
+            },
+            "peerDependenciesMeta": {
+                "typescript": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@typescript-eslint/types": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz",
+            "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==",
+            "engines": {
+                "node": "^18.18.0 || >=20.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            }
+        },
+        "node_modules/@typescript-eslint/typescript-estree": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz",
+            "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==",
+            "dependencies": {
+                "@typescript-eslint/types": "7.6.0",
+                "@typescript-eslint/visitor-keys": "7.6.0",
+                "debug": "^4.3.4",
+                "globby": "^11.1.0",
+                "is-glob": "^4.0.3",
+                "minimatch": "^9.0.4",
+                "semver": "^7.6.0",
+                "ts-api-utils": "^1.3.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || >=20.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependenciesMeta": {
+                "typescript": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+            "dependencies": {
+                "yallist": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+            "version": "9.0.4",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
+            "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
+            "dependencies": {
+                "brace-expansion": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=16 || 14 >=14.17"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
+        "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+            "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
+            "dependencies": {
+                "lru-cache": "^6.0.0"
+            },
+            "bin": {
+                "semver": "bin/semver.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+            "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+        },
+        "node_modules/@typescript-eslint/utils": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz",
+            "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==",
+            "dependencies": {
+                "@eslint-community/eslint-utils": "^4.4.0",
+                "@types/json-schema": "^7.0.15",
+                "@types/semver": "^7.5.8",
+                "@typescript-eslint/scope-manager": "7.6.0",
+                "@typescript-eslint/types": "7.6.0",
+                "@typescript-eslint/typescript-estree": "7.6.0",
+                "semver": "^7.6.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || >=20.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "eslint": "^8.56.0"
+            }
+        },
+        "node_modules/@typescript-eslint/utils/node_modules/lru-cache": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+            "dependencies": {
+                "yallist": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/@typescript-eslint/utils/node_modules/semver": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+            "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
+            "dependencies": {
+                "lru-cache": "^6.0.0"
+            },
+            "bin": {
+                "semver": "bin/semver.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/@typescript-eslint/utils/node_modules/yallist": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+            "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+        },
+        "node_modules/@typescript-eslint/visitor-keys": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz",
+            "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==",
+            "dependencies": {
+                "@typescript-eslint/types": "7.6.0",
+                "eslint-visitor-keys": "^3.4.3"
+            },
+            "engines": {
+                "node": "^18.18.0 || >=20.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            }
+        },
         "node_modules/@ungap/structured-clone": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@@ -9993,7 +10401,6 @@
             "version": "0.0.8",
             "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz",
             "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==",
-            "dev": true,
             "engines": [
                 "node >= 0.8.0"
             ],
@@ -14367,6 +14774,20 @@
                 "node": ">=6.0"
             }
         },
+        "node_modules/ci-info": {
+            "version": "3.9.0",
+            "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+            "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/sibiraj-s"
+                }
+            ],
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/class-transformer": {
             "version": "0.5.1",
             "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
@@ -15906,8 +16327,7 @@
         "node_modules/deep-is": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
-            "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
-            "dev": true
+            "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
         },
         "node_modules/deepmerge": {
             "version": "4.3.1",
@@ -16188,6 +16608,17 @@
                 "node": ">=0.3.1"
             }
         },
+        "node_modules/dir-glob": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+            "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+            "dependencies": {
+                "path-type": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/dns-packet": {
             "version": "5.6.1",
             "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz",
@@ -16204,7 +16635,6 @@
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
             "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
-            "dev": true,
             "dependencies": {
                 "esutils": "^2.0.2"
             },
@@ -16699,7 +17129,6 @@
             "version": "8.57.0",
             "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
             "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
-            "dev": true,
             "dependencies": {
                 "@eslint-community/eslint-utils": "^4.2.0",
                 "@eslint-community/regexpp": "^4.6.1",
@@ -17678,6 +18107,31 @@
                 "ms": "^2.1.1"
             }
         },
+        "node_modules/eslint-import-resolver-typescript": {
+            "version": "3.6.1",
+            "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz",
+            "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==",
+            "dev": true,
+            "dependencies": {
+                "debug": "^4.3.4",
+                "enhanced-resolve": "^5.12.0",
+                "eslint-module-utils": "^2.7.4",
+                "fast-glob": "^3.3.1",
+                "get-tsconfig": "^4.5.0",
+                "is-core-module": "^2.11.0",
+                "is-glob": "^4.0.3"
+            },
+            "engines": {
+                "node": "^14.18.0 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts"
+            },
+            "peerDependencies": {
+                "eslint": "*",
+                "eslint-plugin-import": "*"
+            }
+        },
         "node_modules/eslint-module-utils": {
             "version": "2.8.1",
             "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz",
@@ -18044,7 +18498,6 @@
             "version": "7.2.2",
             "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
             "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
-            "dev": true,
             "dependencies": {
                 "esrecurse": "^4.3.0",
                 "estraverse": "^5.2.0"
@@ -18084,7 +18537,6 @@
             "version": "3.4.3",
             "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
             "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
-            "dev": true,
             "engines": {
                 "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
             },
@@ -18092,11 +18544,69 @@
                 "url": "https://opencollective.com/eslint"
             }
         },
+        "node_modules/eslint-webpack-plugin": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-4.1.0.tgz",
+            "integrity": "sha512-C3wAG2jyockIhN0YRLuKieKj2nx/gnE/VHmoHemD5ifnAtY6ZU+jNPfzPoX4Zd6RIbUyWTiZUh/ofUlBhoAX7w==",
+            "dependencies": {
+                "@types/eslint": "^8.56.5",
+                "jest-worker": "^29.7.0",
+                "micromatch": "^4.0.5",
+                "normalize-path": "^3.0.0",
+                "schema-utils": "^4.2.0"
+            },
+            "engines": {
+                "node": ">= 14.15.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/webpack"
+            },
+            "peerDependencies": {
+                "eslint": "^8.0.0",
+                "webpack": "^5.0.0"
+            }
+        },
+        "node_modules/eslint-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": ">=8"
+            }
+        },
+        "node_modules/eslint-webpack-plugin/node_modules/jest-worker": {
+            "version": "29.7.0",
+            "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+            "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+            "dependencies": {
+                "@types/node": "*",
+                "jest-util": "^29.7.0",
+                "merge-stream": "^2.0.0",
+                "supports-color": "^8.0.0"
+            },
+            "engines": {
+                "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+            }
+        },
+        "node_modules/eslint-webpack-plugin/node_modules/supports-color": {
+            "version": "8.1.1",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+            "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+            "dependencies": {
+                "has-flag": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/supports-color?sponsor=1"
+            }
+        },
         "node_modules/eslint/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==",
-            "dev": true,
             "dependencies": {
                 "color-convert": "^2.0.1"
             },
@@ -18110,14 +18620,12 @@
         "node_modules/eslint/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==",
-            "dev": true
+            "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
         },
         "node_modules/eslint/node_modules/brace-expansion": {
             "version": "1.1.11",
             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
             "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-            "dev": true,
             "dependencies": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -18127,7 +18635,6 @@
             "version": "4.1.2",
             "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
             "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-            "dev": true,
             "dependencies": {
                 "ansi-styles": "^4.1.0",
                 "supports-color": "^7.1.0"
@@ -18143,7 +18650,6 @@
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
             "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-            "dev": true,
             "dependencies": {
                 "color-name": "~1.1.4"
             },
@@ -18154,14 +18660,12 @@
         "node_modules/eslint/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==",
-            "dev": true
+            "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
         },
         "node_modules/eslint/node_modules/globals": {
             "version": "13.24.0",
             "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
             "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
-            "dev": true,
             "dependencies": {
                 "type-fest": "^0.20.2"
             },
@@ -18176,7 +18680,6 @@
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
             "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-            "dev": true,
             "engines": {
                 "node": ">=8"
             }
@@ -18185,7 +18688,6 @@
             "version": "4.1.0",
             "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
             "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
-            "dev": true,
             "dependencies": {
                 "argparse": "^2.0.1"
             },
@@ -18197,7 +18699,6 @@
             "version": "3.1.2",
             "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
             "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-            "dev": true,
             "dependencies": {
                 "brace-expansion": "^1.1.7"
             },
@@ -18209,7 +18710,6 @@
             "version": "7.2.0",
             "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
             "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-            "dev": true,
             "dependencies": {
                 "has-flag": "^4.0.0"
             },
@@ -18221,7 +18721,6 @@
             "version": "9.6.1",
             "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
             "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
-            "dev": true,
             "dependencies": {
                 "acorn": "^8.9.0",
                 "acorn-jsx": "^5.3.2",
@@ -18250,7 +18749,6 @@
             "version": "1.5.0",
             "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
             "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
-            "dev": true,
             "dependencies": {
                 "estraverse": "^5.1.0"
             },
@@ -18599,6 +19097,32 @@
             "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
             "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
         },
+        "node_modules/fast-glob": {
+            "version": "3.3.2",
+            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+            "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+            "dependencies": {
+                "@nodelib/fs.stat": "^2.0.2",
+                "@nodelib/fs.walk": "^1.2.3",
+                "glob-parent": "^5.1.2",
+                "merge2": "^1.3.0",
+                "micromatch": "^4.0.4"
+            },
+            "engines": {
+                "node": ">=8.6.0"
+            }
+        },
+        "node_modules/fast-glob/node_modules/glob-parent": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+            "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+            "dependencies": {
+                "is-glob": "^4.0.1"
+            },
+            "engines": {
+                "node": ">= 6"
+            }
+        },
         "node_modules/fast-json-stable-stringify": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -18607,8 +19131,7 @@
         "node_modules/fast-levenshtein": {
             "version": "2.0.6",
             "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-            "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
-            "dev": true
+            "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
         },
         "node_modules/fastest-levenshtein": {
             "version": "1.0.16",
@@ -18622,7 +19145,6 @@
             "version": "1.17.1",
             "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
             "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
-            "dev": true,
             "dependencies": {
                 "reusify": "^1.0.4"
             }
@@ -18693,7 +19215,6 @@
             "version": "6.0.1",
             "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
             "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
-            "dev": true,
             "dependencies": {
                 "flat-cache": "^3.0.4"
             },
@@ -18848,7 +19369,6 @@
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
             "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
-            "dev": true,
             "dependencies": {
                 "locate-path": "^6.0.0",
                 "path-exists": "^4.0.0"
@@ -18891,7 +19411,6 @@
             "version": "3.2.0",
             "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
             "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
-            "dev": true,
             "dependencies": {
                 "flatted": "^3.2.9",
                 "keyv": "^4.5.3",
@@ -18905,7 +19424,6 @@
             "version": "1.1.11",
             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
             "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-            "dev": true,
             "dependencies": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -18915,7 +19433,6 @@
             "version": "7.2.3",
             "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
             "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
-            "dev": true,
             "dependencies": {
                 "fs.realpath": "^1.0.0",
                 "inflight": "^1.0.4",
@@ -18935,7 +19452,6 @@
             "version": "3.1.2",
             "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
             "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-            "dev": true,
             "dependencies": {
                 "brace-expansion": "^1.1.7"
             },
@@ -18947,7 +19463,6 @@
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
             "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
-            "dev": true,
             "dependencies": {
                 "glob": "^7.1.3"
             },
@@ -18961,8 +19476,7 @@
         "node_modules/flatted": {
             "version": "3.3.1",
             "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
-            "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
-            "dev": true
+            "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="
         },
         "node_modules/flexlayout-react": {
             "version": "0.7.15",
@@ -19693,6 +20207,18 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/get-tsconfig": {
+            "version": "4.7.3",
+            "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz",
+            "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==",
+            "dev": true,
+            "dependencies": {
+                "resolve-pkg-maps": "^1.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+            }
+        },
         "node_modules/get-value": {
             "version": "2.0.6",
             "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
@@ -19748,7 +20274,6 @@
             "version": "6.0.2",
             "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
             "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
-            "dev": true,
             "dependencies": {
                 "is-glob": "^4.0.3"
             },
@@ -19846,6 +20371,25 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/globby": {
+            "version": "11.1.0",
+            "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+            "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+            "dependencies": {
+                "array-union": "^2.1.0",
+                "dir-glob": "^3.0.1",
+                "fast-glob": "^3.2.9",
+                "ignore": "^5.2.0",
+                "merge2": "^1.4.1",
+                "slash": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/golden-layout": {
             "version": "2.6.0",
             "resolved": "https://registry.npmjs.org/golden-layout/-/golden-layout-2.6.0.tgz",
@@ -19963,8 +20507,7 @@
         "node_modules/graphemer": {
             "version": "1.4.0",
             "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
-            "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
-            "dev": true
+            "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="
         },
         "node_modules/grid-index": {
             "version": "1.1.0",
@@ -20326,7 +20869,6 @@
             "version": "2.5.2",
             "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
             "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
-            "dev": true,
             "funding": [
                 {
                     "type": "github",
@@ -20973,7 +21515,6 @@
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
             "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
-            "dev": true,
             "engines": {
                 "node": ">=0.8.19"
             }
@@ -21700,7 +22241,6 @@
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
             "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
-            "dev": true,
             "engines": {
                 "node": ">=8"
             }
@@ -21987,6 +22527,86 @@
                 "@pkgjs/parseargs": "^0.11.0"
             }
         },
+        "node_modules/jest-util": {
+            "version": "29.7.0",
+            "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+            "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+            "dependencies": {
+                "@jest/types": "^29.6.3",
+                "@types/node": "*",
+                "chalk": "^4.0.0",
+                "ci-info": "^3.2.0",
+                "graceful-fs": "^4.2.9",
+                "picomatch": "^2.2.3"
+            },
+            "engines": {
+                "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+            }
+        },
+        "node_modules/jest-util/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": {
+                "color-convert": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+            }
+        },
+        "node_modules/jest-util/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": {
+                "ansi-styles": "^4.1.0",
+                "supports-color": "^7.1.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/chalk?sponsor=1"
+            }
+        },
+        "node_modules/jest-util/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": {
+                "color-name": "~1.1.4"
+            },
+            "engines": {
+                "node": ">=7.0.0"
+            }
+        },
+        "node_modules/jest-util/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/jest-util/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": ">=8"
+            }
+        },
+        "node_modules/jest-util/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": {
+                "has-flag": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/jest-worker": {
             "version": "27.5.1",
             "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
@@ -22243,8 +22863,7 @@
         "node_modules/json-stable-stringify-without-jsonify": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
-            "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
-            "dev": true
+            "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
         },
         "node_modules/json-stringify-pretty-compact": {
             "version": "3.0.0",
@@ -22588,7 +23207,6 @@
             "version": "0.4.1",
             "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
             "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
-            "dev": true,
             "dependencies": {
                 "prelude-ls": "^1.2.1",
                 "type-check": "~0.4.0"
@@ -22650,7 +23268,6 @@
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
             "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
-            "dev": true,
             "dependencies": {
                 "p-locate": "^5.0.0"
             },
@@ -23341,6 +23958,14 @@
             "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
             "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
         },
+        "node_modules/merge2": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+            "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+            "engines": {
+                "node": ">= 8"
+            }
+        },
         "node_modules/methods": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -24484,8 +25109,7 @@
         "node_modules/natural-compare": {
             "version": "1.4.0",
             "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
-            "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
-            "dev": true
+            "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
         },
         "node_modules/needle": {
             "version": "2.9.1",
@@ -27705,7 +28329,6 @@
             "version": "0.9.3",
             "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
             "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
-            "dev": true,
             "dependencies": {
                 "@aashutoshrathi/word-wrap": "^1.2.3",
                 "deep-is": "^0.1.3",
@@ -27758,7 +28381,6 @@
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
             "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
-            "dev": true,
             "dependencies": {
                 "p-limit": "^3.0.2"
             },
@@ -27773,7 +28395,6 @@
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
             "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
-            "dev": true,
             "dependencies": {
                 "yocto-queue": "^0.1.0"
             },
@@ -27788,7 +28409,6 @@
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
             "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
-            "dev": true,
             "engines": {
                 "node": ">=10"
             },
@@ -28519,7 +29139,6 @@
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
             "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
-            "dev": true,
             "engines": {
                 "node": ">= 0.8.0"
             }
@@ -28941,7 +29560,6 @@
             "version": "1.2.3",
             "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
             "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
-            "dev": true,
             "funding": [
                 {
                     "type": "github",
@@ -30155,6 +30773,15 @@
                 "node": ">=8"
             }
         },
+        "node_modules/resolve-pkg-maps": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+            "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+            "dev": true,
+            "funding": {
+                "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+            }
+        },
         "node_modules/resolve-protobuf-schema": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
@@ -30209,7 +30836,6 @@
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
             "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
-            "dev": true,
             "engines": {
                 "iojs": ">=1.0.0",
                 "node": ">=0.10.0"
@@ -30281,7 +30907,6 @@
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
             "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
-            "dev": true,
             "funding": [
                 {
                     "type": "github",
@@ -31026,6 +31651,14 @@
             "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz",
             "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg=="
         },
+        "node_modules/slash": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+            "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/slice-ansi": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
@@ -31688,7 +32321,6 @@
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
             "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
-            "dev": true,
             "engines": {
                 "node": ">=8"
             },
@@ -32090,8 +32722,7 @@
         "node_modules/text-table": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
-            "dev": true
+            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
         },
         "node_modules/textarea-caret": {
             "version": "3.1.0",
@@ -32333,6 +32964,17 @@
             "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz",
             "integrity": "sha512-6C5h3CE+0qjGp+YKYTs74xR0k/Nw/ePtl/Lp6CCf44hqBQ66qnH1sDFR5mV/Gc48EsrHLB53lCFSffQCkka3kg=="
         },
+        "node_modules/ts-api-utils": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
+            "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
+            "engines": {
+                "node": ">=16"
+            },
+            "peerDependencies": {
+                "typescript": ">=4.2.0"
+            }
+        },
         "node_modules/ts-loader": {
             "version": "9.5.1",
             "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz",
@@ -32934,7 +33576,6 @@
             "version": "0.4.0",
             "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
             "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
-            "dev": true,
             "dependencies": {
                 "prelude-ls": "^1.2.1"
             },
@@ -32946,7 +33587,6 @@
             "version": "0.20.2",
             "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
             "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
-            "dev": true,
             "engines": {
                 "node": ">=10"
             },
@@ -33056,6 +33696,31 @@
             "resolved": "https://registry.npmjs.org/typescript-collections/-/typescript-collections-1.3.3.tgz",
             "integrity": "sha512-7sI4e/bZijOzyURng88oOFZCISQPTHozfE2sUu5AviFYk5QV7fYGb6YiDl+vKjF/pICA354JImBImL9XJWUvdQ=="
         },
+        "node_modules/typescript-eslint": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.6.0.tgz",
+            "integrity": "sha512-LY6vH6F1l5jpGqRtU+uK4+mOecIb4Cd4kaz1hAiJrgnNiHUA8wiw8BkJyYS+MRLM69F1QuSKwtGlQqnGl1Rc6w==",
+            "dependencies": {
+                "@typescript-eslint/eslint-plugin": "7.6.0",
+                "@typescript-eslint/parser": "7.6.0",
+                "@typescript-eslint/utils": "7.6.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || >=20.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "eslint": "^8.56.0"
+            },
+            "peerDependenciesMeta": {
+                "typescript": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/typescript-language-server": {
             "version": "4.3.3",
             "resolved": "https://registry.npmjs.org/typescript-language-server/-/typescript-language-server-4.3.3.tgz",
@@ -33952,7 +34617,6 @@
             "version": "2.26.1",
             "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.26.1.tgz",
             "integrity": "sha512-khZGfAeJx6I8K9zKohEWWYN6KDlVw2DHownoe+6Vtwj1LP9WFgegXnVMSkZ/dBEBtXFwrkkydsaPFlB7f8wU2A==",
-            "dev": true,
             "dependencies": {
                 "ansi-html-community": "0.0.8",
                 "html-entities": "^2.1.0",
diff --git a/package.json b/package.json
index 13d97b45c..3f2f5a70f 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,9 @@
     "version": "1.0.0",
     "description": "",
     "main": "index.js",
+    "engines": {
+        "node": ">=12.0.0"
+    },
     "browser": {
         "child_process": false
     },
@@ -62,7 +65,6 @@
         "@types/uuid": "^9.0.7",
         "@types/valid-url": "^1.0.7",
         "@types/webpack": "^5.28.5",
-        "@types/webpack-hot-middleware": "^2.25.9",
         "@types/youtube": "0.0.50",
         "chai": "^5.0.0",
         "cross-env": "^7.0.3",
@@ -71,7 +73,8 @@
         "eslint-config-airbnb": "^19.0.4",
         "eslint-config-node": "^4.1.0",
         "eslint-config-prettier": "^9.1.0",
-        "eslint-plugin-import": "^2.29.0",
+        "eslint-import-resolver-typescript": "^3.6.1",
+        "eslint-plugin-import": "^2.29.1",
         "eslint-plugin-jsx-a11y": "^6.8.0",
         "eslint-plugin-node": "^11.1.0",
         "eslint-plugin-prettier": "^5.0.1",
@@ -86,9 +89,7 @@
         "ts-loader": "^9.5.1",
         "ts-node": "^10.9.1",
         "ts-node-dev": "^2.0.0",
-        "typescript": "^5.3.3",
-        "webpack-dev-server": "^5.0.4",
-        "webpack-hot-middleware": "^2.25.4"
+        "webpack-dev-server": "^5.0.4"
     },
     "dependencies": {
         "@adobe/react-spectrum": "^3.32.2",
@@ -131,6 +132,8 @@
         "@types/reveal": "^4.2.0",
         "@types/supercluster": "^7.1.3",
         "@types/web": "^0.0.143",
+        "@types/webpack-hot-middleware": "^2.25.9",
+        "@typescript-eslint/parser": "^7.6.0",
         "@webscopeio/react-textarea-autocomplete": "^4.9.2",
         "adm-zip": "^0.5.10",
         "archiver": "^7.0.1",
@@ -167,6 +170,7 @@
         "D": "^1.0.0",
         "d3": "^7.8.5",
         "depcheck": "^1.4.7",
+        "eslint-webpack-plugin": "^4.1.0",
         "exif": "^0.6.0",
         "exifr": "^7.1.3",
         "express": "^4.18.2",
@@ -286,7 +290,9 @@
         "tough-cookie": "^4.1.3",
         "tslint": "^6.1.3",
         "tslint-loader": "^3.5.4",
+        "typescript": "^5.3.3",
         "typescript-collections": "^1.3.3",
+        "typescript-eslint": "^7.6.0",
         "typescript-language-server": "^4.1.3",
         "uninstall": "^0.0.0",
         "url": "^0.11.3",
@@ -298,6 +304,7 @@
         "webpack": "^5.89.0",
         "webpack-cli": "^5.1.4",
         "webpack-dev-middleware": "^7.0.0",
+        "webpack-hot-middleware": "^2.25.4",
         "wikijs": "^6.4.1",
         "words-to-numbers": "^1.5.1",
         "xoauth2": "^1.2.0",
diff --git a/src/ServerUtils.ts b/src/ServerUtils.ts
new file mode 100644
index 000000000..ade4ca35d
--- /dev/null
+++ b/src/ServerUtils.ts
@@ -0,0 +1,27 @@
+import { Socket } from 'socket.io';
+import { Message } from './server/Message';
+import { Utils } from './Utils';
+
+export namespace ServerUtils {
+    export function Emit<T>(socket: Socket, message: Message<T>, args: T) {
+        Utils.log('Emit', message.Name, args, false);
+        socket.emit(message.Message, args);
+    }
+
+    export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) {
+        socket.on(message.Message, Utils.loggingCallback('Incoming', handler, message.Name));
+    }
+
+    export function AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) {
+        socket.on(message.Message, (arg: T, fn: (res: any) => any) => {
+            Utils.log('S receiving', message.Name, arg, true);
+            handler([arg, Utils.loggingCallback('S sending', fn, message.Name)]);
+        });
+    }
+    export type RoomHandler = (socket: Socket, room: string) => any;
+    export type UsedSockets = Socket;
+    export type RoomMessage = 'create or join' | 'created' | 'joined';
+    export function AddRoomHandler(socket: Socket, message: RoomMessage, handler: RoomHandler) {
+        socket.on(message, (room: any) => handler(socket, room));
+    }
+}
diff --git a/src/Utils.ts b/src/Utils.ts
index 291d7c799..0455fd19a 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,52 +1,12 @@
-import * as Color from 'color';
-import { ColorResult } from 'react-color';
-import * as rp from 'request-promise';
-import { Socket } from 'socket.io';
 import * as uuid from 'uuid';
-import { DocumentType } from './client/documents/DocumentTypes';
-import { Colors } from './client/views/global/globalEnums';
-import { DocumentView } from './client/views/nodes/DocumentView';
-import { Message } from './server/Message';
 
+export function clamp(n: number, lower: number, upper: number) {
+    return Math.max(lower, Math.min(upper, n));
+}
 export namespace Utils {
-    export let CLICK_TIME = 300;
-    export let DRAG_THRESHOLD = 4;
-    export let SNAP_THRESHOLD = 10;
-    export function isClick(x: number, y: number, downX: number, downY: number, downTime: number) {
-        return Date.now() - downTime < Utils.CLICK_TIME && Math.abs(x - downX) < Utils.DRAG_THRESHOLD && Math.abs(y - downY) < Utils.DRAG_THRESHOLD;
-    }
-
-    export function cleanDocumentType(type: DocumentType) {
-        switch (type) {
-            case DocumentType.IMG:
-                return 'Image';
-            case DocumentType.AUDIO:
-                return 'Audio';
-            case DocumentType.COL:
-                return 'Collection';
-            case DocumentType.RTF:
-                return 'Text';
-            default:
-                return type.charAt(0).toUpperCase() + type.slice(1);
-        }
-    }
-
-    export function readUploadedFileAsText(inputFile: File) {
-        const temporaryFileReader = new FileReader();
-
-        return new Promise((resolve, reject) => {
-            temporaryFileReader.onerror = () => {
-                temporaryFileReader.abort();
-                reject(new DOMException('Problem parsing input file.'));
-            };
-
-            temporaryFileReader.onload = () => {
-                resolve(temporaryFileReader.result);
-            };
-            temporaryFileReader.readAsText(inputFile);
-        });
+    export function GuestID() {
+        return '__guest__';
     }
-
     export function GenerateGuid(): string {
         return uuid.v4();
     }
@@ -55,49 +15,33 @@ export namespace Utils {
         return uuid.v5(seed, uuid.v5.URL);
     }
 
-    export function GuestID() {
-        return '__guest__';
-    }
+    export const loggingEnabled: Boolean = false;
+    export const logFilter: number | undefined = undefined;
 
-    /**
-     * Uploads an image buffer to the server and stores with specified filename.  by default the image
-     * is stored at multiple resolutions each retrieved by using the filename appended with _o, _s, _m, _l (indicating original, small, medium, or large)
-     * @param imageUri the bytes of the image
-     * @param returnedFilename the base filename to store the image on the server
-     * @param nosuffix optionally suppress creating multiple resolution images
-     */
-    export async function convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false, replaceRootFilename?: string) {
-        try {
-            const posting = Utils.prepend('/uploadURI');
-            const returnedUri = await rp.post(posting, {
-                body: {
-                    uri: imageUri,
-                    name: returnedFilename,
-                    nosuffix,
-                    replaceRootFilename,
-                },
-                json: true,
-            });
-            return returnedUri;
-        } catch (e) {
-            console.log('ConvertDataURI :' + e);
+    export function log(prefixIn: string, messageName: string, messageIn: any, receiving: boolean) {
+        let prefix = prefixIn;
+        let message = messageIn;
+        if (!loggingEnabled) {
+            return;
         }
-    }
-
-    export function GetScreenTransform(ele?: HTMLElement | null): { scale: number; translateX: number; translateY: number } {
-        if (!ele) {
-            return { scale: 1, translateX: 1, translateY: 1 };
+        message = message || {};
+        if (logFilter !== undefined && logFilter !== message.type) {
+            return;
         }
-        const rect = ele.getBoundingClientRect();
-        const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / ele.offsetWidth;
-        const translateX = rect.left;
-        const translateY = rect.top;
+        const idString = (message.id || '').padStart(36, ' ');
+        prefix = prefix.padEnd(16, ' ');
+        console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)} `);
+    }
 
-        return { scale, translateX, translateY };
+    export function loggingCallback(prefix: string, func: (args: any) => any, messageName: string) {
+        return (args: any) => {
+            log(prefix, messageName, args, true);
+            func(args);
+        };
     }
 
     export function TraceConsoleLog() {
-        ['log', 'warn'].forEach(function (method) {
+        ['log', 'warn'].forEach(method => {
             const old = (console as any)[method];
             (console as any)[method] = function () {
                 let stack = new Error('').stack?.split(/\n/);
@@ -112,342 +56,97 @@ export namespace Utils {
         });
     }
 
-    /**
-     * A convenience method. Prepends the full path (i.e. http://localhost:<port>) to the
-     * requested extension
-     * @param extension the specified sub-path to append to the window origin
-     */
-    export function prepend(extension: string): string {
-        return window.location.origin + extension;
-    }
-    export function fileUrl(filename: string): string {
-        return prepend(`/files/${filename}`);
-    }
-
-    export function shareUrl(documentId: string): string {
-        return prepend(`/doc/${documentId}?sharing=true`);
-    }
-
-    export function CorsProxy(url: string): string {
-        return prepend('/corsProxy/') + encodeURIComponent(url);
-    }
-
-    export function CopyText(text: string) {
-        navigator.clipboard.writeText(text);
-    }
-
-    export function decimalToHexString(number: number) {
-        if (number < 0) {
-            number = 0xffffffff + number + 1;
-        }
-        return (number < 16 ? '0' : '') + number.toString(16).toUpperCase();
-    }
-
-    export function colorString(color: ColorResult) {
-        return color.hex.startsWith('#') && color.hex.length < 8 ? color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : 'ff') : color.hex;
-    }
-
-    export function fromRGBAstr(rgba: string) {
-        const rm = rgba.match(/rgb[a]?\(([ 0-9]+)/);
-        const r = rm ? Number(rm[1]) : 0;
-        const gm = rgba.match(/rgb[a]?\([ 0-9]+,([ 0-9]+)/);
-        const g = gm ? Number(gm[1]) : 0;
-        const bm = rgba.match(/rgb[a]?\([ 0-9]+,[ 0-9]+,([ 0-9]+)/);
-        const b = bm ? Number(bm[1]) : 0;
-        const am = rgba.match(/rgba?\([ 0-9]+,[ 0-9]+,[ 0-9]+,([ .0-9]+)/);
-        const a = am ? Number(am[1]) : 1;
-        return { r: r, g: g, b: b, a: a };
-    }
-
-    const isTransparentFunctionHack = 'isTransparent(__value__)';
-    export const noRecursionHack = '__noRecursion';
-
-    // special case filters
-    export const noDragDocsFilter = 'noDragDocs::any::check';
-    export const TransparentBackgroundFilter = `backgroundColor::${isTransparentFunctionHack},${noRecursionHack}::check`; // bcz: hack.  noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
-    export const OpaqueBackgroundFilter = `backgroundColor::${isTransparentFunctionHack},${noRecursionHack}::x`; // bcz: hack.  noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
-
-    export function IsRecursiveFilter(val: string) {
-        return !val.includes(noRecursionHack);
-    }
-    export function HasFunctionFilter(val: string) {
-        if (val.includes(isTransparentFunctionHack)) return (color: string) => color !== '' && DashColor(color).alpha() !== 1;
-        // add other function filters here...
-        return undefined;
-    }
-
-    export function toRGBAstr(col: { r: number; g: number; b: number; a?: number }) {
-        return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')';
-    }
-
-    export function HSLtoRGB(h: number, s: number, l: number) {
-        // Must be fractions of 1
-        // s /= 100;
-        // l /= 100;
-
-        const c = (1 - Math.abs(2 * l - 1)) * s,
-            x = c * (1 - Math.abs(((h / 60) % 2) - 1)),
-            m = l - c / 2;
-        let r = 0,
-            g = 0,
-            b = 0;
-        if (0 <= h && h < 60) {
-            r = c;
-            g = x;
-            b = 0;
-        } else if (60 <= h && h < 120) {
-            r = x;
-            g = c;
-            b = 0;
-        } else if (120 <= h && h < 180) {
-            r = 0;
-            g = c;
-            b = x;
-        } else if (180 <= h && h < 240) {
-            r = 0;
-            g = x;
-            b = c;
-        } else if (240 <= h && h < 300) {
-            r = x;
-            g = 0;
-            b = c;
-        } else if (300 <= h && h < 360) {
-            r = c;
-            g = 0;
-            b = x;
-        }
-        r = Math.round((r + m) * 255);
-        g = Math.round((g + m) * 255);
-        b = Math.round((b + m) * 255);
-        return { r: r, g: g, b: b };
-    }
-
-    export function RGBToHSL(r: number, g: number, b: number) {
-        // Make r, g, and b fractions of 1
-        r /= 255;
-        g /= 255;
-        b /= 255;
-
-        // Find greatest and smallest channel values
-        const cmin = Math.min(r, g, b),
-            cmax = Math.max(r, g, b),
-            delta = cmax - cmin;
-        let h = 0,
-            s = 0,
-            l = 0;
-        // Calculate hue
-
-        // No difference
-        if (delta === 0) h = 0;
-        // Red is max
-        else if (cmax === r) h = ((g - b) / delta) % 6;
-        // Green is max
-        else if (cmax === g) h = (b - r) / delta + 2;
-        // Blue is max
-        else h = (r - g) / delta + 4;
-
-        h = Math.round(h * 60);
-
-        // Make negative hues positive behind 360°
-        if (h < 0) h += 360; // Calculate lightness
-
-        l = (cmax + cmin) / 2;
-
-        // Calculate saturation
-        s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
-
-        // Multiply l and s by 100
-        // s = +(s * 100).toFixed(1);
-        // l = +(l * 100).toFixed(1);
-
-        return { h: h, s: s, l: l };
-    }
-
-    export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number, minSpacing: number, scrollHeight: number) {
-        if (!targetHgt) return targetY; // if there's no height, then assume that
-        if (scrollTop + contextHgt < Math.min(scrollHeight, targetY + minSpacing + targetHgt)) {
-            return Math.ceil(targetY + minSpacing + targetHgt - contextHgt);
-        }
-        if (scrollTop >= Math.max(0, targetY - minSpacing)) {
-            return Math.max(0, Math.floor(targetY - minSpacing));
-        }
-    }
-
-    export function clamp(n: number, lower: number, upper: number) {
-        return Math.max(lower, Math.min(upper, n));
-    }
-
-    export function distanceBetweenHorizontalLines(xs: number, xe: number, y: number, xs2: number, xe2: number, y2: number): [number, number[]] {
-        if ((xs2 <= xs && xe2 >= xs) || (xs2 <= xe && xe2 >= xe) || (xs2 >= xs && xe2 <= xe)) return [Math.abs(y - y2), [Math.max(xs, xs2), y, Math.min(xe, xe2), y]];
-        if (xe2 <= xs) return [Math.sqrt((xe2 - xs) * (xe2 - xs) + (y2 - y) * (y2 - y)), [xs, y, xs, y]];
-        //if (xs2 > xe)
-        return [Math.sqrt((xs2 - xe) * (xs2 - xe) + (y2 - y) * (y2 - y)), [xe, y, xe, y]];
-    }
-    export function distanceBetweenVerticalLines(x: number, ys: number, ye: number, x2: number, ys2: number, ye2: number): [number, number[]] {
-        if ((ys2 <= ys && ye2 >= ys) || (ys2 <= ye && ye2 >= ye) || (ys2 >= ys && ye2 <= ye)) return [Math.abs(x - x2), [x, Math.max(ys, ys2), x, Math.min(ye, ye2)]];
-        if (ye2 <= ys) return [Math.sqrt((ye2 - ys) * (ye2 - ys) + (x2 - x) * (x2 - x)), [x, ys, x, ys]];
-        //if (ys2 > ye)
-        return [Math.sqrt((ys2 - ye) * (ys2 - ye) + (x2 - x) * (x2 - x)), [x, ye, x, ye]];
-    }
-
     export function rotPt(x: number, y: number, radAng: number) {
         return { x: x * Math.cos(radAng) - y * Math.sin(radAng), y: x * Math.sin(radAng) + y * Math.cos(radAng) };
     }
 
-    function project(px: number, py: number, ax: number, ay: number, bx: number, by: number) {
-        if (ax === bx && ay === by) return { point: { x: ax, y: ay }, left: false, dot: 0, t: 0 };
-        const atob = { x: bx - ax, y: by - ay };
-        const atop = { x: px - ax, y: py - ay };
-        const len = atob.x * atob.x + atob.y * atob.y;
-        var dot = atop.x * atob.x + atop.y * atob.y;
-        const t = Math.min(1, Math.max(0, dot / len));
-
-        dot = (bx - ax) * (py - ay) - (by - ay) * (px - ax);
-
-        return {
-            point: {
-                x: ax + atob.x * t,
-                y: ay + atob.y * t,
-            },
-            left: dot < 1,
-            dot: dot,
-            t: t,
-        };
-    }
-
-    export function closestPtBetweenRectangles(l: number, t: number, w: number, h: number, l1: number, t1: number, w1: number, h1: number, x: number, y: number) {
-        const r = l + w,
-            b = t + h;
-        const r1 = l1 + w1,
-            b1 = t1 + h1;
-        const hsegs = [
-            [l, r, t, l1, r1, t1],
-            [l, r, b, l1, r1, t1],
-            [l, r, t, l1, r1, b1],
-            [l, r, b, l1, r1, b1],
-        ];
-        const vsegs = [
-            [l, t, b, l1, t1, b1],
-            [r, t, b, l1, t1, b1],
-            [l, t, b, r1, t1, b1],
-            [r, t, b, r1, t1, b1],
-        ];
-        const res = hsegs.reduce(
-            (closest, seg) => {
-                const res = distanceBetweenHorizontalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
-                return res[0] < closest[0] ? res : closest;
-            },
-            [Number.MAX_VALUE, []] as [number, number[]]
-        );
-        const fres = vsegs.reduce((closest, seg) => {
-            const res = distanceBetweenVerticalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
-            return res[0] < closest[0] ? res : closest;
-        }, res);
+    export function getNearestPointInPerimeter(l: number, t: number, w: number, h: number, xIn: number, yIn: number) {
+        const r = l + w;
+        const b = t + h;
 
-        const near = project(x, y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
-        return project(near.point.x, near.point.y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
-    }
+        const x = clamp(xIn, l, r);
+        const y = clamp(yIn, t, b);
 
-    export function getNearestPointInPerimeter(l: number, t: number, w: number, h: number, x: number, y: number) {
-        const r = l + w,
-            b = t + h;
-
-        (x = clamp(x, l, r)), (y = clamp(y, t, b));
-
-        const dl = Math.abs(x - l),
-            dr = Math.abs(x - r),
-            dt = Math.abs(y - t),
-            db = Math.abs(y - b);
+        const dl = Math.abs(x - l);
+        const dr = Math.abs(x - r);
+        const dt = Math.abs(y - t);
+        const db = Math.abs(y - b);
 
         const m = Math.min(dl, dr, dt, db);
 
         return m === dt ? [x, t] : m === db ? [x, b] : m === dl ? [l, y] : [r, y];
     }
+}
+export function decimalToHexString(numberIn: number) {
+    const number = numberIn < 0 ? 0xffffffff + numberIn + 1 : numberIn;
+    return (number < 16 ? '0' : '') + number.toString(16).toUpperCase();
+}
 
-    export function GetClipboardText(): string {
-        const textArea = document.createElement('textarea');
-        document.body.appendChild(textArea);
-        textArea.focus();
-        textArea.select();
-
-        try {
-            document.execCommand('paste');
-        } catch (err) {}
-
-        const val = textArea.value;
-        document.body.removeChild(textArea);
-        return val;
-    }
-
-    export const loggingEnabled: Boolean = false;
-    export const logFilter: number | undefined = undefined;
-
-    export function log(prefix: string, messageName: string, message: any, receiving: boolean) {
-        if (!loggingEnabled) {
-            return;
-        }
-        message = message || {};
-        if (logFilter !== undefined && logFilter !== message.type) {
-            return;
-        }
-        const idString = (message.id || '').padStart(36, ' ');
-        prefix = prefix.padEnd(16, ' ');
-        console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)} `);
-    }
-
-    export function loggingCallback(prefix: string, func: (args: any) => any, messageName: string) {
-        return (args: any) => {
-            log(prefix, messageName, args, true);
-            func(args);
-        };
-    }
+export function distanceBetweenHorizontalLines(xs: number, xe: number, y: number, xs2: number, xe2: number, y2: number): [number, number[]] {
+    if ((xs2 <= xs && xe2 >= xs) || (xs2 <= xe && xe2 >= xe) || (xs2 >= xs && xe2 <= xe)) return [Math.abs(y - y2), [Math.max(xs, xs2), y, Math.min(xe, xe2), y]];
+    if (xe2 <= xs) return [Math.sqrt((xe2 - xs) * (xe2 - xs) + (y2 - y) * (y2 - y)), [xs, y, xs, y]];
+    // if (xs2 > xe)
+    return [Math.sqrt((xs2 - xe) * (xs2 - xe) + (y2 - y) * (y2 - y)), [xe, y, xe, y]];
+}
+export function distanceBetweenVerticalLines(x: number, ys: number, ye: number, x2: number, ys2: number, ye2: number): [number, number[]] {
+    if ((ys2 <= ys && ye2 >= ys) || (ys2 <= ye && ye2 >= ye) || (ys2 >= ys && ye2 <= ye)) return [Math.abs(x - x2), [x, Math.max(ys, ys2), x, Math.min(ye, ye2)]];
+    if (ye2 <= ys) return [Math.sqrt((ye2 - ys) * (ye2 - ys) + (x2 - x) * (x2 - x)), [x, ys, x, ys]];
+    // if (ys2 > ye)
+    return [Math.sqrt((ys2 - ye) * (ys2 - ye) + (x2 - x) * (x2 - x)), [x, ye, x, ye]];
+}
 
-    export function Emit<T>(socket: Socket, message: Message<T>, args: T) {
-        log('Emit', message.Name, args, false);
-        socket.emit(message.Message, args);
-    }
+function project(px: number, py: number, ax: number, ay: number, bx: number, by: number) {
+    if (ax === bx && ay === by) return { point: { x: ax, y: ay }, left: false, dot: 0, t: 0 };
+    const atob = { x: bx - ax, y: by - ay };
+    const atop = { x: px - ax, y: py - ay };
+    const len = atob.x * atob.x + atob.y * atob.y;
+    let dot = atop.x * atob.x + atop.y * atob.y;
+    const t = Math.min(1, Math.max(0, dot / len));
 
-    export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) {
-        socket.on(message.Message, loggingCallback('Incoming', handler, message.Name));
-    }
+    dot = (bx - ax) * (py - ay) - (by - ay) * (px - ax);
 
-    export function AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) {
-        socket.on(message.Message, (arg: T, fn: (res: any) => any) => {
-            log('S receiving', message.Name, arg, true);
-            handler([arg, loggingCallback('S sending', fn, message.Name)]);
-        });
-    }
-    export type RoomHandler = (socket: Socket, room: string) => any;
-    export type UsedSockets = Socket;
-    export type RoomMessage = 'create or join' | 'created' | 'joined';
-    export function AddRoomHandler(socket: Socket, message: RoomMessage, handler: RoomHandler) {
-        socket.on(message, (room: any) => handler(socket, room));
-    }
+    return {
+        point: {
+            x: ax + atob.x * t,
+            y: ay + atob.y * t,
+        },
+        left: dot < 1,
+        dot: dot,
+        t: t,
+    };
 }
 
-export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?: (dup: any) => void): { omit: any; extract: any } {
-    const omit: any = { ...obj };
-    const extract: any = {};
-    keys.forEach(key => {
-        extract[key] = omit[key];
-        delete omit[key];
-    });
-    pattern &&
-        Array.from(Object.keys(omit))
-            .filter(key => key.match(pattern))
-            .forEach(key => {
-                extract[key] = omit[key];
-                delete omit[key];
-            });
-    addKeyFunc?.(omit);
-    return { omit, extract };
-}
+export function closestPtBetweenRectangles(l: number, t: number, w: number, h: number, l1: number, t1: number, w1: number, h1: number, x: number, y: number) {
+    const r = l + w;
+    const b = t + h;
+    const r1 = l1 + w1;
+    const b1 = t1 + h1;
+    const hsegs = [
+        [l, r, t, l1, r1, t1],
+        [l, r, b, l1, r1, t1],
+        [l, r, t, l1, r1, b1],
+        [l, r, b, l1, r1, b1],
+    ];
+    const vsegs = [
+        [l, t, b, l1, t1, b1],
+        [r, t, b, l1, t1, b1],
+        [l, t, b, r1, t1, b1],
+        [r, t, b, r1, t1, b1],
+    ];
+    const res = hsegs.reduce(
+        (closest, seg) => {
+            const res = distanceBetweenHorizontalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
+            return res[0] < closest[0] ? res : closest;
+        },
+        [Number.MAX_VALUE, []] as [number, number[]]
+    );
+    const fres = vsegs.reduce((closest, seg) => {
+        const res = distanceBetweenVerticalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
+        return res[0] < closest[0] ? res : closest;
+    }, res);
 
-export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void) {
-    const dup: any = {};
-    keys.forEach(key => (dup[key] = obj[key]));
-    addKeyFunc && addKeyFunc(dup);
-    return dup;
+    const near = project(x, y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
+    return project(near.point.x, near.point.y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
 }
 
 export function timenow() {
@@ -455,7 +154,6 @@ export function timenow() {
     let ampm = 'am';
     let h = now.getHours();
     let m: any = now.getMinutes();
-    const s: any = now.getSeconds();
     if (h >= 12) {
         if (h > 12) h -= 12;
         ampm = 'pm';
@@ -464,14 +162,8 @@ export function timenow() {
     return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm;
 }
 
-export function incrementTitleCopy(title: string) {
-    const numstr = title.match(/.*(\{([0-9]*)\})+/);
-    const copyNumStr = `{${1 + (numstr ? +numstr[2] : 0)}}`;
-    return (numstr ? title.replace(numstr[1], '') : title) + copyNumStr;
-}
-
-export function formatTime(time: number) {
-    time = Math.round(time);
+export function formatTime(timeIn: number) {
+    const time = Math.round(timeIn);
     const hours = Math.floor(time / 60 / 60);
     const minutes = Math.floor(time / 60) - hours * 60;
     const seconds = time % 60;
@@ -503,93 +195,31 @@ export function intersectRect(r1: { left: number; top: number; width: number; he
 }
 
 export function stringHash(s?: string) {
+    // eslint-disable-next-line no-bitwise
     return !s ? undefined : Math.abs(s.split('').reduce((a: any, b: any) => (a => a & a)((a << 5) - a + b.charCodeAt(0)), 0));
 }
 
 export function percent2frac(percent: string) {
     return Number(percent.substr(0, percent.length - 1)) / 100;
 }
+export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
+
+export type Predicate<K, V> = (entry: [K, V]) => boolean;
 
 export function numberRange(num: number) {
     return num > 0 && num < 1000 ? Array.from(Array(num)).map((v, i) => i) : [];
 }
 
-export function returnTransparent() {
-    return 'transparent';
-}
-
-export function returnTrue() {
-    return true;
-}
-
-export function returnIgnore(): 'ignore' {
-    return 'ignore';
-}
-export function returnAlways(): 'always' {
-    return 'always';
-}
-export function returnNever(): 'never' {
-    return 'never';
-}
-
-export function returnDefault(): 'default' {
-    return 'default';
-}
-
-export function return18() {
-    return 18;
-}
-
-export function returnFalse() {
-    return false;
-}
-
-export function returnAll(): 'all' {
-    return 'all';
-}
-
-export function returnNone(): 'none' {
-    return 'none';
-}
-
-export function returnVal(val1?: number, val2?: number) {
-    return val1 ? val1 : val2 !== undefined ? val2 : 0;
-}
-
-export function returnOne() {
-    return 1;
-}
-
-export function returnZero() {
-    return 0;
-}
-
-export function returnEmptyString() {
-    return '';
-}
-
-export function returnEmptyFilter() {
-    return [] as string[];
-}
-
-export function returnEmptyDoclist() {
-    return [] as any[];
-}
-
-export let emptyPath: DocumentView[] = [];
-
 export function emptyFunction() {
     return undefined;
 }
 
+export const emptyPath: any[] = [];
+
 export function unimplementedFunction() {
     throw new Error('This function is not implemented, but should be.');
 }
 
-export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
-
-export type Predicate<K, V> = (entry: [K, V]) => boolean;
-
 export function DeepCopy<K, V>(source: Map<K, V>, predicate?: Predicate<K, V>) {
     const deepCopy = new Map<K, V>();
     const entries = source.entries();
@@ -616,197 +246,6 @@ export namespace JSONUtils {
     }
 }
 
-const easeFunc = (transition: 'ease' | 'linear' | undefined, currentTime: number, start: number, change: number, duration: number) => {
-    if (transition === 'linear') {
-        let newCurrentTime = currentTime / duration; //  currentTime / (duration / 2);
-        return start + newCurrentTime * change;
-    }
-
-    let newCurrentTime = currentTime / (duration / 2);
-    if (newCurrentTime < 1) {
-        return (change / 2) * newCurrentTime * newCurrentTime + start;
-    }
-
-    newCurrentTime -= 1;
-    return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start;
-};
-
-export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number, transition: 'ease' | 'linear' | undefined, stopper?: () => void) {
-    stopper?.();
-    const elements = element instanceof HTMLElement ? [element] : element;
-    const starts = elements.map(element => element.scrollTop);
-    const startDate = new Date().getTime();
-    let _stop = false;
-    const stop = () => (_stop = true);
-    const animateScroll = () => {
-        const currentDate = new Date().getTime();
-        const currentTime = currentDate - startDate;
-        const setScrollTop = (element: HTMLElement, value: number) => (element.scrollTop = value);
-        if (!_stop) {
-            if (currentTime < duration) {
-                elements.forEach((element, i) => currentTime && setScrollTop(element, easeFunc(transition, Math.min(currentTime, duration), starts[i], to - starts[i], duration)));
-                requestAnimationFrame(animateScroll);
-            } else {
-                elements.forEach(element => setScrollTop(element, to));
-            }
-        }
-    };
-    animateScroll();
-    return stop;
-}
-
-export function smoothScrollHorizontal(duration: number, element: HTMLElement | HTMLElement[], to: number) {
-    const elements = element instanceof HTMLElement ? [element] : element;
-    const starts = elements.map(element => element.scrollLeft);
-    const startDate = new Date().getTime();
-
-    const animateScroll = () => {
-        const currentDate = new Date().getTime();
-        const currentTime = currentDate - startDate;
-        elements.map((element, i) => (element.scrollLeft = easeFunc('ease', currentTime, starts[i], to - starts[i], duration)));
-
-        if (currentTime < duration) {
-            requestAnimationFrame(animateScroll);
-        } else {
-            elements.forEach(element => (element.scrollLeft = to));
-        }
-    };
-    animateScroll();
-}
-
-export function addStyleSheet(styleType: string = 'text/css') {
-    const style = document.createElement('style');
-    style.type = styleType;
-    const sheets = document.head.appendChild(style);
-    return (sheets as any).sheet;
-}
-export function addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') {
-    const propText =
-        typeof css === 'string'
-            ? css
-            : Object.keys(css)
-                  .map(p => p + ':' + (p === 'content' ? "'" + css[p] + "'" : css[p]))
-                  .join(';');
-    return sheet.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length);
-}
-export function removeStyleSheetRule(sheet: any, rule: number) {
-    if (sheet.rules.length) {
-        sheet.removeRule(rule);
-        return true;
-    }
-    return false;
-}
-export function clearStyleSheetRules(sheet: any) {
-    if (sheet.rules.length) {
-        numberRange(sheet.rules.length).map(n => sheet.removeRule(0));
-        return true;
-    }
-    return false;
-}
-
-export function simulateMouseClick(element: Element | null | undefined, x: number, y: number, sx: number, sy: number, rightClick = true) {
-    if (!element) return;
-    ['pointerdown', 'pointerup'].map(event => {
-        const me = new PointerEvent(event, {
-            view: window,
-            bubbles: true,
-            cancelable: true,
-            button: 2,
-            pointerType: 'mouse',
-            clientX: x,
-            clientY: y,
-            screenX: sx,
-            screenY: sy,
-        });
-        (me as any).dash = true;
-        element.dispatchEvent(me);
-    });
-
-    if (rightClick) {
-        const me = new MouseEvent('contextmenu', {
-            view: window,
-            bubbles: true,
-            cancelable: true,
-            button: 2,
-            clientX: x,
-            clientY: y,
-            movementX: 0,
-            movementY: 0,
-            screenX: sx,
-            screenY: sy,
-        });
-        (me as any).dash = true;
-        element.dispatchEvent(me);
-    }
-}
-
-export function DashColor(color: string) {
-    try {
-        return color ? Color(color.toLowerCase()) : Color('transparent');
-    } catch (e) {
-        if (color.includes('gradient')) console.log("using color 'white' in place of :" + color);
-        else console.log('COLOR error:', e);
-        return Color('white');
-    }
-}
-
-export function lightOrDark(color: any) {
-    if (color === 'transparent' || !color) return Colors.BLACK;
-    if (color.startsWith?.('linear')) return Colors.BLACK;
-    if (DashColor(color).isLight()) return Colors.BLACK;
-    return Colors.WHITE;
-}
-
-export function getWordAtPoint(elem: any, x: number, y: number): string | undefined {
-    if (elem.tagName === 'INPUT') return 'input';
-    if (elem.tagName === 'TEXTAREA') return 'textarea';
-    if (elem.nodeType === elem.TEXT_NODE) {
-        const range = elem.ownerDocument.createRange();
-        range.selectNodeContents(elem);
-        var currentPos = 0;
-        const endPos = range.endOffset;
-        while (currentPos + 1 <= endPos) {
-            range.setStart(elem, currentPos);
-            range.setEnd(elem, currentPos + 1);
-            const rangeRect = range.getBoundingClientRect();
-            if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y) {
-                range.expand?.('word'); // doesn't exist in firefox
-                const ret = range.toString();
-                range.detach();
-                return ret;
-            }
-            currentPos += 1;
-        }
-    } else {
-        for (const childNode of elem.childNodes) {
-            const range = childNode.ownerDocument.createRange();
-            range.selectNodeContents(childNode);
-            const rangeRect = range.getBoundingClientRect();
-            if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y) {
-                range.detach();
-                const word = getWordAtPoint(childNode, x, y);
-                if (word) return word;
-            } else {
-                range.detach();
-            }
-        }
-    }
-    return undefined;
-}
-
-export function isTargetChildOf(ele: HTMLDivElement | null, target: Element | null) {
-    let entered = false;
-    for (let child = target; !entered && child; child = child.parentElement) {
-        entered = child === ele;
-    }
-    return entered;
-}
-
-export function StopEvent(e: React.PointerEvent | React.MouseEvent) {
-    e.stopPropagation();
-    e.preventDefault();
-}
-
 /**
  * Helper method for converting pixel string eg. '32px' into number eg. 32
  * @param value: string with 'px' ending
@@ -816,100 +255,10 @@ export function StopEvent(e: React.PointerEvent | React.MouseEvent) {
  * '32px' -> 32
  */
 export function numberValue(value: string | undefined): number {
-    if (value == undefined) return 0;
+    if (value === undefined) return 0;
     return parseInt(value);
 }
 
 export function numbersAlmostEqual(num1: number, num2: number) {
     return Math.abs(num1 - num2) < 0.2;
 }
-
-export function setupMoveUpEvents(
-    target: object,
-    e: React.PointerEvent,
-    moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean,
-    upEvent: (e: PointerEvent, movement: number[], isClick: boolean) => any,
-    clickEvent: (e: PointerEvent, doubleTap?: boolean) => any,
-    stopPropagation: boolean = true,
-    stopMovePropagation: boolean = true,
-    noDoubleTapTimeout?: () => void
-) {
-    const doubleTapTimeout = 300;
-    (target as any)._doubleTap = Date.now() - (target as any)._lastTap < doubleTapTimeout;
-    (target as any)._lastTap = Date.now();
-    (target as any)._downX = (target as any)._lastX = e.clientX;
-    (target as any)._downY = (target as any)._lastY = e.clientY;
-    (target as any)._noClick = false;
-    var moving = false;
-
-    const _moveEvent = (e: PointerEvent): void => {
-        if (moving || Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) {
-            moving = true;
-            if ((target as any)._doubleTime) {
-                clearTimeout((target as any)._doubleTime);
-                (target as any)._doubleTime = undefined;
-            }
-            if (moveEvent(e, [(target as any)._downX, (target as any)._downY], [e.clientX - (target as any)._lastX, e.clientY - (target as any)._lastY])) {
-                document.removeEventListener('pointermove', _moveEvent);
-                document.removeEventListener('pointerup', _upEvent);
-            }
-        }
-        (target as any)._lastX = e.clientX;
-        (target as any)._lastY = e.clientY;
-        stopMovePropagation && e.stopPropagation();
-    };
-    const _upEvent = (e: PointerEvent): void => {
-        const isClick = Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4;
-        upEvent(e, [e.clientX - (target as any)._downX, e.clientY - (target as any)._downY], isClick);
-        if (isClick) {
-            if (!(target as any)._doubleTime && noDoubleTapTimeout) {
-                (target as any)._doubleTime = setTimeout(() => {
-                    noDoubleTapTimeout?.();
-                    (target as any)._doubleTime = undefined;
-                }, doubleTapTimeout);
-            }
-            if ((target as any)._doubleTime && (target as any)._doubleTap) {
-                clearTimeout((target as any)._doubleTime);
-                (target as any)._doubleTime = undefined;
-            }
-            (target as any)._noClick = clickEvent(e, (target as any)._doubleTap);
-        }
-        document.removeEventListener('pointermove', _moveEvent);
-        document.removeEventListener('pointerup', _upEvent, true);
-    };
-    const _clickEvent = (e: MouseEvent): void => {
-        if ((target as any)._noClick) e.stopPropagation();
-        document.removeEventListener('click', _clickEvent, true);
-    };
-    if (stopPropagation) {
-        e.stopPropagation();
-        e.preventDefault();
-    }
-    document.addEventListener('pointermove', _moveEvent);
-    document.addEventListener('pointerup', _upEvent, true);
-    document.addEventListener('click', _clickEvent, true);
-}
-
-export function DivHeight(ele: HTMLElement): number {
-    return Number(getComputedStyle(ele).height.replace('px', ''));
-}
-export function DivWidth(ele: HTMLElement): number {
-    return Number(getComputedStyle(ele).width.replace('px', ''));
-}
-
-export function dateRangeStrToDates(dateStr: string) {
-    // dateStr in yyyy-mm-dd format
-    const dateRangeParts = dateStr.split('|'); // splits into from and to date
-    const fromParts = dateRangeParts[0].split('-');
-    const toParts = dateRangeParts[1].split('-');
-
-    const fromYear = parseInt(fromParts[0]);
-    const fromMonth = parseInt(fromParts[1]) - 1;
-    const fromDay = parseInt(fromParts[2]);
-
-    const toYear = parseInt(toParts[0]);
-    const toMonth = parseInt(toParts[1]) - 1;
-    const toDay = parseInt(toParts[2]);
-
-    return [new Date(fromYear, fromMonth, fromDay), new Date(toYear, toMonth, toDay)];
-}
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 321572071..804e6d1d7 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,20 +1,15 @@
 import { runInAction } from 'mobx';
-import * as rp from 'request-promise';
-import { Doc, DocListCast, Opt } from '../fields/Doc';
+import { Socket, io } from 'socket.io-client';
+import { ClientUtils } from '../ClientUtils';
+import { Utils, emptyFunction } from '../Utils';
+import { Doc, Opt } from '../fields/Doc';
 import { UpdatingFromServer } from '../fields/DocSymbols';
 import { FieldLoader } from '../fields/FieldLoader';
 import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols';
-import { ObjectField } from '../fields/ObjectField';
+import { ObjectField, SetObjGetRefField, SetObjGetRefFields } from '../fields/ObjectField';
 import { RefField } from '../fields/RefField';
-import { DocCast, StrCast } from '../fields/Types';
-//import MobileInkOverlay from '../mobile/MobileInkOverlay';
-import { io, Socket } from 'socket.io-client';
-import { emptyFunction, Utils } from '../Utils';
 import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message';
-import { DocumentType } from './documents/DocumentTypes';
-import { LinkManager } from './util/LinkManager';
 import { SerializationHelper } from './util/SerializationHelper';
-//import { GestureOverlay } from './views/GestureOverlay';
 
 /**
  * This class encapsulates the transfer and cross-client synchronization of
@@ -30,23 +25,32 @@ import { SerializationHelper } from './util/SerializationHelper';
  * or update ourselves based on the server's update message, that occurs here
  */
 export namespace DocServer {
-    let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
+    // eslint-disable-next-line import/no-mutable-exports
+    export let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
+
+    function errorFunc(): never {
+        throw new Error("Can't use DocServer without calling init first");
+    }
+    let _UpdateField: (id: string, diff: any) => void = errorFunc;
+    let _CreateField: (field: RefField) => void = errorFunc;
 
     export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) {
         socket.on(message.Message, Utils.loggingCallback('Incoming', handler, message.Name));
     }
     export function Emit<T>(socket: Socket, message: Message<T>, args: T) {
-        //log('Emit', message.Name, args, false);
+        // log('Emit', message.Name, args, false);
         socket.emit(message.Message, args);
     }
     export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T): Promise<any>;
     export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn: (args: any) => any): void;
     export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> {
-        //log('Emit', message.Name, args, false);
+        // log('Emit', message.Name, args, false);
         if (fn) {
             socket.emit(message.Message, args, Utils.loggingCallback('Receiving', fn, message.Name));
         } else {
-            return new Promise<any>(res => socket.emit(message.Message, args, Utils.loggingCallback('Receiving', res, message.Name)));
+            return new Promise<any>(res => {
+                socket.emit(message.Message, args, Utils.loggingCallback('Receiving', res, message.Name));
+            });
         }
     }
 
@@ -59,57 +63,21 @@ export namespace DocServer {
 
         return foundDocId ? (_cache[foundDocId] as Doc) : undefined;
     }
-    let cacheDocumentIds = ''; // ; separate string of all documents ids in the user's working set (cached on the server)
-    export let CacheNeedsUpdate = false;
-    export function UPDATE_SERVER_CACHE() {
-        const prototypes = Object.values(DocumentType)
-            .filter(type => type !== DocumentType.NONE)
-            .map(type => _cache[type + 'Proto'])
-            .filter(doc => doc instanceof Doc)
-            .map(doc => doc as Doc);
-        const references = new Set<Doc>(prototypes);
-        Doc.FindReferences(Doc.UserDoc(), references, undefined);
-        DocListCast(DocCast(Doc.UserDoc().myLinkDatabase).data).forEach(link => {
-            if (!references.has(DocCast(link.link_anchor_1)) && !references.has(DocCast(link.link_anchor_2))) {
-                Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase), 'data', link);
-                Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
-            }
-        });
-        LinkManager.Instance.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
-        const filtered = Array.from(references);
-
-        const newCacheUpdate = filtered.map(doc => doc[Id]).join(';');
-        if (newCacheUpdate === cacheDocumentIds) return;
-        cacheDocumentIds = newCacheUpdate;
-
-        // print out cached docs
-        Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = ');
-        const is_filtered = filtered.filter(doc => !Doc.IsSystem(doc));
-        const strings = is_filtered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
-        Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
-
-        rp.post(Utils.prepend('/setCacheDocumentIds'), {
-            body: {
-                cacheDocumentIds,
-            },
-            json: true,
-        });
-    }
     export let _socket: Socket;
     // this client's distinct GUID created at initialization
     let USER_ID: string;
     // indicates whether or not a document is currently being udpated, and, if so, its id
 
     export enum WriteMode {
-        Default = 0, //Anything goes
-        Playground = 1, //Playground (write own/no read other updates)
-        LiveReadonly = 2, //Live Readonly (no write/read others)
-        LivePlayground = 3, //Live Playground (write own/read others)
+        Default = 0, // Anything goes
+        Playground = 1, // Playground (write own/no read other updates)
+        LiveReadonly = 2, // Live Readonly (no write/read others)
+        LivePlayground = 3, // Live Playground (write own/read others)
     }
     const fieldWriteModes: { [field: string]: WriteMode } = {};
     const docsWithUpdates: { [field: string]: Set<Doc> } = {};
 
-    export var PlaygroundFields: string[] = [];
+    export const PlaygroundFields: string[] = [];
     export function setLivePlaygroundFields(livePlaygroundFields: string[]) {
         DocServer.PlaygroundFields.push(...livePlaygroundFields);
         livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
@@ -134,7 +102,7 @@ export namespace DocServer {
     }
 
     export function getFieldWriteMode(field: string) {
-        return Doc.CurrentUserEmail === 'guest' ? WriteMode.LivePlayground : fieldWriteModes[field] || WriteMode.Default;
+        return ClientUtils.CurrentUserEmail === 'guest' ? WriteMode.LivePlayground : fieldWriteModes[field] || WriteMode.Default;
     }
 
     export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) {
@@ -185,56 +153,14 @@ export namespace DocServer {
         window.location.reload();
     }
 
-    export function init(protocol: string, hostname: string, port: number, identifier: string) {
-        _cache = {};
-        USER_ID = identifier;
-        protocol = protocol.startsWith('https') ? 'wss' : 'ws';
-        _socket = io(`${protocol}://${hostname}:${port}`, { transports: ['websocket'], rejectUnauthorized: false });
-        _socket.on("connect_error", (err:any) => console.log(err));
-        // io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
-
-        _GetCachedRefField = _GetCachedRefFieldImpl;
-        _GetRefField = _GetRefFieldImpl;
-        _GetRefFields = _GetRefFieldsImpl;
-        _CreateField = _CreateFieldImpl;
-        _UpdateField = _UpdateFieldImpl;
-
-        /**
-         * Whenever the server sends us its handshake message on our
-         * websocket, we use the above function to return the handshake.
-         */
-        DocServer.AddServerHandler(_socket, MessageStore.Foo, onConnection);
-        DocServer.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
-        DocServer.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
-        DocServer.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
-        DocServer.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
-
-        // // mobile ink overlay socket events to communicate between mobile view and desktop view
-        // _socket.addEventListener('receiveGesturePoints', (content: GestureContent) => {
-        //     //   MobileInkOverlay.Instance.drawStroke(content);
-        // });
-        // _socket.addEventListener('receiveOverlayTrigger', (content: MobileInkOverlayContent) => {
-        //     //GestureOverlay.Instance.enableMobileInkOverlay(content);
-        //     //    MobileInkOverlay.Instance.initMobileInkOverlay(content);
-        // });
-        // _socket.addEventListener('receiveUpdateOverlayPosition', (content: UpdateMobileInkOverlayPositionContent) => {
-        //     //   MobileInkOverlay.Instance.updatePosition(content);
-        // });
-        // _socket.addEventListener('receiveMobileDocumentUpload', (content: MobileDocumentUploadContent) => {
-        //     //   MobileInkOverlay.Instance.uploadDocument(content);
-        // });
-    }
-
-    function errorFunc(): never {
-        throw new Error("Can't use DocServer without calling init first");
-    }
-
     export namespace Control {
         let _isReadOnly = false;
         export function makeReadOnly() {
             if (!_isReadOnly) {
                 _isReadOnly = true;
-                _CreateField = field => (_cache[field[Id]] = field);
+                _CreateField = field => {
+                    _cache[field[Id]] = field;
+                };
                 _UpdateField = emptyFunction;
                 // _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB
             }
@@ -313,7 +239,8 @@ export namespace DocServer {
                     });
                     cached[UpdatingFromServer] = false;
                     return cached;
-                } else if (field !== undefined) {
+                }
+                if (field !== undefined) {
                     _cache[id] = field;
                 } else {
                     delete _cache[id];
@@ -327,24 +254,25 @@ export namespace DocServer {
             // being retrieved and cached
             !force && (_cache[id] = deserializeField);
             return force ? (cached as any) : deserializeField;
-        } else if (cached instanceof Promise) {
+        }
+        if (cached instanceof Promise) {
             // BEING RETRIEVED AND CACHED => some other caller previously (likely recently) called GetRefField(s),
             // and requested the document I'm looking for. Shouldn't fetch again, just
             // return this promise which will resolve to the field itself (see 7)
             return cached;
-        } else {
-            // CACHED => great, let's just return the cached field we have
-            return Promise.resolve(cached).then(field => {
-                //(field instanceof Doc) && fetchProto(field);
-                return field;
-            });
         }
+        // CACHED => great, let's just return the cached field we have
+        return Promise.resolve(cached).then(
+            field => field
+            // (field instanceof Doc) && fetchProto(field);
+        );
     };
     const _GetCachedRefFieldImpl = (id: string): Opt<RefField> => {
         const cached = _cache[id];
         if (cached !== undefined && !(cached instanceof Promise)) {
             return cached;
         }
+        return undefined;
     };
 
     let _GetRefField: (id: string, force: boolean) => Promise<Opt<RefField>> = errorFunc;
@@ -358,7 +286,7 @@ export namespace DocServer {
     }
 
     export async function getYoutubeChannels() {
-        return await DocServer.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
+        return DocServer.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.Channels });
     }
 
     export function getYoutubeVideos(videoTitle: string, callBack: (videos: any[]) => void) {
@@ -379,21 +307,24 @@ export namespace DocServer {
         const requestedIds: string[] = [];
         const promises: Promise<any>[] = [];
 
-        let defaultRes: any = undefined;
-        const defaultPromise = new Promise<any>(res => (defaultRes = res));
-        let defaultPromises: { p: Promise<any>; id: string }[] = [];
+        let defaultRes: any;
+        const defaultPromise = new Promise<any>(res => {
+            defaultRes = res;
+        });
+        const defaultPromises: { p: Promise<any>; id: string }[] = [];
         // 1) an initial pass through the cache to determine
         // i) which documents need to be fetched
         // ii) which are already in the process of being fetched
         // iii) which already exist in the cache
+        // eslint-disable-next-line no-restricted-syntax
         for (const id of ids.filter(id => id)) {
             const cached = _cache[id];
             if (cached === undefined) {
                 defaultPromises.push({
                     id,
-                    p: (_cache[id] = new Promise<any>(async res => {
-                        await defaultPromise;
-                        res(_cache[id]);
+                    // eslint-disable-next-line no-loop-func
+                    p: (_cache[id] = new Promise<any>(res => {
+                        defaultPromise.then(() => res(_cache[id]));
                     })),
                 });
                 // NOT CACHED => we'll have to send a request to the server
@@ -415,7 +346,11 @@ export namespace DocServer {
             // fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of
             // the fields have been returned from the server
             console.log('Requesting ' + requestedIds.length);
-            setTimeout(() => runInAction(() => (FieldLoader.ServerLoadStatus.requested = requestedIds.length)));
+            setTimeout(() =>
+                runInAction(() => {
+                    FieldLoader.ServerLoadStatus.requested = requestedIds.length;
+                })
+            );
             const serializedFields = await DocServer.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds);
 
             // 3) when the serialized RefFields have been received, go head and begin deserializing them into objects.
@@ -423,11 +358,17 @@ export namespace DocServer {
             // future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
             let processed = 0;
             console.log('deserializing ' + serializedFields.length + ' fields');
+            // eslint-disable-next-line no-restricted-syntax
             for (const field of serializedFields) {
                 processed++;
                 if (processed % 150 === 0) {
-                    runInAction(() => (FieldLoader.ServerLoadStatus.retrieved = processed));
-                    await new Promise(res => setTimeout(res)); // force loading to yield to splash screen rendering to update progress
+                    runInAction(() => {
+                        FieldLoader.ServerLoadStatus.retrieved = processed;
+                    });
+                    // eslint-disable-next-line no-await-in-loop
+                    await new Promise(res => {
+                        setTimeout(res);
+                    }); // force loading to yield to splash screen rendering to update progress
                 }
                 const cached = _cache[field.id];
                 if (!cached || (cached instanceof Promise && defaultPromises.some(dp => dp.p === cached))) {
@@ -435,7 +376,7 @@ export namespace DocServer {
                     // adds to a list of promises that will be awaited asynchronously
                     promises.push(
                         (_cache[field.id] = SerializationHelper.Deserialize(field).then(deserialized => {
-                            //overwrite or delete any promises (that we inserted as flags
+                            // overwrite or delete any promises (that we inserted as flags
                             // to indicate that the field was in the process of being fetched). Now everything
                             // should be an actual value within or entirely absent from the cache.
                             if (deserialized !== undefined) {
@@ -455,7 +396,7 @@ export namespace DocServer {
                     // get the resolved field
                 } else if (cached instanceof Promise) {
                     console.log('.');
-                    //promises.push(cached);
+                    // promises.push(cached);
                 } else if (field) {
                     // console.log('-');
                 }
@@ -472,7 +413,7 @@ export namespace DocServer {
         // 6) with this confidence, we can now go through and update the cache at the ids of the fields that
         // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given
         // id to the soon-to-be-returned field mapping.
-        //ids.forEach(id => (map[id] = _cache[id] as any));
+        // ids.forEach(id => (map[id] = _cache[id] as any));
 
         // 7) those promises we encountered in the else if of 1), which represent
         // other callers having already submitted a request to the server for (a) document(s)
@@ -481,7 +422,7 @@ export namespace DocServer {
         //
         // fortunately, those other callers will also hit their own version of 6) and clean up
         // the shared cache when these promises resolve, so all we have to do is...
-        //const otherCallersFetching = await Promise.all(promises);
+        // const otherCallersFetching = await Promise.all(promises);
         // ...extract the RefFields returned from the resolution of those promises and add them to our
         // own map.
         // waitingIds.forEach((id, index) => (map[id] = otherCallersFetching[index]));
@@ -506,6 +447,7 @@ export namespace DocServer {
     }
 
     // WRITE A NEW DOCUMENT TO THE SERVER
+    export let CacheNeedsUpdate = false;
 
     /**
      * A wrapper around the function local variable _createField.
@@ -521,11 +463,9 @@ export namespace DocServer {
     function _CreateFieldImpl(field: RefField) {
         _cache[field[Id]] = field;
         const initialState = SerializationHelper.Serialize(field);
-        Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.CreateField, initialState);
+        ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.CreateField, initialState);
     }
 
-    let _CreateField: (field: RefField) => void = errorFunc;
-
     // NOTIFY THE SERVER OF AN UPDATE TO A DOC'S STATE
 
     /**
@@ -541,13 +481,11 @@ export namespace DocServer {
     }
 
     function _UpdateFieldImpl(id: string, diff: any) {
-        !DocServer.Control.isReadOnly() && Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.UpdateField, { id, diff });
+        !DocServer.Control.isReadOnly() && ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.UpdateField, { id, diff });
     }
 
-    let _UpdateField: (id: string, diff: any) => void = errorFunc;
-
     function _respondToUpdateImpl(diff: any) {
-        const id = diff.id;
+        const { id } = diff;
         // to be valid, the Diff object must reference
         // a document's id
         if (id === undefined) {
@@ -578,11 +516,11 @@ export namespace DocServer {
     }
 
     export function DeleteDocument(id: string) {
-        Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteField, id);
+        ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteField, id);
     }
 
     export function DeleteDocuments(ids: string[]) {
-        Doc.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteFields, ids);
+        ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Emit(_socket, MessageStore.DeleteFields, ids);
     }
 
     function _respondToDeleteImpl(ids: string | string[]) {
@@ -596,7 +534,7 @@ export namespace DocServer {
         }
     }
 
-    let _RespondToUpdate = _respondToUpdateImpl;
+    const _RespondToUpdate = _respondToUpdateImpl;
     const _respondToDelete = _respondToDeleteImpl;
 
     function respondToUpdate(diff: any) {
@@ -606,4 +544,28 @@ export namespace DocServer {
     function respondToDelete(ids: string | string[]) {
         _respondToDelete(ids);
     }
+
+    export function init(protocol: string, hostname: string, port: number, identifier: string) {
+        _cache = {};
+        USER_ID = identifier;
+        _socket = io(`${protocol.startsWith('https') ? 'wss' : 'ws'}://${hostname}:${port}`, { transports: ['websocket'], rejectUnauthorized: false });
+        _socket.on('connect_error', (err: any) => console.log(err));
+        // io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket
+
+        _GetCachedRefField = _GetCachedRefFieldImpl;
+        SetObjGetRefField((_GetRefField = _GetRefFieldImpl));
+        SetObjGetRefFields((_GetRefFields = _GetRefFieldsImpl));
+        _CreateField = _CreateFieldImpl;
+        _UpdateField = _UpdateFieldImpl;
+
+        /**
+         * Whenever the server sends us its handshake message on our
+         * websocket, we use the above function to return the handshake.
+         */
+        DocServer.AddServerHandler(_socket, MessageStore.Foo, onConnection);
+        DocServer.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
+        DocServer.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
+        DocServer.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
+        DocServer.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
+    }
 }
diff --git a/src/client/Network.ts b/src/client/Network.ts
index cdcd5225a..968c407b2 100644
--- a/src/client/Network.ts
+++ b/src/client/Network.ts
@@ -1,5 +1,6 @@
-import { Utils } from '../Utils';
 import * as requestPromise from 'request-promise';
+import { ClientUtils } from '../ClientUtils';
+import { Utils } from '../Utils';
 import { Upload } from '../server/SharedMediaTypes';
 
 /**
@@ -14,7 +15,7 @@ export namespace Networking {
 
     export async function PostToServer(relativeRoute: string, body?: any) {
         const options = {
-            uri: Utils.prepend(relativeRoute),
+            uri: ClientUtils.prepend(relativeRoute),
             method: 'POST',
             body,
             json: true,
@@ -49,14 +50,14 @@ export namespace Networking {
             }
             const maxFileSize = 50000000;
             if (fileguidpairs.some(f => f.file.size > maxFileSize)) {
-                return new Promise<any>(res =>
+                return new Promise<any>(res => {
                     res([
                         {
                             source: { name: '', type: '', size: 0, toJson: () => ({ name: '', type: '' }) },
                             result: { name: '', message: `max file size (${maxFileSize / 1000000}MB) exceeded` },
                         },
-                    ])
-                );
+                    ]);
+                });
             }
             formData.set('fileguids', fileguidpairs.map(pair => pair.guid).join(';'));
             formData.set('filesize', fileguidpairs.reduce((sum, pair) => sum + pair.file.size, 0).toString());
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index e8fd8fb8a..757031fec 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -1,5 +1,6 @@
 import { AssertionError } from 'assert';
 import { EditorState } from 'prosemirror-state';
+import { ClientUtils } from '../../../ClientUtils';
 import { Doc, DocListCastAsync, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { RichTextField } from '../../../fields/RichTextField';
@@ -7,9 +8,8 @@ import { RichTextUtils } from '../../../fields/RichTextUtils';
 import { Cast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { MediaItem, NewMediaItemResult } from '../../../server/apis/google/SharedTypes';
-import { Utils } from '../../../Utils';
-import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
 import { Networking } from '../../Network';
+import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents';
 import { FormattedTextBox } from '../../views/nodes/formattedText/FormattedTextBox';
 import { GoogleAuthenticationManager } from '../GoogleAuthenticationManager';
 import Photos = require('googlephotos');
@@ -111,7 +111,7 @@ export namespace GooglePhotos {
                     await Query.TagChildImages(collection);
                 }
                 collection.albumId = id;
-                Transactions.AddTextEnrichment(collection, `Find me at ${Utils.prepend(`/doc/${collection[Id]}?sharing=true`)}`);
+                Transactions.AddTextEnrichment(collection, `Find me at ${ClientUtils.prepend(`/doc/${collection[Id]}?sharing=true`)}`);
                 return { albumId: id, mediaItems };
             }
         };
@@ -124,7 +124,7 @@ export namespace GooglePhotos {
             await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
             const response = await Query.ContentSearch(requested);
             const uploads = await Transactions.WriteMediaItemsToServer(response);
-            const children = uploads.map((upload: Transactions.UploadInformation) => Docs.Create.ImageDocument(Utils.fileUrl(upload.fileNames.clean) /*, {"data_contentSize":upload.contentSize}*/));
+            const children = uploads.map((upload: Transactions.UploadInformation) => Docs.Create.ImageDocument(ClientUtils.fileUrl(upload.fileNames.clean) /*, {"data_contentSize":upload.contentSize}*/));
             const options = { _width: 500, _height: 500 };
             return constructor(children, options);
         };
@@ -327,7 +327,7 @@ export namespace GooglePhotos {
         };
 
         const parseDescription = (document: Doc, descriptionKey: string) => {
-            let description: string = Utils.prepend(`/doc/${document[Id]}?sharing=true`);
+            let description: string = ClientUtils.prepend(`/doc/${document[Id]}?sharing=true`);
             const target = document[descriptionKey];
             if (typeof target === 'string') {
                 description = target;
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index fb51278ae..63563cb79 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -25,14 +25,18 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
  * @param inputText Text to process
  * @returns AI Output
  */
+let lastCall = '';
+let lastResp = '';
 const gptAPICall = async (inputText: string, callType: GPTCallType) => {
     if (callType === GPTCallType.SUMMARY) inputText += '.';
     const opts: GPTCallOpts = callTypeMap[callType];
+    if (lastCall === inputText) return lastResp;
     try {
         const configuration: ClientOptions = {
             apiKey: process.env.OPENAI_KEY,
             dangerouslyAllowBrowser: true,
         };
+        lastCall = inputText;
         const openai = new OpenAI(configuration);
         const response = await openai.completions.create({
             model: opts.model,
@@ -40,6 +44,7 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
             temperature: opts.temp,
             prompt: `${opts.prompt}${inputText}`,
         });
+        lastResp = response.choices[0].text;
         return response.choices[0].text;
     } catch (err) {
         console.log(err);
diff --git a/src/client/apis/youtube/YoutubeBox.scss b/src/client/apis/youtube/YoutubeBox.scss
deleted file mode 100644
index eabdbb1ac..000000000
--- a/src/client/apis/youtube/YoutubeBox.scss
+++ /dev/null
@@ -1,126 +0,0 @@
-.youtubeBox-cont {
-    ul {
-        list-style-type: none;
-        padding-inline-start: 10px;
-    }
-
-
-    li {
-        margin: 4px;
-        display: inline-flex;
-    }
-
-    li:hover {
-        cursor: pointer;
-        opacity: 0.8;
-    }
-
-    .search_wrapper {
-        width: 100%;
-        display: inline-flex;
-        height: 175px;
-
-        .video_duration {
-            // margin: 0;
-            // padding: 0;
-            border: 0;
-            background: transparent;
-            display: inline-block;
-            position: relative;
-            bottom: 25px;
-            left: 85%;
-            margin: 4px;
-            color: #FFFFFF;
-            background-color: rgba(0, 0, 0, 0.80);
-            padding: 2px 4px;
-            border-radius: 2px;
-            letter-spacing: .5px;
-            font-size: 1.2rem;
-            font-weight: 500;
-            line-height: 1.2rem;
-
-        }
-
-        .textual_info {
-            font-family: Arial, Helvetica, sans-serif;
-
-            .videoTitle {
-                margin-left: 4px;
-                // display: inline-block;
-                color: #0D0D0D;
-                -webkit-line-clamp: 2;
-                display: block;
-                max-height: 4.8rem;
-                overflow: hidden;
-                font-size: 1.8rem;
-                font-weight: 400;
-                line-height: 2.4rem;
-                -webkit-box-orient: vertical;
-                text-overflow: ellipsis;
-                white-space: normal;
-                display: -webkit-box;
-            }
-
-            .channelName {
-                color: #606060;
-                margin-left: 4px;
-                font-size: 1.3rem;
-                font-weight: 400;
-                line-height: 1.8rem;
-                text-transform: none;
-                margin-top: 0px;
-                display: inline-block;
-            }
-
-            .video_description {
-                margin-left: 4px;
-                // font-size: 12px;
-                color: #606060;
-                padding-top: 8px;
-                margin-bottom: 8px;
-                display: block;
-                line-height: 1.8rem;
-                max-height: 4.2rem;
-                overflow: hidden;
-                font-size: 1.3rem;
-                font-weight: 400;
-                text-transform: none;
-            }
-
-            .publish_time {
-                //display: inline-block;
-                margin-left: 8px;
-                padding: 0;
-                border: 0;
-                background: transparent;
-                color: #606060;
-                max-width: 100%;
-                line-height: 1.8rem;
-                max-height: 3.6rem;
-                overflow: hidden;
-                font-size: 1.3rem;
-                font-weight: 400;
-                text-transform: none;
-            }
-
-            .viewCount {
-
-                margin-left: 8px;
-                padding: 0;
-                border: 0;
-                background: transparent;
-                color: #606060;
-                max-width: 100%;
-                line-height: 1.8rem;
-                max-height: 3.6rem;
-                overflow: hidden;
-                font-size: 1.3rem;
-                font-weight: 400;
-                text-transform: none;
-            }
-
-
-
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
deleted file mode 100644
index d3a15cd84..000000000
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ /dev/null
@@ -1,369 +0,0 @@
-import { action, observable, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import { Doc, DocListCastAsync } from '../../../fields/Doc';
-import { InkTool } from '../../../fields/InkField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { Utils } from '../../../Utils';
-import { DocServer } from '../../DocServer';
-import { Docs } from '../../documents/Documents';
-import { DocumentView } from '../../views/nodes/DocumentView';
-import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
-import '../../views/nodes/WebBox.scss';
-import './YoutubeBox.scss';
-import * as React from 'react';
-import { SnappingManager } from '../../util/SnappingManager';
-
-interface VideoTemplate {
-    thumbnailUrl: string;
-    videoTitle: string;
-    videoId: string;
-    duration: string;
-    channelTitle: string;
-    viewCount: string;
-    publishDate: string;
-    videoDescription: string;
-}
-
-/**
- * This class models the youtube search document that can be dropped on to canvas.
- */
-@observer
-export class YoutubeBox extends React.Component<FieldViewProps> {
-    @observable YoutubeSearchElement: HTMLInputElement | undefined;
-    @observable searchResultsFound: boolean = false;
-    @observable searchResults: any[] = [];
-    @observable videoClicked: boolean = false;
-    @observable selectedVideoUrl: string = '';
-    @observable lisOfBackUp: JSX.Element[] = [];
-    @observable videoIds: string | undefined;
-    @observable videoDetails: any[] = [];
-    @observable curVideoTemplates: VideoTemplate[] = [];
-
-    public static LayoutString(fieldKey: string) {
-        return FieldView.LayoutString(YoutubeBox, fieldKey);
-    }
-
-    /**
-     * When component mounts, last search's results are laoded in based on the back up stored
-     * in the document of the props.
-     */
-    async componentDidMount() {
-        //DocServer.getYoutubeChannels();
-        const castedSearchBackUp = Cast(this.props.Document.cachedSearchResults, Doc);
-        const awaitedBackUp = await castedSearchBackUp;
-        const castedDetailBackUp = Cast(this.props.Document.cachedDetails, Doc);
-        const awaitedDetails = await castedDetailBackUp;
-
-        if (awaitedBackUp) {
-            const jsonList = await DocListCastAsync(awaitedBackUp.json);
-            const jsonDetailList = await DocListCastAsync(awaitedDetails!.json);
-
-            if (jsonList!.length !== 0) {
-                runInAction(() => (this.searchResultsFound = true));
-                let index = 0;
-                //getting the necessary information from backUps and building templates that will be used to map in render
-                for (const video of jsonList!) {
-                    const videoId = await Cast(video.id, Doc);
-                    const id = StrCast(videoId!.videoId);
-                    const snippet = await Cast(video.snippet, Doc);
-                    const videoTitle = this.filterYoutubeTitleResult(StrCast(snippet!.title));
-                    const thumbnail = await Cast(snippet!.thumbnails, Doc);
-                    const thumbnailMedium = await Cast(thumbnail!.medium, Doc);
-                    const thumbnailUrl = StrCast(thumbnailMedium!.url);
-                    const videoDescription = StrCast(snippet!.description);
-                    const pusblishDate = this.roundPublishTime(StrCast(snippet!.publishedAt))!;
-                    const channelTitle = StrCast(snippet!.channelTitle);
-                    let duration: string = '';
-                    let viewCount: string = '';
-                    if (jsonDetailList!.length !== 0) {
-                        const contentDetails = await Cast(jsonDetailList![index].contentDetails, Doc);
-                        const statistics = await Cast(jsonDetailList![index].statistics, Doc);
-                        duration = this.convertIsoTimeToDuration(StrCast(contentDetails!.duration));
-                        viewCount = this.abbreviateViewCount(parseInt(StrCast(statistics!.viewCount)))!;
-                    }
-                    index = index + 1;
-                    const newTemplate: VideoTemplate = {
-                        videoId: id,
-                        videoTitle: videoTitle,
-                        thumbnailUrl: thumbnailUrl,
-                        publishDate: pusblishDate,
-                        channelTitle: channelTitle,
-                        videoDescription: videoDescription,
-                        duration: duration,
-                        viewCount: viewCount,
-                    };
-                    runInAction(() => this.curVideoTemplates.push(newTemplate));
-                }
-            }
-        }
-    }
-
-    _ignore = 0;
-    onPreWheel = (e: React.WheelEvent) => {
-        this._ignore = e.timeStamp;
-    };
-    onPrePointer = (e: React.PointerEvent) => {
-        this._ignore = e.timeStamp;
-    };
-    onPostPointer = (e: React.PointerEvent) => {
-        if (this._ignore !== e.timeStamp) {
-            e.stopPropagation();
-        }
-    };
-    onPostWheel = (e: React.WheelEvent) => {
-        if (this._ignore !== e.timeStamp) {
-            e.stopPropagation();
-        }
-    };
-
-    /**
-     * Function that submits the title entered by user on enter press.
-     */
-    onEnterKeyDown = (e: React.KeyboardEvent) => {
-        if (e.keyCode === 13) {
-            const submittedTitle = this.YoutubeSearchElement!.value;
-            this.YoutubeSearchElement!.value = '';
-            this.YoutubeSearchElement!.blur();
-            DocServer.getYoutubeVideos(submittedTitle, this.processesVideoResults);
-        }
-    };
-
-    /**
-     * The callback that is passed in to server, which functions as a way to
-     * get videos that is returned by search. It also makes a call to server
-     * to get details for the videos found.
-     */
-    @action
-    processesVideoResults = (videos: any[]) => {
-        this.searchResults = videos;
-        if (this.searchResults.length > 0) {
-            this.searchResultsFound = true;
-            this.videoIds = '';
-            videos.forEach(video => {
-                if (this.videoIds === '') {
-                    this.videoIds = video.id.videoId;
-                } else {
-                    this.videoIds = this.videoIds! + ', ' + video.id.videoId;
-                }
-            });
-            //Asking for details that include duration and viewCount from server for videoIds
-            DocServer.getYoutubeVideoDetails(this.videoIds, this.processVideoDetails);
-            this.backUpSearchResults(videos);
-            if (this.videoClicked) {
-                this.videoClicked = false;
-            }
-        }
-    };
-
-    /**
-     * The callback that is given to server to process and receive returned details about the videos.
-     */
-    @action
-    processVideoDetails = (videoDetails: any[]) => {
-        this.videoDetails = videoDetails;
-        this.props.Document.cachedDetails = Doc.Get.FromJson({ data: videoDetails, title: 'detailBackUp' });
-    };
-
-    /**
-     * The function that stores the search results in the props document.
-     */
-    backUpSearchResults = (videos: any[]) => {
-        this.props.Document.cachedSearchResults = Doc.Get.FromJson({ data: videos, title: 'videosBackUp' });
-    };
-
-    /**
-     * The function that filters out escaped characters returned by the api
-     * in the title of the videos.
-     */
-    filterYoutubeTitleResult = (resultTitle: string) => {
-        let processedTitle: string = resultTitle.replace(/&amp;/g, '&'); //.ReplaceAll("&amp;", "&");
-        processedTitle = processedTitle.replace(/"&#39;/g, "'");
-        processedTitle = processedTitle.replace(/&quot;/g, '"');
-        return processedTitle;
-    };
-
-    /**
-     * The function that converts ISO date, which is passed in, to normal date and finds the
-     * difference between today's date and that date, in terms of "ago" to imitate youtube.
-     */
-    roundPublishTime = (publishTime: string) => {
-        const date = new Date(publishTime).getTime();
-        const curDate = new Date().getTime();
-        const timeDif = curDate - date;
-        const totalSeconds = timeDif / 1000;
-        const totalMin = totalSeconds / 60;
-        const totalHours = totalMin / 60;
-        const totalDays = totalHours / 24;
-        const totalMonths = totalDays / 30.417;
-        const totalYears = totalMonths / 12;
-
-        const truncYears = Math.trunc(totalYears);
-        const truncMonths = Math.trunc(totalMonths);
-        const truncDays = Math.trunc(totalDays);
-        const truncHours = Math.trunc(totalHours);
-        const truncMin = Math.trunc(totalMin);
-        const truncSec = Math.trunc(totalSeconds);
-
-        let pluralCase = '';
-
-        if (truncYears !== 0) {
-            truncYears > 1 ? (pluralCase = 's') : (pluralCase = '');
-            return truncYears + ' year' + pluralCase + ' ago';
-        } else if (truncMonths !== 0) {
-            truncMonths > 1 ? (pluralCase = 's') : (pluralCase = '');
-            return truncMonths + ' month' + pluralCase + ' ago';
-        } else if (truncDays !== 0) {
-            truncDays > 1 ? (pluralCase = 's') : (pluralCase = '');
-            return truncDays + ' day' + pluralCase + ' ago';
-        } else if (truncHours !== 0) {
-            truncHours > 1 ? (pluralCase = 's') : (pluralCase = '');
-            return truncHours + ' hour' + pluralCase + ' ago';
-        } else if (truncMin !== 0) {
-            truncMin > 1 ? (pluralCase = 's') : (pluralCase = '');
-            return truncMin + ' minute' + pluralCase + ' ago';
-        } else if (truncSec !== 0) {
-            truncSec > 1 ? (pluralCase = 's') : (pluralCase = '');
-            return truncSec + ' second' + pluralCase + ' ago';
-        }
-    };
-
-    /**
-     * The function that converts the passed in ISO time to normal duration time.
-     */
-    convertIsoTimeToDuration = (isoDur: string) => {
-        const convertedTime = isoDur.replace(/D|H|M/g, ':').replace(/P|T|S/g, '').split(':');
-
-        if (1 === convertedTime.length) {
-            2 !== convertedTime[0].length && (convertedTime[0] = '0' + convertedTime[0]), (convertedTime[0] = '0:' + convertedTime[0]);
-        } else {
-            for (var r = 1, l = convertedTime.length - 1; l >= r; r++) {
-                2 !== convertedTime[r].length && (convertedTime[r] = '0' + convertedTime[r]);
-            }
-        }
-
-        return convertedTime.join(':');
-    };
-
-    /**
-     * The function that rounds the viewCount to the nearest
-     * thousand, million or billion, given a viewCount number.
-     */
-    abbreviateViewCount = (viewCount: number) => {
-        if (viewCount < 1000) {
-            return viewCount.toString();
-        } else if (viewCount >= 1000 && viewCount < 1000000) {
-            return Math.trunc(viewCount / 1000) + 'K';
-        } else if (viewCount >= 1000000 && viewCount < 1000000000) {
-            return Math.trunc(viewCount / 1000000) + 'M';
-        } else if (viewCount >= 1000000000) {
-            return Math.trunc(viewCount / 1000000000) + 'B';
-        }
-    };
-
-    /**
-     * The function that is called to decide on what'll be rendered by the component.
-     * It renders search Results if found. If user didn't do a new search, it renders from the videoTemplates
-     * generated by the backUps. If none present, renders nothing.
-     */
-    renderSearchResultsOrVideo = () => {
-        if (this.searchResultsFound) {
-            if (this.searchResults.length !== 0) {
-                return (
-                    <ul>
-                        {this.searchResults.map((video, index) => {
-                            const filteredTitle = this.filterYoutubeTitleResult(video.snippet.title);
-                            const channelTitle = video.snippet.channelTitle;
-                            const videoDescription = video.snippet.description;
-                            const pusblishDate = this.roundPublishTime(video.snippet.publishedAt);
-                            let duration;
-                            let viewCount;
-                            if (this.videoDetails.length !== 0) {
-                                duration = this.convertIsoTimeToDuration(this.videoDetails[index].contentDetails.duration);
-                                viewCount = this.abbreviateViewCount(this.videoDetails[index].statistics.viewCount);
-                            }
-
-                            return (
-                                <li onClick={() => this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}>
-                                    <div className="search_wrapper">
-                                        <div style={{ backgroundColor: 'yellow' }}>
-                                            <img src={video.snippet.thumbnails.medium.url} />
-                                            <span className="video_duration">{duration}</span>
-                                        </div>
-                                        <div className="textual_info">
-                                            <span className="videoTitle">{filteredTitle}</span>
-                                            <span className="channelName">{channelTitle}</span>
-                                            <span className="viewCount">{viewCount}</span>
-                                            <span className="publish_time">{pusblishDate}</span>
-                                            <p className="video_description">{videoDescription}</p>
-                                        </div>
-                                    </div>
-                                </li>
-                            );
-                        })}
-                    </ul>
-                );
-            } else if (this.curVideoTemplates.length !== 0) {
-                return (
-                    <ul>
-                        {this.curVideoTemplates.map((video: VideoTemplate) => {
-                            return (
-                                <li onClick={() => this.embedVideoOnClick(video.videoId, video.videoTitle)} key={Utils.GenerateGuid()}>
-                                    <div className="search_wrapper">
-                                        <div style={{ backgroundColor: 'yellow' }}>
-                                            <img src={video.thumbnailUrl} />
-                                            <span className="video_duration">{video.duration}</span>
-                                        </div>
-                                        <div className="textual_info">
-                                            <span className="videoTitle">{video.videoTitle}</span>
-                                            <span className="channelName">{video.channelTitle}</span>
-                                            <span className="viewCount">{video.viewCount}</span>
-                                            <span className="publish_time">{video.publishDate}</span>
-                                            <p className="video_description">{video.videoDescription}</p>
-                                        </div>
-                                    </div>
-                                </li>
-                            );
-                        })}
-                    </ul>
-                );
-            }
-        } else {
-            return null;
-        }
-    };
-
-    /**
-     * Given a videoId and title, creates a new youtube embedded url, and uses that
-     * to create a new video document.
-     */
-    @action
-    embedVideoOnClick = (videoId: string, filteredTitle: string) => {
-        const embeddedUrl = 'https://www.youtube.com/embed/' + videoId;
-        this.selectedVideoUrl = embeddedUrl;
-        const addFunction = this.props.addDocument!;
-        const newVideoX = NumCast(this.props.Document.x);
-        const newVideoY = NumCast(this.props.Document.y) + NumCast(this.props.Document.height);
-
-        addFunction(Docs.Create.VideoDocument(embeddedUrl, { title: filteredTitle, _width: 400, _height: 315, x: newVideoX, y: newVideoY }));
-        this.videoClicked = true;
-    };
-
-    render() {
-        const content = (
-            <div className="youtubeBox-cont" style={{ width: '100%', height: '100%', position: 'absolute' }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
-                <input type="text" placeholder="Search for a video" onKeyDown={this.onEnterKeyDown} style={{ height: 40, width: '100%', border: '1px solid black', padding: 5, textAlign: 'center' }} ref={e => (this.YoutubeSearchElement = e!)} />
-                {this.renderSearchResultsOrVideo()}
-            </div>
-        );
-
-        const frozen = !this.props.isSelected() || SnappingManager.IsResizing;
-
-        const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !SnappingManager.IsResizing ? '-interactive' : '');
-        return (
-            <>
-                <div className={classname}>{content}</div>
-                {!frozen ? null : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
-            </>
-        );
-    }
-}
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 408903324..4ad03aab4 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -1,18 +1,16 @@
-import * as request from 'request-promise';
-import { Doc, Field } from '../../fields/Doc';
-import { Cast } from '../../fields/Types';
-import { Utils } from '../../Utils';
+import * as rp from 'request-promise';
+import { Doc, FieldType } from '../../fields/Doc';
 import { InkData } from '../../fields/InkField';
-import { UndoManager } from '../util/UndoManager';
-import requestPromise = require('request-promise');
 import { List } from '../../fields/List';
+import { Cast } from '../../fields/Types';
+import { UndoManager } from '../util/UndoManager';
 
 type APIManager<D> = { converter: BodyConverter<D>; requester: RequestExecutor };
 type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>;
 type AnalysisApplier<D> = (target: Doc, relevantKeys: string[], data: D, ...args: any) => any;
 type BodyConverter<D> = (data: D) => string;
-type Converter = (results: any) => Field;
-type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field; external_recommendations: any; kp_string: string[] }>;
+type Converter = (results: any) => FieldType;
+type TextConverter = (results: any, data: string) => Promise<{ keyterms: FieldType; external_recommendations: any; kp_string: string[] }>;
 type BingConverter = (results: any) => Promise<{ title_vals: string[]; url_vals: string[] }>;
 
 export type Tag = { name: string; confidence: number };
@@ -95,7 +93,7 @@ export namespace CognitiveServices {
                     },
                 };
 
-                return request.post(options);
+                return rp.post(options);
             },
         };
 
@@ -344,10 +342,10 @@ export namespace CognitiveServices {
         export namespace Appliers {
             export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false) {
                 console.log('vectorizing...');
-                //keyterms = ["father", "king"];
+                // keyterms = ["father", "king"];
 
-                const args = { method: 'POST', uri: Utils.prepend('/recommender'), body: { keyphrases: keyterms }, json: true };
-                await requestPromise.post(args).then(async wordvecs => {
+                const args = { method: 'POST', uri: ClientUtils.prepend('/recommender'), body: { keyphrases: keyterms }, json: true };
+                await rp.post(args).then(async wordvecs => {
                     if (wordvecs) {
                         const indices = Object.keys(wordvecs);
                         console.log('successful vectorization!');
@@ -355,7 +353,7 @@ export namespace CognitiveServices {
                         indices.forEach((ind: any) => {
                             vectorValues.push(wordvecs[ind]);
                         });
-                        //ClientRecommender.Instance.processVector(vectorValues, dataDoc, mainDoc);
+                        // ClientRecommender.Instance.processVector(vectorValues, dataDoc, mainDoc);
                     } // adds document to internal doc set
                     else {
                         console.log('unsuccessful :( word(s) not in vocabulary');
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 1123bcac9..4e3496608 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -25,14 +25,13 @@ export enum DocumentType {
     MAP = 'map',
     DATAVIZ = 'dataviz',
     LOADING = 'loading',
-    SIMULATION = 'simulation', //physics simulation
+    SIMULATION = 'simulation', // physics simulation
 
     // special purpose wrappers that either take no data or are compositions of lower level types
     LINK = 'link',
     IMPORT = 'import',
     PRES = 'presentation',
     PRESELEMENT = 'preselement',
-    YOUTUBE = 'youtube',
     COMPARISON = 'comparison',
     GROUP = 'group',
     PUSHPIN = 'pushpin',
@@ -56,11 +55,10 @@ export enum CollectionViewType {
     Carousel = 'carousel',
     Carousel3D = '3D Carousel',
     Linear = 'linear',
-    //Staff = "staff",
     Map = 'map',
     Grid = 'grid',
     Pile = 'pileup',
     StackedTimeline = 'stacked timeline',
     NoteTaking = 'notetaking',
-    Calendar = 'calendar'
+    Calendar = 'calendar',
 }
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b160379df..d41a96db2 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,28 +1,35 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { saveAs } from 'file-saver';
+import * as JSZip from 'jszip';
 import { action, reaction, runInAction } from 'mobx';
 import { basename } from 'path';
-import { OmitKeys, Utils } from '../../Utils';
+import { ClientUtils, OmitKeys } from '../../ClientUtils';
+import * as JSZipUtils from '../../JSZipUtils';
+import { decycle } from '../../decycler/decycler';
 import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
 import { DocData, Initializing } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { HtmlField } from '../../fields/HtmlField';
-import { InkField, PointData } from '../../fields/InkField';
-import { List } from '../../fields/List';
+import { InkDataFieldName, InkField } from '../../fields/InkField';
+import { List, ListFieldName } from '../../fields/List';
+import { ProxyField } from '../../fields/Proxy';
 import { RichTextField } from '../../fields/RichTextField';
 import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
 import { ComputedField, ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types';
-import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField';
+import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from '../../fields/URLField';
 import { SharingPermissions, inheritParentAcls } from '../../fields/util';
+import { PointData } from '../../pen-gestures/GestureTypes';
 import { Upload } from '../../server/SharedMediaTypes';
 import { DocServer } from '../DocServer';
 import { Networking } from '../Network';
-import { YoutubeBox } from '../apis/youtube/YoutubeBox';
-import { DragManager, dropActionType } from '../util/DragManager';
+import { DragManager } from '../util/DragManager';
+import { dropActionType } from '../util/DropActionTypes';
 import { FollowLinkScript } from '../util/LinkFollower';
 import { LinkManager } from '../util/LinkManager';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { SerializationHelper } from '../util/SerializationHelper';
 import { UndoManager, undoable } from '../util/UndoManager';
 import { ContextMenu } from '../views/ContextMenu';
 import { ContextMenuProps } from '../views/ContextMenuItem';
@@ -30,7 +37,7 @@ import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveIn
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { CollectionView } from '../views/collections/CollectionView';
 import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
-import { AudioBox, media_state } from '../views/nodes/AudioBox';
+import { AudioBox, mediaState } from '../views/nodes/AudioBox';
 import { ComparisonBox } from '../views/nodes/ComparisonBox';
 import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
 import { OpenWhere } from '../views/nodes/DocumentView';
@@ -60,6 +67,7 @@ import { PresBox } from '../views/nodes/trails/PresBox';
 import { PresElementBox } from '../views/nodes/trails/PresElementBox';
 import { SearchBox } from '../views/search/SearchBox';
 import { CollectionViewType, DocumentType } from './DocumentTypes';
+
 const { DFLT_IMAGE_NATIVE_DIM } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
@@ -83,7 +91,7 @@ export class FInfo {
     description: string = '';
     readOnly: boolean = false;
     fieldType?: FInfoFieldType;
-    values?: Field[];
+    values?: FieldType[];
 
     filterable?: boolean = true; // can be used as a Filter in FilterPanel
     // format?: string; // format to display values (e.g, decimal places, $, etc)
@@ -252,8 +260,8 @@ export class DocumentOptions {
     opacity?: NUMt = new NumInfo('document opacity', false);
     viewTransitionTime?: NUMt = new NumInfo('transition duration for view parameters', false);
     dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc', false);
-    _undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
-    undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
+    _undoIgnoreFields?: List<string>; // 'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
+    undoIgnoreFields?: List<string>; // 'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)'
     _header_height?: NUMt = new NumInfo('height of document header used for displaying title', false);
     _header_fontSize?: NUMt = new NumInfo('font size of header of custom notes', false);
     _header_pointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume');
@@ -321,7 +329,7 @@ export class DocumentOptions {
     _label_maxFontSize?: NUMt = new NumInfo('maximum font size for labelBoxes', false);
     stroke_width?: NUMt = new NumInfo('width of an ink stroke', false);
     stroke_showLabel?: BOOLt = new BoolInfo('show label inside of stroke');
-    mediaState?: STRt = new StrInfo(`status of audio/video media document: ${media_state.PendingRecording}, ${media_state.Recording}, ${media_state.Paused}, ${media_state.Playing}`, false);
+    mediaState?: STRt = new StrInfo(`status of audio/video media document: ${mediaState.PendingRecording}, ${mediaState.Recording}, ${mediaState.Paused}, ${mediaState.Playing}`, false);
     recording?: BOOLt = new BoolInfo('whether WebCam is recording or not');
     slides?: DOCt = new DocInfo('presentation slide associated with video recording (bcz: should be renamed!!)');
     autoPlayAnchors?: BOOLt = new BoolInfo('whether to play audio/video when an anchor is clicked in a stackedTimeline.');
@@ -401,7 +409,7 @@ export class DocumentOptions {
     _freeform_noZoom?: BOOLt = new BoolInfo('disables zooming (used by Pile docs)');
     _freeform_fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content');
 
-    //BUTTONS
+    // BUTTONS
     buttonText?: string;
     btnType?: string;
     btnList?: List<string>;
@@ -413,7 +421,7 @@ export class DocumentOptions {
     switchToggle?: boolean;
     badgeValue?: ScriptField;
 
-    //LINEAR VIEW
+    // LINEAR VIEW
     linearView_IsOpen?: BOOLt = new BoolInfo('is linear view open');
     linearView_Expandable?: BOOLt = new BoolInfo('can linear view be expanded');
     linearView_Dropdown?: BOOLt = new BoolInfo('can linear view be opened as a dropdown');
@@ -431,7 +439,7 @@ export class DocumentOptions {
     link_anchor_1_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)');
     link_anchor_2_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)');
     link_relationshipList?: List<string>; // for storing different link relationships (when set by user in the link editor)
-    link_relationshipSizes?: List<number>; //stores number of links contained in each relationship
+    link_relationshipSizes?: List<number>; // stores number of links contained in each relationship
     link_colorList?: List<string>; // colors of links corresponding to specific link relationships
     followLinkZoom?: BOOLt = new BoolInfo('whether to zoom to the target of a link');
     followLinkToggle?: BOOLt = new BoolInfo('whether target of link should be toggled on and off when following a link to it');
@@ -463,7 +471,7 @@ export class DocumentOptions {
     dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', false, true);
     dragFactory?: DOCt = new DocInfo('document to create when dragging with a suitable onDragStart script', false);
     clickFactory?: DOCt = new DocInfo('document to create when clicking on a button with a suitable onClick script', false);
-    onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
+    onDragStart?: ScriptField; // script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
     target?: Doc; // available for use in scripts.  used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script)
     tags?: LISTt = new ListInfo('hashtags added to document, typically using a text view', true);
     treeView_HideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view');
@@ -502,8 +510,6 @@ export class DocumentOptions {
 
 export const DocOptions = new DocumentOptions();
 export namespace Docs {
-    export let newAccount: boolean = false;
-
     export namespace Prototypes {
         type LayoutSource = { LayoutString: (key: string) => string };
         type PrototypeTemplate = {
@@ -646,12 +652,6 @@ export namespace Docs {
                     options: { systemIcon: 'BsFileEarmarkCodeFill' },
                 },
             ],
-            [
-                DocumentType.YOUTUBE,
-                {
-                    layout: { view: YoutubeBox, dataField: defaultDataKey },
-                },
-            ],
             [
                 DocumentType.LABEL,
                 {
@@ -670,7 +670,7 @@ export namespace Docs {
                         layout_nativeDimEditable: true,
                         layout_hideDecorationTitle: true,
                         systemIcon: 'BsCalculatorFill',
-                    }, ///systemIcon: 'BsSuperscript' + BsSubscript
+                    }, // systemIcon: 'BsSuperscript' + BsSubscript
                 },
             ],
             [
@@ -850,7 +850,7 @@ export namespace Docs {
             // fetch the actual prototype documents from the server
             const actualProtos = await DocServer.GetRefFields(prototypeIds);
             // update this object to include any default values: DocumentOptions for all prototypes
-            prototypeIds.map(id => {
+            prototypeIds.forEach(id => {
                 const existing = actualProtos[id] as Doc;
                 const type = id.replace(suffix, '') as DocumentType;
                 // get or create prototype of the specified type...
@@ -908,7 +908,7 @@ export namespace Docs {
             if (!template) {
                 return undefined;
             }
-            const layout = template.layout;
+            const { layout } = template;
             // create title
             const upper = suffix.toUpperCase();
             const title = prototypeId.toUpperCase().replace(upper, `_${upper}`);
@@ -928,7 +928,7 @@ export namespace Docs {
             };
             Object.entries(options)
                 .filter(pair => typeof pair[1] === 'string' && pair[1].startsWith('@'))
-                .map(pair => {
+                .forEach(pair => {
                     if (!existing || ScriptCast(existing[pair[0]])?.script.originalScript !== pair[1].substring(1)) {
                         (options as any)[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1));
                     }
@@ -960,7 +960,9 @@ export namespace Docs {
          * only when creating a DockDocument from the current user's already existing
          * main document.
          */
-        function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDoc?: Doc, noView?: boolean) {
+        // eslint-disable-next-line default-param-last
+        function InstanceFromProto(proto: Doc, data: FieldType | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDocIn?: Doc, noView?: boolean) {
+            const placeholderDoc = placeholderDocIn;
             const viewKeys = ['x', 'y', 'isSystem']; // keys that should be addded to the view document even though they don't begin with an "_"
             const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, '^_');
 
@@ -968,7 +970,7 @@ export namespace Docs {
             dataProps['acl-Guest'] = options['acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View);
             dataProps.isSystem = viewProps.isSystem;
             dataProps.isDataDoc = true;
-            dataProps.author = Doc.CurrentUserEmail;
+            dataProps.author = ClientUtils.CurrentUserEmail;
             dataProps.author_date = new DateField();
             if (fieldKey) {
                 dataProps[`${fieldKey}_modificationDate`] = new DateField();
@@ -988,7 +990,7 @@ export namespace Docs {
             }
 
             if (!noView) {
-                const viewFirstProps: { [id: string]: any } = { author: Doc.CurrentUserEmail };
+                const viewFirstProps: { [id: string]: any } = { author: ClientUtils.CurrentUserEmail };
                 viewFirstProps['acl-Guest'] = options['_acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View);
                 let viewDoc: Doc;
                 // determines whether viewDoc should be created using placeholder Doc or default
@@ -998,7 +1000,9 @@ export namespace Docs {
                     viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true);
                     Array.from(Object.keys(placeholderDoc))
                         .filter(key => key.startsWith('acl'))
-                        .forEach(key => (dataDoc[key] = viewDoc[key] = placeholderDoc[key]));
+                        .forEach(key => {
+                            dataDoc[key] = viewDoc[key] = placeholderDoc[key];
+                        });
                 } else {
                     viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true);
                 }
@@ -1017,6 +1021,7 @@ export namespace Docs {
             return dataDoc;
         }
 
+        // eslint-disable-next-line default-param-last
         export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) {
             const imgField = url instanceof ImageField ? url : url ? new ImageField(url) : undefined;
             return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField?.url.href ?? '-no image-'), ...options }, undefined, undefined, undefined, overwriteDoc);
@@ -1035,18 +1040,16 @@ export namespace Docs {
          * @param fieldKey the field that the compiled script is written into.
          * @returns the Scripting Doc
          */
+        // eslint-disable-next-line default-param-last
         export function ScriptingDocument(script: Opt<ScriptField> | null, options: DocumentOptions = {}, fieldKey?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script : undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined });
+            return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script || undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined });
         }
 
+        // eslint-disable-next-line default-param-last
         export function VideoDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
             return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options, undefined, undefined, undefined, overwriteDoc);
         }
 
-        export function YoutubeDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
-            return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(url), options, undefined, undefined, undefined, overwriteDoc);
-        }
-
         export function WebCamDocument(url: string, options: DocumentOptions = {}) {
             return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), '', options);
         }
@@ -1059,6 +1062,7 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), undefined, options);
         }
 
+        // eslint-disable-next-line default-param-last
         export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
             return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), options, undefined, undefined, undefined, overwriteDoc);
         }
@@ -1072,7 +1076,7 @@ export namespace Docs {
         }
 
         export function LoadingDocument(file: File | string, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file == 'string' ? file : file.name, ...options }, undefined, '');
+            return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file === 'string' ? file : file.name, ...options }, undefined, '');
         }
 
         export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') {
@@ -1101,6 +1105,7 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
         }
 
+        // eslint-disable-next-line default-param-last
         export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) {
             const linkDoc = InstanceFromProto(
                 Prototypes.get(DocumentType.LINK),
@@ -1124,7 +1129,7 @@ export namespace Docs {
             options: DocumentOptions = {},
             strokeWidth = ActiveInkWidth(),
             color = ActiveInkColor(),
-            stroke_bezier = ActiveInkBezierApprox(),
+            strokeBezier = ActiveInkBezierApprox(),
             fillColor = ActiveFillColor(),
             arrowStart = ActiveArrowStart(),
             arrowEnd = ActiveArrowEnd(),
@@ -1138,7 +1143,7 @@ export namespace Docs {
             I.fillColor = fillColor;
             I.stroke = new InkField(points);
             I.stroke_width = strokeWidth;
-            I.stroke_bezier = stroke_bezier;
+            I.stroke_bezier = strokeBezier;
             I.stroke_startMarker = arrowStart;
             I.stroke_endMarker = arrowEnd;
             I.stroke_dash = dash;
@@ -1148,12 +1153,13 @@ export namespace Docs {
             I.defaultDoubleClick = 'ignore';
             I.author_date = new DateField();
             I['acl-Guest'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View;
-            //I['acl-Override'] = SharingPermissions.Unset;
+            // I['acl-Override'] = SharingPermissions.Unset;
             I[Initializing] = false;
 
             return ink;
         }
 
+        // eslint-disable-next-line default-param-last
         export function PdfDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) {
             const width = options._width || undefined;
             const height = options._height || undefined;
@@ -1169,7 +1175,7 @@ export namespace Docs {
             const nwid = options._nativeWidth || undefined;
             const nhght = options._nativeHeight || undefined;
             if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width);
-            return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url ? url : 'https://www.wikipedia.org/'), options);
+            return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url || 'https://www.wikipedia.org/'), options);
         }
 
         export function HtmlDocument(html: string, options: DocumentOptions = {}) {
@@ -1324,10 +1330,10 @@ export namespace Docs {
             const doc = DockDocument(
                 configs.map(c => c.doc),
                 JSON.stringify(layoutConfig),
-                Doc.CurrentUserEmail === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options },
+                ClientUtils.CurrentUserEmail === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options },
                 id
             );
-            configs.map(c => {
+            configs.forEach(c => {
                 Doc.SetContainer(c.doc, doc);
                 inheritParentAcls(doc, c.doc, false);
             });
@@ -1345,8 +1351,9 @@ export namespace Docs {
 }
 
 export namespace DocUtils {
-    function matchFieldValue(doc: Doc, key: string, value: any): boolean {
-        const hasFunctionFilter = Utils.HasFunctionFilter(value);
+    function matchFieldValue(doc: Doc, key: string, valueIn: any): boolean {
+        let value = valueIn;
+        const hasFunctionFilter = ClientUtils.HasFunctionFilter(value);
         if (hasFunctionFilter) {
             return hasFunctionFilter(StrCast(doc[key]));
         }
@@ -1365,7 +1372,7 @@ export namespace DocUtils {
                                            matchLink(value,DocCast(link.link_anchor_2)) ));
         }
         if (typeof value === 'string') {
-            value = value.replace(`,${Utils.noRecursionHack}`, '');
+            value = value.replace(`,${ClientUtils.noRecursionHack}`, '');
         }
         const fieldVal = doc[key];
         // prettier-ignore
@@ -1377,7 +1384,7 @@ export namespace DocUtils {
         if (vals.length) {
             return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
         }
-        return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+        return Field.toString(fieldVal as FieldType).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
     }
     /**
      * @param docs
@@ -1410,7 +1417,9 @@ export namespace DocUtils {
                   if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) {
                       return false;
                   }
-                  for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragDocsFilter.split(Doc.FilterSep)[0])) {
+                  const facetKeys = Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== ClientUtils.noDragDocsFilter.split(Doc.FilterSep)[0]);
+                  // eslint-disable-next-line no-restricted-syntax
+                  for (const facetKey of facetKeys) {
                       const facet = filterFacets[facetKey];
 
                       // facets that match some value in the field of the document (e.g. some text field)
@@ -1431,8 +1440,8 @@ export namespace DocUtils {
                       if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
                       const failsNotEqualFacets = !xs.length ? false : xs.some(value => matchFieldValue(d, facetKey, value));
                       const satisfiesCheckFacets = !checks.length ? true : checks.some(value => matchFieldValue(d, facetKey, value));
-                      const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length));
-                      const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined);
+                      const satisfiesExistsFacets = !exists.length ? true : facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length;
+                      const satisfiesUnsetsFacets = !unsets.length ? true : d[facetKey] === undefined;
                       const satisfiesMatchFacets = !matches.length
                           ? true
                           : matches.some(value => {
@@ -1441,20 +1450,18 @@ export namespace DocUtils {
                                     const allKeys = Array.from(Object.keys(d));
                                     allKeys.push(...Object.keys(Doc.GetProto(d)));
                                     const keys = allKeys.filter(key => key.includes(facetKey.substring(1)));
-                                    return keys.some(key => Field.toString(d[key] as Field).includes(value));
+                                    return keys.some(key => Field.toString(d[key] as FieldType).includes(value));
                                 }
-                                return Field.toString(d[facetKey] as Field).includes(value);
+                                return Field.toString(d[facetKey] as FieldType).includes(value);
                             });
                       // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
                       if (parentCollection?.childFilters_boolean === 'OR') {
                           if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
                       }
                       // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
-                      else {
-                          if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
-                      }
+                      else if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
                   }
-                  return parentCollection?.childFilters_boolean === 'OR' ? false : true;
+                  return parentCollection?.childFilters_boolean !== 'OR';
               })
             : childDocs;
         const rangeFilteredDocs = filteredDocs.filter(d => {
@@ -1464,7 +1471,7 @@ export namespace DocUtils {
                 const max = Number(childFiltersByRanges[i + 2]);
                 const val = typeof d[key] === 'string' ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], 'number', null);
                 if (val === undefined) {
-                    //console.log("Should 'undefined' pass range filter or not?")
+                    // console.log("Should 'undefined' pass range filter or not?")
                 } else if (val < min || val > max) return false;
             }
             return true;
@@ -1472,10 +1479,10 @@ export namespace DocUtils {
         return rangeFilteredDocs;
     }
 
-    export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = [];
+    export const ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = [];
 
     export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) {
-        broadcastEvent && runInAction(() => (Doc.RecordingEvent = Doc.RecordingEvent + 1));
+        broadcastEvent && runInAction(() => { Doc.RecordingEvent += 1; }); // prettier-ignore
         return DocUtils.ActiveRecordings.map(audio => {
             const sourceDoc = getSourceDoc();
             return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { link_displayLine: false, link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' });
@@ -1510,7 +1517,9 @@ export namespace DocUtils {
                 }
 
                 setTimeout(
-                    action(() => (TaskCompletionBox.taskCompleted = false)),
+                    action(() => {
+                        TaskCompletionBox.taskCompleted = false;
+                    }),
                     2500
                 );
             }
@@ -1550,7 +1559,7 @@ export namespace DocUtils {
 
     export function AssignScripts(doc: Doc, scripts?: { [key: string]: string | undefined }, funcs?: { [key: string]: string }) {
         scripts &&
-            Object.keys(scripts).map(key => {
+            Object.keys(scripts).forEach(key => {
                 const script = scripts[key];
                 if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) {
                     (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, {
@@ -1573,7 +1582,7 @@ export namespace DocUtils {
         funcs &&
             Object.keys(funcs)
                 .filter(key => !key.endsWith('-setter'))
-                .map(key => {
+                .forEach(key => {
                     const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
                     if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) {
                         const setFunc = Cast(funcs[key + '-setter'], 'string', null);
@@ -1602,6 +1611,7 @@ export namespace DocUtils {
         return doc;
     }
     export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) {
+        // eslint-disable-next-line no-return-assign
         return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs);
     }
 
@@ -1649,7 +1659,7 @@ export namespace DocUtils {
      * @returns
      */
     export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise<Opt<Doc>> {
-        let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined = undefined;
+        let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined;
         if (type.indexOf('image') !== -1) {
             ctor = Docs.Create.ImageDocument;
             if (!options._width) options._width = 300;
@@ -1672,7 +1682,7 @@ export namespace DocUtils {
             if (!options._width) options._width = 400;
             if (!options._height) options._height = ((options._width as number) * 1200) / 927;
         }
-        //TODO:al+glr
+        // TODO:al+glr
         // if (type.indexOf("map") !== -1) {
         //     ctor = Docs.Create.MapDocument;
         //     if (!options._width) options._width = 800;
@@ -1695,6 +1705,7 @@ export namespace DocUtils {
                 });
             }
             ctor = Docs.Create.WebDocument;
+            // eslint-disable-next-line no-param-reassign
             options = { ...options, _width: 400, _height: 512, title: path };
         }
 
@@ -1706,12 +1717,12 @@ export namespace DocUtils {
             .filter(btnDoc => !btnDoc.hidden)
             .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
             .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title)
-            .map((dragDoc, i) => ({
+            .map(dragDoc => ({
                 description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
-                event: undoable((args: { x: number; y: number }) => {
+                event: undoable(() => {
                     const newDoc = DocUtils.copyDragFactory(dragDoc);
                     if (newDoc) {
-                        newDoc.author = Doc.CurrentUserEmail;
+                        newDoc.author = ClientUtils.CurrentUserEmail;
                         newDoc.x = x;
                         newDoc.y = y;
                         EquationBox.SelectOnLoad = newDoc[Id];
@@ -1732,9 +1743,9 @@ export namespace DocUtils {
         !simpleMenu &&
             ContextMenu.Instance.addItem({
                 description: 'Styled Notes',
-                subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map((note, i) => ({
+                subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map(note => ({
                     description: ':' + StrCast(note.title),
-                    event: undoable((args: { x: number; y: number }) => {
+                    event: undoable(() => {
                         const textDoc = Docs.Create.TextDocument('', {
                             _width: 200,
                             x,
@@ -1757,12 +1768,12 @@ export namespace DocUtils {
             .filter(btnDoc => !btnDoc.hidden)
             .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
             .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title)
-            .map((dragDoc, i) => ({
+            .map(dragDoc => ({
                 description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
-                event: undoable((args: { x: number; y: number }) => {
+                event: undoable(() => {
                     const newDoc = DocUtils.delegateDragFactory(dragDoc);
                     if (newDoc) {
-                        newDoc.author = Doc.CurrentUserEmail;
+                        newDoc.author = ClientUtils.CurrentUserEmail;
                         newDoc.x = x;
                         newDoc.y = y;
                         EquationBox.SelectOnLoad = newDoc[Id];
@@ -1802,11 +1813,11 @@ export namespace DocUtils {
     }
     export function findTemplate(templateName: string, type: string, signature: string) {
         let docLayoutTemplate: Opt<Doc>;
-        const iconViews = DocListCast(Cast(Doc.UserDoc()['template_icons'], Doc, null)?.data);
-        const templBtns = DocListCast(Cast(Doc.UserDoc()['template_buttons'], Doc, null)?.data);
-        const noteTypes = DocListCast(Cast(Doc.UserDoc()['template_notes'], Doc, null)?.data);
-        const userTypes = DocListCast(Cast(Doc.UserDoc()['template_user'], Doc, null)?.data);
-        const clickFuncs = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data);
+        const iconViews = DocListCast(Cast(Doc.UserDoc().template_icons, Doc, null)?.data);
+        const templBtns = DocListCast(Cast(Doc.UserDoc().template_buttons, Doc, null)?.data);
+        const noteTypes = DocListCast(Cast(Doc.UserDoc().template_notes, Doc, null)?.data);
+        const userTypes = DocListCast(Cast(Doc.UserDoc().template_user, Doc, null)?.data);
+        const clickFuncs = DocListCast(Cast(Doc.UserDoc().template_clickFuncs, Doc, null)?.data);
         const allTemplates = iconViews
             .concat(templBtns)
             .concat(noteTypes)
@@ -1816,13 +1827,20 @@ export namespace DocUtils {
             .filter(doc => doc.isTemplateDoc);
         // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document.  but type is not reliable and there could be other types of template searches so this should be generalized
         // first try to find a template that matches the specific document type (<typeName>_<templateName>).  otherwise, fallback to a general match on <templateName>
-        !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc));
-        !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
+        !docLayoutTemplate &&
+            allTemplates.forEach(tempDoc => {
+                StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc);
+            });
+        !docLayoutTemplate &&
+            allTemplates.forEach(tempDoc => {
+                StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc);
+            });
         return docLayoutTemplate;
     }
     export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) {
         const templateName = templateSignature.replace(/\(.*\)/, '');
-        doc.layout_fieldKey = 'layout_' + (templateSignature || docLayoutTemplate?.title);
+        doc.layout_fieldKey = 'layout_' + (templateSignature || (docLayoutTemplate?.title ?? ''));
+        // eslint-disable-next-line no-param-reassign
         docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature);
 
         const customName = 'layout_' + templateSignature;
@@ -1859,14 +1877,15 @@ export namespace DocUtils {
         }
     }
     export function iconify(doc: Doc) {
-        const layout_fieldKey = Cast(doc.layout_fieldKey, 'string', null);
+        const layoutFieldKey = Cast(doc.layout_fieldKey, 'string', null);
         DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined);
-        if (layout_fieldKey && layout_fieldKey !== 'layout' && layout_fieldKey !== 'layout_icon') doc.deiconifyLayout = layout_fieldKey.replace('layout_', '');
+        if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') doc.deiconifyLayout = layoutFieldKey.replace('layout_', '');
     }
 
     export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) {
         runInAction(() => {
-            docList.forEach((d, i) => {
+            docList.forEach((doc, i) => {
+                const d = doc;
                 DocUtils.iconify(d);
                 d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size - size;
                 d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size - size;
@@ -1880,6 +1899,7 @@ export namespace DocUtils {
             newCollection._width = newCollection._height = size * 2;
             return newCollection;
         }
+        return undefined;
     }
     export function makeIntoPortal(doc: Doc, layoutDoc: Doc, allLinks: Doc[]) {
         const portalLink = allLinks.find(d => d.link_anchor_1 === doc && d.link_relationship === 'portal to:portal from');
@@ -1921,7 +1941,7 @@ export namespace DocUtils {
                 _timecodeToShow: Cast(doc._timecodeToShow, 'number', null),
             });
             Doc.AddDocToList(context, annotationField, pushpin);
-            const pushpinLink = DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, '');
+            DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, '');
             doc._timecodeToShow = undefined;
             return pushpin;
         }
@@ -1948,23 +1968,24 @@ export namespace DocUtils {
     // }
 
     function ConvertDMSToDD(degrees: number, minutes: number, seconds: number, direction: string) {
-        var dd = degrees + minutes / 60 + seconds / (60 * 60);
+        let dd = degrees + minutes / 60 + seconds / (60 * 60);
         if (direction === 'S' || direction === 'W') {
-            dd = dd * -1;
+            dd *= -1;
         } // Don't do anything for N or E
         return dd;
     }
 
-    export function assignImageInfo(result: Upload.FileInformation, proto: Doc) {
+    export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) {
+        const proto = protoIn;
         if (Upload.isImageInformation(result)) {
             const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
             const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
-            proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
-            proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
-            proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
-            if (NumCast(proto['data-nativeOrientation']) >= 5) {
-                proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
-                proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+            proto.data_nativeOrientation = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
+            proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+            proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+            if (NumCast(proto.data_nativeOrientation) >= 5) {
+                proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+                proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
             }
             proto.data_exif = JSON.stringify(result.exifData?.data);
             proto.data_contentSize = result.contentSize;
@@ -2033,7 +2054,7 @@ export namespace DocUtils {
         const generatedDocuments: Doc[] = [];
         Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => {
             const {
-                source: { newFilename, originalFilename, mimetype },
+                source: { newFilename, mimetype },
                 result,
             } = upfiles.lastElement();
             if ((result as any).message) {
@@ -2061,12 +2082,9 @@ export namespace DocUtils {
         const fileNoGuidPairs: Networking.FileGuidPair[] = files.map(file => ({ file }));
 
         const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs);
-        for (const {
-            source: { newFilename, mimetype },
-            result,
-        } of upfiles) {
+        upfiles.forEach(({ source: { newFilename, mimetype }, result }) => {
             newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options);
-        }
+        });
         return generatedDocuments;
     }
 
@@ -2091,34 +2109,115 @@ export namespace DocUtils {
     export function copyDragFactory(dragFactory: Doc) {
         if (!dragFactory) return undefined;
         const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
-        if (ndoc && dragFactory['dragFactory_count'] !== undefined) {
-            dragFactory['dragFactory_count'] = NumCast(dragFactory['dragFactory_count']) + 1;
-            Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory['dragFactory_count']).toString(), true);
+        if (ndoc && dragFactory.dragFactory_count !== undefined) {
+            dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1;
+            Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString(), true);
         }
 
         return ndoc;
     }
     export function delegateDragFactory(dragFactory: Doc) {
         const ndoc = Doc.MakeDelegateWithProto(dragFactory);
-        if (ndoc && dragFactory['dragFactory_count'] !== undefined) {
-            dragFactory['dragFactory_count'] = NumCast(dragFactory['dragFactory_count']) + 1;
-            Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory['dragFactory_count']).toString();
+        if (ndoc && dragFactory.dragFactory_count !== undefined) {
+            dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1;
+            Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString();
         }
         return ndoc;
     }
+
+    export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
+        const { clone, map, linkMap } = await Doc.MakeClone(doc);
+        const proms = new Set<string>();
+        function replacer(key: any, value: any) {
+            if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
+            if (value?.__type === 'image') {
+                const extension = value.url.replace(/.*\./, '');
+                proms.add(value.url.replace('.' + extension, '_o.' + extension));
+                return SerializationHelper.Serialize(new ImageField(value.url));
+            }
+            if (value?.__type === 'pdf') {
+                proms.add(value.url);
+                return SerializationHelper.Serialize(new PdfField(value.url));
+            }
+            if (value?.__type === 'audio') {
+                proms.add(value.url);
+                return SerializationHelper.Serialize(new AudioField(value.url));
+            }
+            if (value?.__type === 'video') {
+                proms.add(value.url);
+                return SerializationHelper.Serialize(new VideoField(value.url));
+            }
+            if (
+                value instanceof Doc ||
+                value instanceof ScriptField ||
+                value instanceof RichTextField ||
+                value instanceof InkField ||
+                value instanceof CsvField ||
+                value instanceof WebField ||
+                value instanceof DateField ||
+                value instanceof ProxyField ||
+                value instanceof ComputedField
+            ) {
+                return SerializationHelper.Serialize(value);
+            }
+            if (value instanceof Array && key !== ListFieldName && key !== InkDataFieldName) return { fields: value, __type: 'list' };
+            return value;
+        }
+
+        const docs: { [id: string]: any } = {};
+        const links: { [id: string]: any } = {};
+        Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1]));
+        Array.from(linkMap.entries()).forEach(l => (links[l[0]] = l[1]));
+        const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer));
+
+        const zip = new JSZip();
+        var count = 0;
+        const promArr = Array.from(proms)
+            .filter(url => url?.startsWith('/files'))
+            .map(url => url.replace('/', '')); // window.location.origin));
+        console.log(promArr.length);
+        if (!promArr.length) {
+            zip.file('docs.json', jsonDocs);
+            zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
+        } else
+            promArr.forEach((url, i) => {
+                // loading a file and add it in a zip file
+                JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => {
+                    if (err) throw err; // or handle the error
+                    // // Generate a directory within the Zip file structure
+                    // const assets = zip.folder("assets");
+                    // assets.file(filename, data, {binary: true});
+                    const assetPathOnServer = promArr[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%');
+                    zip.file(assetPathOnServer, data, { binary: true });
+                    console.log(' => ' + url);
+                    if (++count === promArr.length) {
+                        zip.file('docs.json', jsonDocs);
+                        zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
+                        // const a = document.createElement("a");
+                        // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+                        // a.href = url;
+                        // a.download = `DocExport-${this.props.Document[Id]}.zip`;
+                        // a.click();
+                    }
+                });
+            });
+    }
 }
 
 ScriptingGlobals.add('Docs', Docs);
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) {
     return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory;
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function makeDelegate(proto: any) {
     const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title });
     return d;
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function generateLinkTitle(link: Doc) {
-    const link_anchor_1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : '<?>';
-    const link_anchor_2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>';
+    const linkAnchor1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : '<?>';
+    const linkAnchor2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>';
     const relation = link.link_relationship || 'to';
-    return `${link_anchor_1title} (${relation}) ${link_anchor_2title}`;
+    return `${linkAnchor1title} (${relation}) ${linkAnchor2title}`;
 });
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 8451357ef..2939ba581 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { addStyleSheet } from '../../Utils';
+import { addStyleSheet } from '../../ClientUtils';
 import { Doc } from '../../fields/Doc';
 import { DocCast, StrCast } from '../../fields/Types';
 import { LightboxView } from '../views/LightboxView';
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 081115879..7811f8605 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,6 +1,6 @@
 import { observable, reaction, runInAction } from "mobx";
 import * as rp from 'request-promise';
-import { OmitKeys, Utils } from "../../Utils";
+import { ClientUtils, OmitKeys } from "../../ClientUtils";
 import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
 import { DocData } from "../../fields/DocSymbols";
 import { InkTool } from "../../fields/InkField";
@@ -12,7 +12,7 @@ import { ScriptField } from "../../fields/ScriptField";
 import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types";
 import { WebField, nullAudio } from "../../fields/URLField";
 import { SetCachedGroups, SharingPermissions } from "../../fields/util";
-import { GestureUtils } from "../../pen-gestures/GestureUtils";
+import { Gestures } from "../../pen-gestures/GestureTypes";
 import { DocServer } from "../DocServer";
 import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
 import { DocUtils, Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents";
@@ -20,20 +20,21 @@ import { DashboardView } from "../views/DashboardView";
 import { OverlayView } from "../views/OverlayView";
 import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView";
 import { Colors } from "../views/global/globalEnums";
-import { media_state } from "../views/nodes/AudioBox";
+import { mediaState } from "../views/nodes/AudioBox";
 import { OpenWhere } from "../views/nodes/DocumentView";
 import { ButtonType, FontIconBox } from "../views/nodes/FontIconBox/FontIconBox";
+import { ImageBox } from "../views/nodes/ImageBox";
+import { LabelBox } from "../views/nodes/LabelBox";
 import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
-import { DragManager, dropActionType } from "./DragManager";
+import { DragManager } from "./DragManager";
+import { dropActionType } from "./DropActionTypes";
 import { MakeTemplate } from "./DropConverter";
 import { FollowLinkScript } from "./LinkFollower";
-import { LinkManager } from "./LinkManager";
+import { LinkManager, UPDATE_SERVER_CACHE } from "./LinkManager";
 import { ScriptingGlobals } from "./ScriptingGlobals";
 import { ColorScheme } from "./SettingsManager";
 import { SnappingManager } from "./SnappingManager";
 import { UndoManager } from "./UndoManager";
-import { LabelBox } from "../views/nodes/LabelBox";
-import { ImageBox } from "../views/nodes/ImageBox";
 
 interface Button {
     // DocumentOptions fields a button can set
@@ -60,6 +61,7 @@ interface Button {
     subMenu?: Button[];
 }
 
+// eslint-disable-next-line import/no-mutable-exports
 export let resolvedPorts: { server: number, socket: number };
 export class CurrentUserUtils {
 
@@ -90,6 +92,7 @@ export class CurrentUserUtils {
         });
 
         const reqdOpts:DocumentOptions = { title: "child click editors", _height:75, isSystem: true};
+        // eslint-disable-next-line no-return-assign
         return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
     }
 
@@ -114,6 +117,7 @@ export class CurrentUserUtils {
         });
 
         const reqdOpts:DocumentOptions = {title: "click editor templates", _height:75, isSystem: true};
+        // eslint-disable-next-line no-return-assign
         return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
     }
 
@@ -131,6 +135,7 @@ export class CurrentUserUtils {
         }), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))];
 
         const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true };
+        // eslint-disable-next-line no-return-assign
         return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts));
     }
     static setupUserTemplates(doc: Doc, field="template_user") {
@@ -138,6 +143,7 @@ export class CurrentUserUtils {
         const reqdUserList = DocListCast(tempUsers?.data);
 
         const reqdOpts:DocumentOptions = { title: "User Layouts", _height: 75, isSystem: true };
+        // eslint-disable-next-line no-return-assign
         return DocUtils.AssignOpts(tempUsers, reqdOpts, reqdUserList) ?? (doc[field] = Docs.Create.TreeDocument(reqdUserList, reqdOpts));
     }
 
@@ -159,24 +165,26 @@ export class CurrentUserUtils {
         const reqdOpts = { title: "icon templates", _height: 75, isSystem: true };
         const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
 
+        const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({
+            layout: LabelBox.LayoutString(fieldKey), textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px",  _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
+        });
+        const imageBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.ImageDocument( "http://www.cs.brown.edu/~bcz/noImage.png", { layout:ImageBox.LayoutString(fieldKey), "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG,  _width: 360 / 4, _height: 270 / 4,  _layout_showTitle: "title", ...opts });
+        const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts  });
+        
         const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => {
             const title = "icon" + (type ? "_" + type : "");
             const curIcon = DocCast(templateIconsDoc[title]);
-            let creator = labelBox;
-            switch (opts.iconTemplate) {
-                case DocumentType.IMG : creator = imageBox; break;
-                case DocumentType.FONTICON: creator = fontBox; break;
-            }
+            const creator =  (() => { switch (opts.iconTemplate) {
+                                        case DocumentType.IMG : return imageBox;
+                                        case DocumentType.FONTICON: return fontBox;
+                                        default: return labelBox;
+                                    }})();
             const allopts = {isSystem: true, onClickScriptDisable: "never",  ...opts, title};
+            // eslint-disable-next-line no-return-assign
             return DocUtils.AssignScripts(  (curIcon?.iconTemplate === opts.iconTemplate ? 
                       DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[title] = MakeTemplate(creator(allopts, templateField)))),
                 {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", });
         };
-        const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({
-            layout: LabelBox.LayoutString(fieldKey), textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px",  _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
-        });
-        const imageBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.ImageDocument( "http://www.cs.brown.edu/~bcz/noImage.png", { layout:ImageBox.LayoutString(fieldKey), "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG,  _width: 360 / 4, _height: 270 / 4,  _layout_showTitle: "title", ...opts });
-        const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts  });
         const iconTemplates = [
             makeIconTemplate(undefined,          "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "dimgray"}),
             makeIconTemplate(DocumentType.AUDIO, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "lightgreen"}),
@@ -188,9 +196,9 @@ export class CurrentUserUtils {
             makeIconTemplate(DocumentType.COL,   "icon",  { iconTemplate:DocumentType.IMG}),
             makeIconTemplate(DocumentType.VID,   "icon",  { iconTemplate:DocumentType.IMG}),
             makeIconTemplate(DocumentType.BUTTON,"title", { iconTemplate:DocumentType.FONTICON}),
-            //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now
+            // nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now
             makeIconTemplate("transcription" as any, "transcription", { iconTemplate:DocumentType.LABEL, backgroundColor: "orange" }),
-            //makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts))
+            // makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts))
         ].filter(d => d).map(d => d!);
         DocUtils.AssignOpts(DocCast(doc[field]), {}, iconTemplates);
     }
@@ -241,14 +249,15 @@ export class CurrentUserUtils {
                         Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true,  _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
                     ], {...opts, title: "Slide View Template"}));
         const plotlyApi = () => {
-            var plotly = Doc.MyPublishedDocs.find(doc => doc.title === "@plotly");
+            let plotly = Doc.MyPublishedDocs.find(doc => doc.title === "@plotly");
             if (!plotly) {
-                const plotly = Docs.Create.TextDocument(
+                plotly = Docs.Create.TextDocument(
                     `await import("https://cdn.plot.ly/plotly-2.27.0.min.js");
                     Plotly.newPlot(dashDiv.id, [ --DOCDATA-- ])`
                     , {title: "@plotly", title_custom: true, _layout_showTitle:"title",  _width:300,_height:400});
                 Doc.AddToMyPublished(plotly);
             }
+            return plotly;
         }
         const plotlyView = (opts:DocumentOptions) => {
             const rtfield = new RichTextField(JSON.stringify(
@@ -280,10 +289,10 @@ export class CurrentUserUtils {
             return slide; 
         }
         const mermaidsApi = () => {
-            var mermaids = Doc.MyPublishedDocs.find(doc => doc.title === "@mermaids");
+            let mermaids = Doc.MyPublishedDocs.find(doc => doc.title === "@mermaids");
             if (!mermaids) {
-                const mermaids = Docs.Create.TextDocument(
-                `const mdef = (await import("https://cdn.jsdelivr.net/npm/mermaid\@10.8.0/dist/mermaid.esm.min.mjs")).default;
+                mermaids = Docs.Create.TextDocument(
+                `const mdef = (await import("https://cdn.jsdelivr.net/npm/mermaid@10.8.0/dist/mermaid.esm.min.mjs")).default;
                 window["callb"] = (x)  => { 
                    alert(x);
                 }
@@ -301,6 +310,7 @@ export class CurrentUserUtils {
                 , {title: "@mermaids", title_custom: true, _layout_showTitle:"title",  _width:300,_height:400});
                 Doc.AddToMyPublished(mermaids);
             }
+            return mermaids;
         }
         const mermaidsView = (opts:DocumentOptions) => {
             const rtfield = new RichTextField(JSON.stringify(
@@ -333,7 +343,7 @@ pie title Minerals in my tap water
             slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
             return slide; 
         }
-        const apis = [plotlyApi(), mermaidsApi()]
+        plotlyApi(); mermaidsApi();
         const emptyThings:{key:string,  // the field name where the empty thing will be stored
                           opts:DocumentOptions,   // the document options that are required for the empty thing
                           funcs?:{[key:string]: any},  // computed fields that are rquired for the empth thing
@@ -465,83 +475,9 @@ pie title Minerals in my tap water
         return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
     }
 
-    // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu
-    static setupActiveMobileMenu(doc: Doc, field="activeMobileMenu") {
-        const reqdOpts = { _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, isSystem: true, _chromeHidden: true,};
-        DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(this.setupMobileButtons(), opts), reqdOpts);
-    }
-
-    // Sets up mobile buttons for inside mobile menu
-    static setupMobileButtons(doc?: Doc, buttons?: string[]) {
-        return [];
-        const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [
-            { title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards from your mobile, and navigate through all of your documents. " },
-            { title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." },
-            { title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploadCollection()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." },
-            { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "lightgrey", info: "Use your phone to record, dictate and then upload audio onto Dash Web." },
-            { title: "PRESENTATION", icon: "desktop", click: 'switchToMobilePresentation()', backgroundColor: "lightgrey", info: "Use your phone as a remote for you presentation." },
-            { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "lightgrey", info: "Change your password, log out, or manage your account security." }
-        ];
-        // returns a list of mobile buttons
-        return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data =>
-            this.mobileButton({
-                title: data.title,
-                _lockedPosition: true,
-                onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
-                backgroundColor: data.backgroundColor, isSystem: true
-            },
-                [this.createToolButton({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", isSystem: true, btnType: ButtonType.ClickButton, }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])])
-        );
-    }
-
-    // sets up the main document for the mobile button
-    static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, {
-        ...opts,
-         _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15,
-        layout_borderRounding: "5px", layout_boxShadow: "0 0", isSystem: true
-    }) as any as Doc
-
-    // sets up the text container for the information contained within the mobile button
-    static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, {
-        ...opts,
-        _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25,
-        backgroundColor: "rgba(0,0,0,0)", layout_borderRounding: "0", layout_boxShadow: "0 0", ignoreClick: true, isSystem: true
-    }) as any as Doc
-
-    // Sets up the title of the button
-    static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, {
-        ...opts,
-        title: buttonTitle, _text_fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", isSystem: true
-    }) as any as Doc
-
-    // Sets up the description of the button
-    static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, {
-        ...opts,
-        title: "info", _text_fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, isSystem: true
-    }) as any as Doc
-
-
-
-    static setupMobileInkingDoc(userDoc: Doc) {
-        return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white", isSystem: true });
-    }
-
-    static setupMobileUploadDoc(userDoc: Doc) {
-        // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" })
-        const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", {
-            title: "Upload Images From the Web", _lockedPosition: true, isSystem: true
-        });
-        const uploadDoc = Docs.Create.StackingDocument([], {
-            title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, isSystem: true, _chromeHidden: true,
-        });
-        return Docs.Create.StackingDocument([webDoc, uploadDoc], {
-            _width: screen.width, _lockedPosition: true, title: "Upload", _layout_autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", isSystem: true, _chromeHidden: true,
-        });
-    }
-
     /// Search option on the left side button panel
     static setupSearcher(doc: Doc, field:string) {
-        return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), {
+        return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.SearchDocument(opts), {
                 dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDragAction: dropActionType.embed, 
                 _lockedPosition: true, _type_collection: CollectionViewType.Schema  });
     }
@@ -552,7 +488,7 @@ pie title Minerals in my tap water
         const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, allTools?.length ? allTools[0]:undefined);
         const userTools = allTools && allTools?.length > 1 ? allTools[1]:undefined;
         const userBtns = CurrentUserUtils.setupUserDocumentCreatorButtons(doc, userTools);
-        //doc.myUserBtns = new PrefetchProxy(userBtns);
+        // doc.myUserBtns = new PrefetchProxy(userBtns);
         const reqdToolOps:DocumentOptions = {
             title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
             layout_explainer: "This is a palette of documents that can be created.",
@@ -563,7 +499,7 @@ pie title Minerals in my tap water
 
     /// initializes the left sidebar dashboard pane
     static setupDashboards(doc: Doc, field:string) {
-        var myDashboards = DocCast(doc[field]);
+        let myDashboards = DocCast(doc[field]);
 
         const newDashboard = `createNewDashboard()`;
 
@@ -572,9 +508,9 @@ pie title Minerals in my tap water
         const reqdBtnScript = {onClick: newDashboard,}
         const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.layout_headerButton), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
        
-        const contextMenuScripts = [/*newDashboard*/] as string[];
-        const contextMenuLabels = [/*"Create New Dashboard"*/]  as string[];
-        const contextMenuIcons = [/*"plus"*/]  as string[];
+        const contextMenuScripts = [/* newDashboard */] as string[];
+        const contextMenuLabels = [/* "Create New Dashboard" */]  as string[];
+        const contextMenuIcons = [/* "plus" */]  as string[];
         const childContextMenuScripts = [`toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(this)`, 'removeDashboard(this)', 'resetDashboard(this)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters
         const childContextMenuFilters =  ['!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, '!IsNoviceMode()'];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts
         const childContextMenuLabels =  ["Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters
@@ -605,7 +541,7 @@ pie title Minerals in my tap water
 
     /// initializes the left sidebar File system pane
     static setupFilesystem(doc: Doc, field:string) {
-        var myFilesystem = DocCast(doc[field]);
+        const myFilesystem = DocCast(doc[field]);
         
         const newFolderOpts: DocumentOptions = {
             _forceActive: true, _dragOnlyWithinContainer: true, _embedContainer: Doc.MyFilesystem, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
@@ -650,7 +586,7 @@ pie title Minerals in my tap water
     /// initializes the left sidebar panel view of the UserDoc
     static setupUserDocView(doc: Doc, field:string) {
         const reqdOpts:DocumentOptions = {
-            _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
+            _lockedPosition: true, _gridGap: 5, _forceActive: true, title: ClientUtils.CurrentUserEmail +"-view",
             layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: dropActionType.same, ignoreClick: true, isSystem: true,
             treeView_HideTitle: true, treeView_TruncateTitleWidth: 350
         };
@@ -671,7 +607,7 @@ pie title Minerals in my tap water
 
     static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
         btnType: ButtonType.ToolButton, _dropPropertiesToRemove: new List<string>([ "layout_hideContextMenu"]), 
-        /*_nativeWidth: 40, _nativeHeight: 40, */ _width: 40, _height: 40, isSystem: true, ...opts,
+        /* _nativeWidth: 40, _nativeHeight: 40, */ _width: 40, _height: 40, isSystem: true, ...opts,
     })
 
     /// initializes the required buttons in the expanding button menu at the bottom of the Dash window
@@ -694,8 +630,8 @@ pie title Minerals in my tap water
             title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: dropActionType.move,
             childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: true, ignoreClick: true
         };
-        reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
-        reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
+        reaction(() => UndoManager.redoStack.slice(), () => { Doc.GetProto(btns.find(btn => btn.title === "Redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4; }, { fireImmediately: true });
+        reaction(() => UndoManager.undoStack.slice(), () => { Doc.GetProto(btns.find(btn => btn.title === "Undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4; }, { fireImmediately: true });
         return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
     }
 
@@ -752,9 +688,9 @@ pie title Minerals in my tap water
             { title: "Pen",    toolTip: "Pen (Ctrl+P)",                     btnType: ButtonType.ToggleButton, icon: "pen-nib",    toolType: "pen",                            scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }},
             { title: "Write",  toolTip: "Write (Ctrl+Shift+P)",             btnType: ButtonType.ToggleButton, icon: "pen",        toolType: "write",                          scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
             { title: "Eraser", toolTip: "Eraser (Ctrl+E)",                  btnType: ButtonType.ToggleButton, icon: "eraser",     toolType: "eraser",                         scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
-            { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",     toolType:GestureUtils.Gestures.Circle,      scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
-            { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",     toolType:GestureUtils.Gestures.Rectangle,   scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
-            { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",      toolType:GestureUtils.Gestures.Line,        scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+            { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",     toolType: Gestures.Circle,      scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+            { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",     toolType: Gestures.Rectangle,   scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+            { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",      toolType: Gestures.Line,        scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
             { title: "Mask",   toolTip: "Mask",                             btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask",                        scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
             { title: "Labels", toolTip: "Lab els",                          btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels",                         scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, },
             { title: "Width",  toolTip: "Stroke width",                     btnType: ButtonType.NumberSliderButton,               toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1},
@@ -835,13 +771,13 @@ pie title Minerals in my tap water
     
     static setupContextMenuBtn(params:Button, menuDoc:Doc):Doc {
         const menuBtnDoc = DocListCast(menuDoc?.data).find( doc => doc.title === params.title);
-        const subMenu = params.subMenu;
+        const {subMenu} = params;
         if (!subMenu) { // button does not have a sub menu
             return this.setupContextMenuButton(params, menuBtnDoc, menuDoc);
         }  
         // linear view
         const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
-            childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
+            childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: !params.scripts?.onClick,
             linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc};
         
         const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
@@ -873,10 +809,10 @@ pie title Minerals in my tap water
             { opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true},                                                                                              scripts: { onClick:  `snapshotDashboard()`}},
             { opts: { title: "Recordings",            toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`}, scripts: { script:  `{ return replayWorkspace(value, _readOnly_); }`, onDragScript: `{ return startRecordingDrag(value); }`}},
             { opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`},                                                  scripts: { onClick: `stopWorkspaceRecording()`}},
-            { opts: { title: "Play",    icon: "play", toolTip: "Play recording",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`},                       scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
-            { opts: { title: "Pause",   icon: "pause",toolTip: "Pause playback",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Playing}"`},                      scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}},
-            { opts: { title: "Stop",    icon: "stop", toolTip: "Stop playback",        btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`},                       scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}},
-            { opts: { title: "Delete",  icon: "trash",toolTip: "delete selected rec",  btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`},                       scripts: { onClick: `removeWorkspaceReplaying(getCurrentRecording())`}}
+            { opts: { title: "Play",    icon: "play", toolTip: "Play recording",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`},                       scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
+            { opts: { title: "Pause",   icon: "pause",toolTip: "Pause playback",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Playing}"`},                      scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}},
+            { opts: { title: "Stop",    icon: "stop", toolTip: "Stop playback",        btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`},                       scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}},
+            { opts: { title: "Delete",  icon: "trash",toolTip: "delete selected rec",  btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`},                       scripts: { onClick: `removeWorkspaceReplaying(getCurrentRecording())`}}
         ];
         const btns = btnDescs.map(desc => dockBtn({_width: desc.opts.width??30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts, desc.funcs));
         const dockBtnsReqdOpts:DocumentOptions = {
@@ -890,12 +826,14 @@ pie title Minerals in my tap water
         return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", isSystem: true });
     }
     
-    /// The database of all links on all documents
+    static newAccount: boolean = false;
+
+    // The database of all links on all documents
     static setupLinkDocs(doc: Doc, linkDatabaseId: string) {
-        if (!(Docs.newAccount ? undefined : DocCast(doc.myLinkDatabase))) {
+        if (!(CurrentUserUtils.newAccount ? undefined : DocCast(doc.myLinkDatabase))) {
             const linkDocs = new Doc(linkDatabaseId, true);
-            linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail;
-            linkDocs.author = Doc.CurrentUserEmail;
+            linkDocs.title = "LINK DATABASE: " + ClientUtils.CurrentUserEmail;
+            linkDocs.author = ClientUtils.CurrentUserEmail;
             linkDocs.isSystem = true;
             linkDocs.data = new List<Doc>([]);
             linkDocs["acl-Guest"] = SharingPermissions.Augment;
@@ -949,16 +887,17 @@ pie title Minerals in my tap water
     /// Updates the UserDoc to have all required fields, docs, etc.  No changes should need to be
     /// written to the server if the code hasn't changed.  However, choices need to be made for each Doc/field
     /// whether to revert to "default" values, or to leave them as the user/system last set them.
-    static updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
+    static updateUserDocument(docIn: Doc, sharingDocumentId: string, linkDatabaseId: string) {
+        const doc = docIn;
         DocUtils.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {});
-        reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data_modificationDate"]),
+        reaction(() => DateCast(DocCast(doc.globalGroupDatabase).data_modificationDate),
             async () => {
                 const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
-                const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
-                SetCachedGroups(["Guest", ...mygroups?.map(g => StrCast(g.title))]);
+                const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(ClientUtils.CurrentUserEmail)) || [];
+                SetCachedGroups(["Guest", ...(mygroups?.map(g => StrCast(g.title))??[])]);
             }, { fireImmediately: true });
         doc.isSystem ?? (doc.isSystem = true);
-        doc.title ?? (doc.title = Doc.CurrentUserEmail);
+        doc.title ?? (doc.title = ClientUtils.CurrentUserEmail);
         Doc.noviceMode ?? (Doc.noviceMode = true);
         doc._showLabel ?? (doc._showLabel = true);
         doc.textAlign ?? (doc.textAlign = "left");
@@ -971,7 +910,7 @@ pie title Minerals in my tap water
         doc.activeFillColor ?? (doc.activeFillColor = "");
         doc.activeArrowStart ?? (doc.activeArrowStart = "");
         doc.activeArrowEnd ?? (doc.activeArrowEnd = "");
-        doc.activeDash ?? (doc.activeDash == "0");
+        doc.activeDash ?? (doc.activeDash === "0");
         doc.fontSize ?? (doc.fontSize = "12px");
         doc.fontFamily ?? (doc.fontFamily = "Arial");
         doc.fontColor ?? (doc.fontColor = "black");
@@ -988,15 +927,14 @@ pie title Minerals in my tap water
         this.setupLinkDocs(doc, linkDatabaseId);
         this.setupSharedDocs(doc, sharingDocumentId);  // sets up the right sidebar collection for mobile upload documents and sharing
         this.setupDefaultIconTemplates(doc);  // creates a set of icon templates triggered by the document deoration icon
-        this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile
         this.setupPublished(doc);  // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box)
         this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected
         this.setupTopbarButtons(doc);
         this.setupDockedButtons(doc);  // the bottom bar of font icons
         this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left
         this.setupDocTemplates(doc); // sets up the template menu of templates
-        //this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
-        DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) =>  Docs.Prototypes.MainScriptDocument(), {});
+        // sthis.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
+        DocUtils.AssignDocField(doc, "globalScriptDatabase", () =>  Docs.Prototypes.MainScriptDocument(), {});
         DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, _chromeHidden:true, layout_maxShown: 10, childLayoutFitWidth:false, childDocumentsActive:false, dropAction: dropActionType.move}); // drop down panel at top of dashboard for stashing documents
         
         Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards)
@@ -1005,10 +943,11 @@ pie title Minerals in my tap water
 
         Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org")
 
+        // eslint-disable-next-line no-new
         new LinkManager();
 
-        DocServer.CacheNeedsUpdate && setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500);
-        setInterval(DocServer.UPDATE_SERVER_CACHE, 120000);
+        DocServer.CacheNeedsUpdate && setTimeout(UPDATE_SERVER_CACHE, 2500);
+        setInterval(UPDATE_SERVER_CACHE, 120000);
         return doc;
     }
     static setupFieldInfos(doc:Doc, field="fieldInfos") {
@@ -1032,11 +971,11 @@ pie title Minerals in my tap water
 
     @observable public static ServerVersion: string = ';'
     public static async loadCurrentUser() {
-        return rp.get(Utils.prepend("/getCurrentUser")).then(async response => {
+        return rp.get(ClientUtils.prepend("/getCurrentUser")).then(async response => {
             if (response) {
                 const result: { version: string, userDocumentId: string, sharingDocumentId: string, linkDatabaseId: string,  email: string, cacheDocumentIds: string, resolvedPorts: string } = JSON.parse(response);
-                runInAction(() => CurrentUserUtils.ServerVersion = result.version);
-                Doc.CurrentUserEmail = result.email;
+                runInAction(() => { CurrentUserUtils.ServerVersion = result.version; });
+                ClientUtils.CurrentUserEmail = result.email;
                 resolvedPorts = result.resolvedPorts as any;
                 DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts?.socket, result.email);
                 if (result.cacheDocumentIds) 
@@ -1044,13 +983,13 @@ pie title Minerals in my tap water
                     const ids = result.cacheDocumentIds.split(";");
                     const batch = 30000;
                     for (let i = 0; i < ids.length; i += batch) {
+                        // eslint-disable-next-line no-await-in-loop
                         await DocServer.GetRefFields(ids.slice(i, i+batch));
                     }
                 }
                 return result;
-            } else {
-                throw new Error("There should be a user! Why does Dash think there isn't one?");
-            }
+            } 
+            throw new Error("There should be a user! Why does Dash think there isn't one?");
         });
     }
 
@@ -1060,12 +999,12 @@ pie title Minerals in my tap water
         linkDatabaseId: string;
     }) {
         return DocServer.GetRefField(info.userDocumentId).then(async field => {
-            Docs.newAccount = !(field instanceof Doc);
+            CurrentUserUtils.newAccount = !(field instanceof Doc);
             await Docs.Prototypes.initialize();
-            const userDoc = Docs.newAccount ? new Doc(info.userDocumentId, true) : field as Doc;
+            const userDoc = CurrentUserUtils.newAccount ? new Doc(info.userDocumentId, true) : field as Doc;
             this.updateUserDocument(Doc.SetUserDoc(userDoc), info.sharingDocumentId, info.linkDatabaseId);
-            if (Docs.newAccount) {
-                if (Doc.CurrentUserEmail === "guest") {
+            if (CurrentUserUtils.newAccount) {
+                if (ClientUtils.CurrentUserEmail === "guest") {
                     DashboardView.createNewDashboard(undefined, "guest dashboard");
                 } else {
                     userDoc.activePage = "home";
@@ -1082,14 +1021,10 @@ pie title Minerals in my tap water
         input.type = "file";
         input.multiple = true;
         input.accept = ".zip, application/pdf, video/*, image/*, audio/*";
-        input.onchange = async _e => {
+        input.onchange = async () => {
             const file = input.files?.[0];
             if (file?.type === 'application/zip' || file?.type === 'application/x-zip-compressed') {
                 const doc = await Doc.importDocument(file);
-                // NOT USING SOLR, so need to replace this with something else // if (doc instanceof Doc) {
-                //     setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs =>
-                //         docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added.
-                // }
                 const list = Cast(Doc.MyImports.data, listSpec(Doc), null);
                 doc instanceof Doc && list?.splice(0, 0, doc);
             } else if (input.files && input.files.length !== 0) {
@@ -1109,10 +1044,17 @@ pie title Minerals in my tap water
     }
 }
 
-ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs");
-ScriptingGlobals.add(function IsExploreMode() { return SnappingManager.ExploreMode; }, "is Dash in exploration mode");
-ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function MySharedDocs()    { return Doc.MySharedDocs; }, "document containing all shared Docs");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function IsExploreMode()   { return SnappingManager.ExploreMode; }, "is Dash in exploration mode");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function IsNoviceMode()    { return Doc.noviceMode; }, "is Dash in novice mode");
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
-ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function importDocument()  { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
-ScriptingGlobals.add(function getSharingDoc() {return Doc.SharingDoc() });
\ No newline at end of file
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function getSharingDoc()   { return Doc.SharingDoc() });
\ No newline at end of file
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 82c63695c..207d3ea0b 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -1,15 +1,15 @@
 import * as interpreter from 'words-to-numbers';
 // @ts-ignore  bcz: how are you supposed to include these definitions since dom-speech-recognition isn't a module?
 import type {} from '@types/dom-speech-recognition';
+import { ClientUtils } from '../../ClientUtils';
 import { Doc, Opt } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
 import { listSpec } from '../../fields/Schema';
 import { Cast, CastCtor, DocCast } from '../../fields/Types';
 import { AudioField, ImageField } from '../../fields/URLField';
-import { Utils } from '../../Utils';
-import { Docs } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
+import { Docs } from '../documents/Documents';
 import { DictationOverlay } from '../views/DictationOverlay';
 import { DocumentView, OpenWhere } from '../views/nodes/DocumentView';
 import { SelectionManager } from './SelectionManager';
@@ -103,7 +103,7 @@ export namespace DictationManager {
                 results = await (pendingListen = listenImpl(options));
                 pendingListen = undefined;
                 if (results) {
-                    Utils.CopyText(results);
+                    ClientUtils.CopyText(results);
                     if (overlay) {
                         DictationOverlay.Instance.isListening = false;
                         const execute = options?.tryExecute;
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 40d28c690..67a61f17e 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -8,10 +8,9 @@ import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { AudioField } from '../../fields/URLField';
 import { GetEffectiveAcl } from '../../fields/util';
 import { CollectionViewType } from '../documents/DocumentTypes';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { TabDocView } from '../views/collections/TabDocView';
 import { LightboxView } from '../views/LightboxView';
-import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, OpenWhere } from '../views/nodes/DocumentView';
 import { FocusViewOptions } from '../views/nodes/FieldView';
 import { KeyValueBox } from '../views/nodes/KeyValueBox';
 import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
@@ -20,12 +19,14 @@ import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
 
 export class DocumentManager {
+    // eslint-disable-next-line no-use-before-define
     private static _instance: DocumentManager;
     public static get Instance(): DocumentManager {
+        // eslint-disable-next-line no-return-assign
         return this._instance || (this._instance = new this());
     }
 
-    //global holds all of the nodes (regardless of which collection they're in)
+    // global holds all of the nodes (regardless of which collection they're in)
     @observable _documentViews = new Set<DocumentView>();
     @observable.shallow public CurrentlyLoading: Doc[] = [];
     @computed public get DocumentViews() {
@@ -38,7 +39,7 @@ export class DocumentManager {
         this._documentViews.delete(dv);
     }
 
-    //private constructor so no other class can create a nodemanager
+    // private constructor so no other class can create a nodemanager
     private constructor() {
         makeObservable(this);
         observe(this.CurrentlyLoading, change => {
@@ -52,6 +53,7 @@ export class DocumentManager {
                 case 'splice':
                     (change as any).removed.forEach((doc: Doc) => DocumentManager.Instance.getAllDocumentViews(doc).forEach(dv => StrCast(dv.Document.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify())));
                     break;
+                default:
             }
         });
     }
@@ -101,7 +103,7 @@ export class DocumentManager {
         SelectionManager.DeselectView(view);
     });
 
-    //gets all views
+    // gets all views
     public getDocumentViewsById(id: string) {
         const toReturn: DocumentView[] = [];
         DocumentManager.Instance.DocumentViews.forEach(view => {
@@ -135,25 +137,17 @@ export class DocumentManager {
         );
     }
 
-    public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
+    public getLightboxDocumentView = (toFind: Doc): DocumentView | undefined => {
         const views: DocumentView[] = [];
         DocumentManager.Instance.DocumentViews.forEach(view => LightboxView.Contains(view) && Doc.AreProtosEqual(view.Document, toFind) && views.push(view));
-        return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
+        return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /* && view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse */) || (views.length ? views[0] : undefined);
     };
-    public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
-        if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind, originatingDoc);
-        const views = this.getDocumentViews(toFind); //.filter(view => view.Document !== originatingDoc);
-        return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
+    public getFirstDocumentView = (toFind: Doc): DocumentView | undefined => {
+        if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind);
+        const views = this.getDocumentViews(toFind); // .filter(view => view.Document !== originatingDoc);
+        return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /* && view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse */) || (views.length ? views[0] : undefined);
     };
-    public getDocumentViews(toFindIn: Doc): DocumentView[] {
-        const toFind =
-            // Array.from(DocumentManager.Instance.DocumentViews).find(
-            //     dv =>
-            //         ((dv.Document.data as any)?.url?.href && (dv.Document.data as any)?.url?.href === (toFindIn.data as any)?.url?.href) ||
-            //         ((DocCast(dv.Document.annotationOn)?.data as any)?.url?.href && (DocCast(dv.Document.annotationOn)?.data as any)?.url?.href === (DocCast(toFindIn.annotationOn)?.data as any)?.url?.href)
-            // )?.Document ??
-            toFindIn;
-
+    public getDocumentViews(toFind: Doc): DocumentView[] {
         const toReturn: DocumentView[] = [];
         const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.Contains(view));
         const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.Contains(view));
@@ -172,7 +166,7 @@ export class DocumentManager {
     static GetContextPath(doc: Opt<Doc>, includeExistingViews?: boolean) {
         if (!doc) return [];
         const srcContext = DocCast(doc.annotationOn, DocCast(doc.embedContainer));
-        var containerDocContext = srcContext ? [srcContext, doc] : [doc];
+        let containerDocContext = srcContext ? [srcContext, doc] : [doc];
         while (
             containerDocContext.length &&
             DocCast(containerDocContext[0]?.embedContainer) &&
@@ -204,10 +198,6 @@ export class DocumentManager {
         DocumentManager._overlayViews?.clear();
     }
     static _overlayViews = new ObservableSet<DocumentView>();
-    static addView = (doc: Doc, finished?: () => void) => {
-        CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
-        finished?.();
-    };
 
     public static LinkCommonAncestor(linkDoc: Doc) {
         const anchor = (which: number) => {
@@ -228,7 +218,7 @@ export class DocumentManager {
     //           focusing on each context
     public showDocumentView = async (targetDocView: DocumentView, options: FocusViewOptions) => {
         const docViewPath = [...(targetDocView.containerViewPath?.() ?? []), targetDocView];
-        let rootContextView = docViewPath.shift();
+        const rootContextView = docViewPath.shift();
         await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false })));
         if (options.toggleTarget && (!options.didMove || targetDocView.Document.hidden)) targetDocView.Document.hidden = !targetDocView.Document.hidden;
         else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.Document, options.openLocation);
@@ -242,9 +232,10 @@ export class DocumentManager {
     //           and finally restoring the targetDoc to the viewSpec specified by the last document which may either be the targetDoc, or a viewSpec that describes the targetDoc configuration
     public showDocument = async (
         targetDoc: Doc, // document to display
-        options: FocusViewOptions, // options for how to navigate to target
+        optionsIn: FocusViewOptions, // options for how to navigate to target
         finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
     ) => {
+        const options = optionsIn;
         Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
         const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
         if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
@@ -253,13 +244,14 @@ export class DocumentManager {
             options.toggleTarget = false;
             TabDocView.Activate(tabView?._document);
         }
-        let rootContextView =
+        const rootContextView =
             docContextPath.length &&
             (await new Promise<DocumentView>(res => {
                 const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
                 if (viewIndex !== -1) {
                     viewIndex && docContextPath.splice(0, viewIndex);
-                    return res(this.getDocumentView(docContextPath[0])!);
+                    res(this.getDocumentView(docContextPath[0])!);
+                    return;
                 }
                 options.didMove = true;
                 (!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
@@ -270,7 +262,9 @@ export class DocumentManager {
             const target = DocCast(targetDoc.annotationOn, targetDoc);
             const contextView = this.getDocumentView(DocCast(target.embedContainer));
             if (contextView?.ComponentView?.addDocTab?.(target, OpenWhere.lightbox)) {
-                await new Promise<void>(waitres => setTimeout(() => waitres()));
+                await new Promise<void>(waitres => {
+                    setTimeout(() => waitres());
+                });
             }
         }
         docContextPath.shift();
@@ -287,19 +281,25 @@ export class DocumentManager {
     };
 
     focusViewsInPath = async (
-        docView: DocumentView, //
-        options: FocusViewOptions,
+        docViewIn: DocumentView, //
+        optionsIn: FocusViewOptions,
         iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean }>
     ) => {
         let contextView: DocumentView | undefined; // view containing context that contains target
         let focused = false;
+        let docView = docViewIn;
+        const options = optionsIn;
         while (true) {
             if (docView.Document.layout_fieldKey === 'layout_icon') {
-                await new Promise<void>(res => docView.iconify(res));
+                // eslint-disable-next-line no-await-in-loop
+                await new Promise<any>(res => {
+                    docView.iconify(res);
+                });
                 options.didMove = true;
             }
             const nextFocus = docView._props.focus(docView.Document, options); // focus the view within its container
-            focused = focused || (nextFocus === undefined ? false : true); // keep track of whether focusing on a view needed to actually change anything
+            focused = focused || nextFocus !== undefined; // keep track of whether focusing on a view needed to actually change anything
+            // eslint-disable-next-line no-await-in-loop
             const { childDocView, viewSpec } = await iterator(docView);
             if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.Document, docView, contextView, focused };
             contextView = options.anchorDoc?.layout_unrendered && !childDocView.Document.layout_unrendered ? childDocView : docView;
@@ -308,10 +308,11 @@ export class DocumentManager {
     };
 
     @action
-    restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: FocusViewOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+    restoreDocView(viewSpec: Opt<Doc>, docViewIn: DocumentView, options: FocusViewOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+        const docView = docViewIn;
         if (viewSpec && docView) {
-            //if (docView.ComponentView instanceof FormattedTextBox)
-            //viewSpec !== docView.Document &&
+            // if (docView.ComponentView instanceof FormattedTextBox)
+            // viewSpec !== docView.Document &&
             docView.ComponentView?.focus?.(viewSpec, options);
             PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500);
             Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect);
@@ -332,7 +333,10 @@ export class DocumentManager {
         }
     }
 }
-export function DocFocusOrOpen(doc: Doc, options: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
+// eslint-disable-next-line default-param-last
+export function DocFocusOrOpen(docIn: Doc, optionsIn: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
+    let doc = docIn;
+    const options = optionsIn;
     const func = () => {
         const cv = DocumentManager.Instance.getDocumentView(containingDoc);
         const dv = DocumentManager.Instance.getDocumentView(doc, cv);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 9627c5df2..62f055f1a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,3 +1,5 @@
+/* eslint-disable import/no-mutable-exports */
+/* eslint-disable no-use-before-define */
 /**
  * The DragManager handles all dragging interactions that occur entirely within Dash (as opposed to external drag operations from the file system, etc)
  *
@@ -13,32 +15,25 @@
  */
 
 import { action, observable, runInAction } from 'mobx';
+import { ClientUtils } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
 import { DateField } from '../../fields/DateField';
-import { Doc, Field, Opt, StrListCast } from '../../fields/Doc';
+import { Doc, FieldType, Opt, StrListCast } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
 import { ScriptField } from '../../fields/ScriptField';
 import { ScriptCast } from '../../fields/Types';
-import { emptyFunction, Utils } from '../../Utils';
-import { Docs, DocUtils } from '../documents/Documents';
+import { DocUtils, Docs } from '../documents/Documents';
 import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView';
 import { DocumentView } from '../views/nodes/DocumentView';
+import { dropActionType } from './DropActionTypes';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
 import { SnappingManager } from './SnappingManager';
 import { UndoManager } from './UndoManager';
-import { DocData } from '../../fields/DocSymbols';
-const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
 
-export enum dropActionType {
-    embed = 'embed', // create a new embedding of the dragged document for the new location
-    copy = 'copy', // copy the dragged document
-    move = 'move', // move the dragged document to the drop location after removing it from where it was
-    add = 'add', // add the dragged document to the drop location without removing it from where it was
-    same = 'same', // only allow drop within same collection (or same hierarchical tree collection)
-    inPlace = 'inSame', // keep document in place (unless overridden by a drag modifier)
-    proto = 'proto',
-} // undefined = move, same = move but doesn't call dropPropertiesToRemove
+const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
 
 /**
  * Initialize drag
@@ -83,6 +78,9 @@ export namespace DragManager {
     let dragLabel: HTMLDivElement;
     export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => boolean>;
     export let CompleteWindowDrag: Opt<(aborted: boolean) => void>;
+    export let AbortDrag: () => void = emptyFunction;
+    export const docsBeingDragged: Doc[] = observable([]);
+    export let DocDragData: DocumentDragData | undefined;
 
     export function Root() {
         const root = document.getElementById('root');
@@ -91,7 +89,6 @@ export namespace DragManager {
         }
         return root;
     }
-    export let AbortDrag: () => void = emptyFunction;
     export type MoveFunction = (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
     export type RemoveFunction = (document: Doc | Doc[]) => boolean;
 
@@ -106,6 +103,7 @@ export namespace DragManager {
 
     // event called when the drag operation results in a drop action
     export class DropEvent {
+        // eslint-disable-next-line no-useless-constructor
         constructor(
             readonly x: number,
             readonly y: number,
@@ -115,7 +113,9 @@ export namespace DragManager {
             readonly metaKey: boolean,
             readonly ctrlKey: boolean,
             readonly embedKey: boolean
-        ) {}
+        ) {
+            /* empty */
+        }
     }
 
     // event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated
@@ -194,7 +194,7 @@ export namespace DragManager {
         userDropAction?: dropActionType;
     }
 
-    let defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
+    const defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
         if (de.complete.docDragData) {
             targetAction && (de.complete.docDragData.dropAction = targetAction);
             e.stopPropagation();
@@ -228,7 +228,7 @@ export namespace DragManager {
             return dropDoc;
         };
         const finishDrag = async (e: DragCompleteEvent) => {
-            const docDragData = e.docDragData;
+            const { docDragData } = e;
             setTimeout(() => dragData.draggedViews.forEach(view => view.props.dragEnding?.()));
             onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
             if (docDragData && !docDragData.droppedDocuments.length) {
@@ -256,7 +256,9 @@ export namespace DragManager {
                         .forEach((drop: Doc, i: number) => {
                             const dragProps = StrListCast(dragData.draggedDocuments[i].dropPropertiesToRemove);
                             const remProps = (dragData?.dropPropertiesToRemove || []).concat(Array.from(dragProps));
-                            [...remProps, 'dropPropertiesToRemove'].map(prop => (drop[prop] = undefined));
+                            [...remProps, 'dropPropertiesToRemove'].forEach(prop => {
+                                drop[prop] = undefined;
+                            });
                         });
             }
             return e;
@@ -268,15 +270,18 @@ export namespace DragManager {
     }
 
     // drag a button template and drop a new button
-    export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
+    export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: FieldType }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
         const finishDrag = (e: DragCompleteEvent) => {
             const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
-            params.map(p => Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
+            params.forEach(p => {
+                Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc));
+            }); // copy all "captured" arguments into document parameterfields
             initialize?.(bd);
             bd[DocData]['onClick-paramFieldKeys'] = new List<string>(params);
             e.docDragData && (e.docDragData.droppedDocuments = [bd]);
             return e;
         };
+        // eslint-disable-next-line no-param-reassign
         options = options ?? {};
         options.noAutoscroll = true; // these buttons are being dragged on the overlay layer, so scrollin the underlay is not appropriate
         StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
@@ -298,7 +303,7 @@ export namespace DragManager {
     }
 
     export function snapDragAspect(dragPt: number[], snapAspect: number) {
-        let closest = Utils.SNAP_THRESHOLD;
+        let closest = ClientUtils.SNAP_THRESHOLD;
         let near = dragPt;
         const intersect = (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, dragx: number, dragy: number) => {
             if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return undefined; // Check if none of the lines are of length 0
@@ -307,7 +312,7 @@ export namespace DragManager {
 
             const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
             // let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
-            //if (ua < 0 || ua > 1 || ub < 0 || ub > 1)  return undefined;  // is the intersection along the segments
+            // if (ua < 0 || ua > 1 || ub < 0 || ub > 1)  return undefined;  // is the intersection along the segments
 
             // Return a object with the x and y coordinates of the intersection
             const x = x1 + ua * (x2 - x1);
@@ -315,14 +320,14 @@ export namespace DragManager {
             const dist = Math.sqrt((dragx - x) * (dragx - x) + (dragy - y) * (dragy - y));
             return { pt: [x, y], dist };
         };
-        SnappingManager.VertSnapLines.forEach((xCoord, i) => {
+        SnappingManager.VertSnapLines.forEach(xCoord => {
             const pt = intersect(dragPt[0], dragPt[1], dragPt[0] + snapAspect, dragPt[1] + 1, xCoord, -1, xCoord, 1, dragPt[0], dragPt[1]);
             if (pt && pt.dist < closest) {
                 closest = pt.dist;
                 near = pt.pt;
             }
         });
-        SnappingManager.HorizSnapLines.forEach((yCoord, i) => {
+        SnappingManager.HorizSnapLines.forEach(yCoord => {
             const pt = intersect(dragPt[0], dragPt[1], dragPt[0] + snapAspect, dragPt[1] + 1, -1, yCoord, 1, yCoord, dragPt[0], dragPt[1]);
             if (pt && pt.dist < closest) {
                 closest = pt.dist;
@@ -333,7 +338,7 @@ export namespace DragManager {
     }
     // snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line
     export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) {
-        const snapThreshold = Utils.SNAP_THRESHOLD;
+        const snapThreshold = ClientUtils.SNAP_THRESHOLD;
         const snapVal = (pts: number[], drag: number, snapLines: number[]) => {
             if (snapLines.length) {
                 const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt
@@ -350,14 +355,33 @@ export namespace DragManager {
             y: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.HorizSnapLines),
         };
     }
-    export let docsBeingDragged: Doc[] = observable([]);
-    export let CanEmbed = false;
-    export let DocDragData: DocumentDragData | undefined;
-    export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?: string) {
+
+    async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
+        const dropArgs = {
+            cancelable: true, // allows preventDefault() to be called to cancel the drop
+            bubbles: true,
+            detail: {
+                ...pos,
+                complete,
+                shiftKey: e.shiftKey,
+                altKey: e.altKey,
+                metaKey: e.metaKey,
+                ctrlKey: e.ctrlKey,
+                embedKey: SnappingManager.CanEmbed,
+            },
+        };
+        target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
+        UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes)
+        await finishDrag?.(complete);
+        UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
+        options?.dragComplete?.(complete);
+        endDrag?.();
+    }
+    export function StartDrag(elesIn: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?: string) {
         if (dragData.dropAction === 'none' || SnappingManager.ExploreMode) return;
         DocDragData = dragData as DocumentDragData;
         const batch = UndoManager.StartBatch(dragUndoName ?? 'document drag');
-        eles = eles.filter(e => e);
+        const eles = elesIn.filter(e => e);
         SnappingManager.SetCanEmbed(dragData.canEmbed || false);
         if (!dragDiv) {
             dragDiv = document.createElement('div');
@@ -375,9 +399,9 @@ export namespace DragManager {
         }
         Object.assign(dragDiv.style, { width: '', height: '', overflow: '' });
         dragDiv.hidden = false;
-        const scalings: number[] = [],
-            xs: number[] = [],
-            ys: number[] = [];
+        const scalings: number[] = [];
+        const xs: number[] = [];
+        const ys: number[] = [];
 
         const elesCont = {
             left: Number.MAX_SAFE_INTEGER,
@@ -385,7 +409,7 @@ export namespace DragManager {
             top: Number.MAX_SAFE_INTEGER,
             bottom: Number.MIN_SAFE_INTEGER,
         };
-        let rot: number[] = [];
+        const rot: number[] = [];
         const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : [];
         const dragElements = eles.map(ele => {
             // bcz: very hacky -- if dragged element is a freeForm view with a rotation, then extract the rotation in order to apply it to the dragged element
@@ -471,7 +495,9 @@ export namespace DragManager {
                     .filter(pb => pb.width && pb.height)
                     .map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
             }
-            [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => (ele as any).style && ((ele as any).style.pointerEvents = 'none'));
+            [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => {
+                (ele as any).style && ((ele as any).style.pointerEvents = 'none');
+            });
 
             dragDiv.appendChild(dragElement);
             if (dragElement !== ele) {
@@ -493,7 +519,11 @@ export namespace DragManager {
         const hideDragShowOriginalElements = (hide: boolean) => {
             dragLabel.style.display = hide && !SnappingManager.CanEmbed ? '' : 'none';
             !hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
-            setTimeout(() => eles.forEach(ele => (ele.hidden = hide)));
+            setTimeout(() =>
+                eles.forEach(ele => {
+                    ele.hidden = hide;
+                })
+            );
         };
         options?.hideSource && hideDragShowOriginalElements(true);
 
@@ -505,22 +535,7 @@ export namespace DragManager {
         const yFromBottom = elesCont.bottom - downY;
         let scrollAwaiter: Opt<NodeJS.Timeout>;
 
-        AbortDrag = () => {
-            options?.dragComplete?.(new DragCompleteEvent(true, dragData));
-            cleanupDrag(true);
-        };
-
-        const cleanupDrag = action((undo: boolean) => {
-            (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.dragEnding?.());
-            hideDragShowOriginalElements(false);
-            document.removeEventListener('pointermove', moveHandler, true);
-            document.removeEventListener('pointerup', upHandler, true);
-            SnappingManager.SetIsDragging(false);
-            if (batch.end() && undo) UndoManager.Undo();
-            docsBeingDragged.length = 0;
-            SnappingManager.SetCanEmbed(false);
-        });
-        var startWindowDragTimer: any;
+        let startWindowDragTimer: any;
         const moveHandler = (e: PointerEvent) => {
             e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
             if (dragData instanceof DocumentDragData) {
@@ -580,10 +595,10 @@ export namespace DragManager {
                                 defaultPrevented: true,
                                 eventPhase: e.eventPhase,
                                 isTrusted: true,
-                                preventDefault: () => ('not implemented for this event' ? false : false),
-                                isDefaultPrevented: () => ('not implemented for this event' ? false : false),
-                                stopPropagation: () => ('not implemented for this event' ? false : false),
-                                isPropagationStopped: () => ('not implemented for this event' ? false : false),
+                                preventDefault: () => 'not implemented for this event' && false,
+                                isDefaultPrevented: () => 'not implemented for this event' && false,
+                                stopPropagation: () => 'not implemented for this event' && false,
+                                isPropagationStopped: () => 'not implemented for this event' && false,
                                 persist: emptyFunction,
                                 timeStamp: e.timeStamp,
                                 type: 'dashDragMovePause',
@@ -602,7 +617,9 @@ export namespace DragManager {
             const moveVec = { x: x - lastPt.x, y: y - lastPt.y };
             lastPt = { x, y };
 
-            dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot[i]}deg)  scale(${scalings[i]})`));
+            dragElements.forEach((dragElement, i) => {
+                dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot[i]}deg)  scale(${scalings[i]})`;
+            });
             dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`;
         };
         const upHandler = (e: PointerEvent) => {
@@ -610,36 +627,32 @@ export namespace DragManager {
             startWindowDragTimer = undefined;
             dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, () => cleanupDrag(false));
         };
+        const cleanupDrag = action((undo: boolean) => {
+            (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.dragEnding?.());
+            hideDragShowOriginalElements(false);
+            document.removeEventListener('pointermove', moveHandler, true);
+            document.removeEventListener('pointerup', upHandler, true);
+            SnappingManager.SetIsDragging(false);
+            if (batch.end() && undo) UndoManager.Undo();
+            docsBeingDragged.length = 0;
+            SnappingManager.SetCanEmbed(false);
+        });
+        AbortDrag = () => {
+            options?.dragComplete?.(new DragCompleteEvent(true, dragData));
+            cleanupDrag(true);
+        };
         document.addEventListener('pointermove', moveHandler, true);
         document.addEventListener('pointerup', upHandler, true);
     }
-
-    async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
-        const dropArgs = {
-            cancelable: true, // allows preventDefault() to be called to cancel the drop
-            bubbles: true,
-            detail: {
-                ...pos,
-                complete,
-                shiftKey: e.shiftKey,
-                altKey: e.altKey,
-                metaKey: e.metaKey,
-                ctrlKey: e.ctrlKey,
-                embedKey: SnappingManager.CanEmbed,
-            },
-        };
-        target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
-        UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes)
-        await finishDrag?.(complete);
-        UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
-        options?.dragComplete?.(complete);
-        endDrag?.();
-    }
 }
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) {
     if (readOnly) {
         return SelectionManager.Views.some(dv => dv.Document.keepZWhenDragged);
     }
-    SelectionManager.Views.map(dv => (dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged));
+    SelectionManager.Views.forEach(dv => {
+        dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged;
+    });
+    return undefined;
 });
diff --git a/src/client/util/DropActionTypes.ts b/src/client/util/DropActionTypes.ts
new file mode 100644
index 000000000..45b294d97
--- /dev/null
+++ b/src/client/util/DropActionTypes.ts
@@ -0,0 +1,9 @@
+export enum dropActionType {
+    embed = 'embed', // create a new embedding of the dragged document for the new location
+    copy = 'copy', // copy the dragged document
+    move = 'move', // move the dragged document to the drop location after removing it from where it was
+    add = 'add', // add the dragged document to the drop location without removing it from where it was
+    same = 'same', // only allow drop within same collection (or same hierarchical tree collection)
+    inPlace = 'inSame', // keep document in place (unless overridden by a drag modifier)
+    proto = 'proto',
+} // undefined = move, same = move but doesn't call dropPropertiesToRemove
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index f4f879208..6b20b885b 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -5,19 +5,20 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import Select from 'react-select';
 import * as RequestPromise from 'request-promise';
+import { ClientUtils } from '../../ClientUtils';
+import { Utils } from '../../Utils';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { listSpec } from '../../fields/Schema';
 import { Cast, StrCast } from '../../fields/Types';
-import { Utils } from '../../Utils';
 import { MainViewModal } from '../views/MainViewModal';
+import { ObservableReactComponent } from '../views/ObservableReactComponent';
 import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
 import './GroupManager.scss';
 import { GroupMemberView } from './GroupMemberView';
 import { SettingsManager } from './SettingsManager';
 import { SharingManager, User } from './SharingManager';
-import { ObservableReactComponent } from '../views/ObservableReactComponent';
 
 /**
  * Interface for options for the react-select component
@@ -55,7 +56,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
      */
     populateUsers = async () => {
         if (Doc.UserDoc()[Id] !== Utils.GuestID()) {
-            const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
+            const userList = await RequestPromise.get(ClientUtils.prepend('/getUsers'));
             const raw = JSON.parse(userList) as User[];
             raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
         }
@@ -135,7 +136,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
     hasEditAccess(groupDoc: Doc): boolean {
         if (!groupDoc) return false;
         const accessList: string[] = JSON.parse(StrCast(groupDoc.owners));
-        return accessList.includes(Doc.CurrentUserEmail) || this.adminGroupMembers?.includes(Doc.CurrentUserEmail);
+        return accessList.includes(ClientUtils.CurrentUserEmail) || this.adminGroupMembers?.includes(ClientUtils.CurrentUserEmail);
     }
 
     /**
@@ -147,7 +148,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
         const name = groupName.toLowerCase() === 'admin' ? 'Admin' : groupName;
         const groupDoc = new Doc('GROUP:' + name, true);
         groupDoc.title = name;
-        groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]);
+        groupDoc.owners = JSON.stringify([ClientUtils.CurrentUserEmail]);
         groupDoc.members = JSON.stringify(memberEmails);
         this.addGroup(groupDoc);
     }
@@ -176,11 +177,11 @@ export class GroupManager extends ObservableReactComponent<{}> {
                 Doc.RemoveDocFromList(this.GroupManagerDoc, 'data', group);
                 SharingManager.Instance.removeGroup(group);
                 const members = JSON.parse(StrCast(group.members));
-                if (members.includes(Doc.CurrentUserEmail)) {
+                if (members.includes(ClientUtils.CurrentUserEmail)) {
                     const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group);
                     index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1);
                 }
-                this.GroupManagerDoc['data_modificationDate'] = new DateField();
+                this.GroupManagerDoc.data_modificationDate = new DateField();
                 if (group === this.currentGroup) {
                     this.currentGroup = undefined;
                 }
@@ -260,7 +261,10 @@ export class GroupManager extends ObservableReactComponent<{}> {
             alert('Please select a unique group name');
             return;
         }
-        this.createGroupDoc(value, this.selectedUsers?.map(user => user.value));
+        this.createGroupDoc(
+            value,
+            this.selectedUsers?.map(user => user.value)
+        );
         this.selectedUsers = null;
         this.inputRef.current!.value = '';
         this.buttonColour = '#979797';
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 2f1a336cc..b500a5af9 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,6 +1,6 @@
 import * as qs from 'query-string';
 import { Doc } from '../../fields/Doc';
-import { OmitKeys, Utils } from '../../Utils';
+import { OmitKeys, ClientUtils } from '../../ClientUtils';
 import { DocServer } from '../DocServer';
 import { DashboardView } from '../views/DashboardView';
 
@@ -136,7 +136,7 @@ export namespace HistoryUtil {
 
     function addStringifier(type: string, keys: string[], customStringifier?: (state: ParsedUrl, current: string) => string) {
         stringifiers[type] = state => {
-            let path = Utils.prepend(`/${type}`);
+            let path = ClientUtils.prepend(`/${type}`);
             if (customStringifier) {
                 path = customStringifier(state, path);
             }
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index c5f307f44..d1600468a 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -1,5 +1,5 @@
 import { action, runInAction } from 'mobx';
-import { simulateMouseClick } from '../../Utils';
+import { simulateMouseClick } from '../../ClientUtils';
 import { Doc, Opt } from '../../fields/Doc';
 import { Cast, StrCast } from '../../fields/Types';
 import { WebField } from '../../fields/URLField';
@@ -33,7 +33,7 @@ export namespace Hypothesis {
         // await SearchUtil.Search('web', true).then(
         //     action(async (res: SearchUtil.DocSearchResult) => {
         //         const docs = res.docs;
-        //         const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data);
+        //         const filteredDocs = docs.filter(doc => doc.author === ClientUtils.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data);
         //         filteredDocs.forEach(doc => {
         //             uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history?
         //         });
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index d99828956..dfad9755c 100644
--- a/src/client/util/Import & Export/ImageUtils.ts	
+++ b/src/client/util/Import & Export/ImageUtils.ts	
@@ -1,10 +1,10 @@
+import { ClientUtils } from '../../../ClientUtils';
 import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
+import { Id } from '../../../fields/FieldSymbols';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { Cast, StrCast, NumCast } from '../../../fields/Types';
 import { Networking } from '../../Network';
-import { Id } from '../../../fields/FieldSymbols';
-import { Utils } from '../../../Utils';
-import { DocData } from '../../../fields/DocSymbols';
 
 export namespace ImageUtils {
     export type imgInfo = {
@@ -35,7 +35,7 @@ export namespace ImageUtils {
 
     export const ExportHierarchyToFileSystem = async (collection: Doc): Promise<void> => {
         const a = document.createElement('a');
-        a.href = Utils.prepend(`/imageHierarchyExport/${collection[Id]}`);
+        a.href = ClientUtils.prepend(`/imageHierarchyExport/${collection[Id]}`);
         a.download = `Dash Export [${StrCast(collection.title)}].zip`;
         a.click();
     };
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index a2f5826fe..a07550e09 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -1,6 +1,6 @@
 import * as React from 'react';
-import { GestureUtils } from '../../pen-gestures/GestureUtils';
 import { Utils } from '../../Utils';
+import { Gestures } from '../../pen-gestures/GestureTypes';
 import './InteractionUtils.scss';
 
 export namespace InteractionUtils {
@@ -9,81 +9,86 @@ export namespace InteractionUtils {
     export const PENTYPE = 'pen';
     export const ERASERTYPE = 'eraser';
 
-    const POINTER_PEN_BUTTON = -1;
-    const REACT_POINTER_PEN_BUTTON = 0;
     const ERASER_BUTTON = 5;
 
-    export class MultiTouchEvent<T extends React.TouchEvent | TouchEvent> {
-        constructor(
-            readonly fingers: number,
-            readonly targetTouches: T extends React.TouchEvent ? React.Touch[] : Touch[],
-            readonly touches: T extends React.TouchEvent ? React.Touch[] : Touch[],
-            readonly changedTouches: T extends React.TouchEvent ? React.Touch[] : Touch[],
-            readonly touchEvent: T extends React.TouchEvent ? React.TouchEvent : TouchEvent
-        ) {}
-    }
-
-    export interface MultiTouchEventDisposer {
-        (): void;
-    }
-
-    /**
-     *
-     * @param element - element to turn into a touch target
-     * @param startFunc - event handler, typically Touchable.onTouchStart (classes that inherit touchable can pass in this.onTouchStart)
-     */
-    export function MakeMultiTouchTarget(element: HTMLElement, startFunc: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void): MultiTouchEventDisposer {
-        const onMultiTouchStartHandler = (e: Event) => startFunc(e, (e as CustomEvent<MultiTouchEvent<React.TouchEvent>>).detail);
-        // const onMultiTouchMoveHandler = moveFunc ? (e: Event) => moveFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined;
-        // const onMultiTouchEndHandler = endFunc ? (e: Event) => endFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined;
-        element.addEventListener('dashOnTouchStart', onMultiTouchStartHandler);
-        // if (onMultiTouchMoveHandler) {
-        //     element.addEventListener("dashOnTouchMove", onMultiTouchMoveHandler);
-        // }
-        // if (onMultiTouchEndHandler) {
-        //     element.addEventListener("dashOnTouchEnd", onMultiTouchEndHandler);
-        // }
-        return () => {
-            element.removeEventListener('dashOnTouchStart', onMultiTouchStartHandler);
-            // if (onMultiTouchMoveHandler) {
-            //     element.removeEventListener("dashOnTouchMove", onMultiTouchMoveHandler);
-            // }
-            // if (onMultiTouchEndHandler) {
-            //     element.removeEventListener("dashOnTouchend", onMultiTouchEndHandler);
-            // }
-        };
-    }
-
-    /**
-     * Turns an element onto a target for touch hold handling.
-     * @param element - element to add events to
-     * @param func - function to add to the event
-     */
-    export function MakeHoldTouchTarget(element: HTMLElement, func: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void): MultiTouchEventDisposer {
-        const handler = (e: Event) => func(e, (e as CustomEvent<MultiTouchEvent<React.TouchEvent>>).detail);
-        element.addEventListener('dashOnTouchHoldStart', handler);
-        return () => {
-            element.removeEventListener('dashOnTouchHoldStart', handler);
-        };
-    }
-
-    export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent<React.TouchEvent | TouchEvent>, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] {
-        const myTouches = new Array<React.Touch>();
-        for (const pt of mte.touches) {
-            if (!ignorePen || ((pt as any).radiusX > 1 && (pt as any).radiusY > 1)) {
-                for (const tPt of mte.targetTouches) {
-                    if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
-                        if (pt && prevPoints.has(pt.identifier)) {
-                            myTouches.push(pt);
-                        }
-                    }
+    export function makePolygon(shape: string, points: { X: number; Y: number }[]) {
+        // if arrow/line/circle, the two end points should be the starting and the ending point
+        let left = points[0].X;
+        let top = points[0].Y;
+        let right = points[1].X;
+        let bottom = points[1].Y;
+        if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
+            // pointer is up (first and last points are the same)
+            if (![Gestures.Arrow, Gestures.Line, Gestures.Circle].includes(shape as any as Gestures)) {
+                // otherwise take max and min
+                const xs = points.map(p => p.X);
+                const ys = points.map(p => p.Y);
+                right = Math.max(...xs);
+                left = Math.min(...xs);
+                bottom = Math.max(...ys);
+                top = Math.min(...ys);
+            }
+        } else {
+            // if in the middle of drawing
+            // take first and last points
+            right = points[points.length - 1].X;
+            left = points[0].X;
+            bottom = points[points.length - 1].Y;
+            top = points[0].Y;
+            if (shape !== Gestures.Arrow && shape !== Gestures.Line && shape !== Gestures.Circle) {
+                // switch left/right and top/bottom if needed
+                if (left > right) {
+                    const temp = right;
+                    right = left;
+                    left = temp;
+                }
+                if (top > bottom) {
+                    const temp = top;
+                    top = bottom;
+                    bottom = temp;
                 }
             }
         }
-        // if (mte.touches.length !== myTouches.length) {
-        //     throw Error("opo")
-        // }
-        return myTouches;
+        const polyPts = [];
+        switch (shape) {
+            case Gestures.Rectangle:
+                polyPts.push({ X: left, Y: top });
+                polyPts.push({ X: right, Y: top });
+                polyPts.push({ X: right, Y: bottom });
+                polyPts.push({ X: left, Y: bottom });
+                polyPts.push({ X: left, Y: top });
+                break;
+            case Gestures.Triangle:
+                polyPts.push({ X: left, Y: bottom });
+                polyPts.push({ X: right, Y: bottom });
+                polyPts.push({ X: (right + left) / 2, Y: top });
+                polyPts.push({ X: left, Y: bottom });
+                break;
+            case Gestures.Circle:
+                {
+                    const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
+                    const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
+                    const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
+                    for (let x = centerX - radius; x < centerX + radius; x++) {
+                        const y = Math.sqrt(radius ** 2 - (x - centerX) ** 2) + centerY;
+                        polyPts.push({ X: x, Y: y });
+                    }
+                    for (let x = centerX + radius; x > centerX - radius; x--) {
+                        const y = Math.sqrt(radius ** 2 - (x - centerX) ** 2) + centerY;
+                        const newY = centerY - (y - centerY);
+                        polyPts.push({ X: x, Y: newY });
+                    }
+                    polyPts.push({ X: centerX - radius, Y: Math.sqrt(radius ** 2 - (-radius) ** 2) + centerY });
+                }
+                break;
+
+            case Gestures.Line:
+            default:
+                polyPts.push({ X: left, Y: top });
+                polyPts.push({ X: right, Y: bottom });
+                break;
+        }
+        return polyPts;
     }
 
     export function CreatePolyline(
@@ -101,20 +106,20 @@ export namespace InteractionUtils {
         arrowEnd: string,
         markerScale: number,
         dash: string | undefined,
-        scalex: number,
-        scaley: number,
+        scalexIn: number,
+        scaleyIn: number,
         shape: string,
         pevents: string,
         opacity: number,
         nodefs: boolean,
         downHdlr?: (e: React.PointerEvent) => void,
-        mask?: boolean,
-        dropshadow?: string
+        mask?: boolean
+        // dropshadow?: string
     ) {
         const pts = shape ? makePolygon(shape, points) : points;
 
-        if (isNaN(scalex)) scalex = 1;
-        if (isNaN(scaley)) scaley = 1;
+        const scalex = isNaN(scalexIn) ? 1 : scalexIn;
+        const scaley = isNaN(scaleyIn) ? 1 : scaleyIn;
 
         const toScr = (p: { X: number; Y: number }) => ` ${!p ? 0 : (p.X - left - width / 2) * scalex + width / 2}, ${!p ? 0 : (p.Y - top - width / 2) * scaley + width / 2} `;
         const strpts = bezier
@@ -137,7 +142,7 @@ export namespace InteractionUtils {
                     <defs>
                         {!mask ? null : (
                             <filter id={`mask${defGuid}`} x="-1" y="-1" width="500%" height="500%">
-                                <feGaussianBlur result="blurOut" in="offOut" stdDeviation="5"></feGaussianBlur>
+                                <feGaussianBlur result="blurOut" in="offOut" stdDeviation="5" />
                             </filter>
                         )}
                         {arrowStart !== 'dot' && arrowEnd !== 'dot' ? null : (
@@ -172,7 +177,7 @@ export namespace InteractionUtils {
                 <Tag
                     d={bezier ? strpts + (arrowStart || arrowEnd ? ' ' : '') : undefined}
                     points={bezier ? undefined : strpts}
-                    //filter={!dropshadow ? undefined : `drop-shadow(-1px -1px 0px ${dropshadow}) `}
+                    // filter={!dropshadow ? undefined : `drop-shadow(-1px -1px 0px ${dropshadow}) `}
                     style={{
                         // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
                         fill: fill && fill !== 'transparent' ? fill : 'none',
@@ -193,83 +198,6 @@ export namespace InteractionUtils {
         );
     }
 
-    export function makePolygon(shape: string, points: { X: number; Y: number }[]) {
-        if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
-            //pointer is up (first and last points are the same)
-            if (shape === GestureUtils.Gestures.Arrow || shape === GestureUtils.Gestures.Line || shape === GestureUtils.Gestures.Circle) {
-                //if arrow or line, the two end points should be the starting and the ending point
-                var left = points[0].X;
-                var top = points[0].Y;
-                var right = points[1].X;
-                var bottom = points[1].Y;
-            } else {
-                //otherwise take max and min
-                const xs = points.map(p => p.X);
-                const ys = points.map(p => p.Y);
-                right = Math.max(...xs);
-                left = Math.min(...xs);
-                bottom = Math.max(...ys);
-                top = Math.min(...ys);
-            }
-        } else {
-            //if in the middle of drawing
-            //take first and last points
-            right = points[points.length - 1].X;
-            left = points[0].X;
-            bottom = points[points.length - 1].Y;
-            top = points[0].Y;
-            if (shape !== GestureUtils.Gestures.Arrow && shape !== GestureUtils.Gestures.Line && shape !== GestureUtils.Gestures.Circle) {
-                //switch left/right and top/bottom if needed
-                if (left > right) {
-                    const temp = right;
-                    right = left;
-                    left = temp;
-                }
-                if (top > bottom) {
-                    const temp = top;
-                    top = bottom;
-                    bottom = temp;
-                }
-            }
-        }
-        points = [];
-        switch (shape) {
-            case GestureUtils.Gestures.Rectangle:
-                points.push({ X: left, Y: top });
-                points.push({ X: right, Y: top });
-                points.push({ X: right, Y: bottom });
-                points.push({ X: left, Y: bottom });
-                points.push({ X: left, Y: top });
-                break;
-            case GestureUtils.Gestures.Triangle:
-                points.push({ X: left, Y: bottom });
-                points.push({ X: right, Y: bottom });
-                points.push({ X: (right + left) / 2, Y: top });
-                points.push({ X: left, Y: bottom });
-                break;
-            case GestureUtils.Gestures.Circle:
-                const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
-                const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
-                const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
-                for (var x = centerX - radius; x < centerX + radius; x++) {
-                    const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY;
-                    points.push({ X: x, Y: y });
-                }
-                for (var x = centerX + radius; x > centerX - radius; x--) {
-                    const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY;
-                    const newY = centerY - (y - centerY);
-                    points.push({ X: x, Y: newY });
-                }
-                points.push({ X: centerX - radius, Y: Math.sqrt(Math.pow(radius, 2) - Math.pow(-radius, 2)) + centerY });
-                break;
-
-            case GestureUtils.Gestures.Line:
-                points.push({ X: left, Y: top });
-                points.push({ X: right, Y: bottom });
-                break;
-        }
-        return points;
-    }
     /**
      * Returns whether or not the pointer event passed in is of the type passed in
      * @param e - pointer event. this event could be from a mouse, a pen, or a finger
@@ -279,10 +207,11 @@ export namespace InteractionUtils {
         // prettier-ignore
         switch (type) {
             // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
-            case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
+            case PENTYPE:    return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
             case ERASERTYPE: return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
-            case TOUCHTYPE: return e.pointerType === TOUCHTYPE;
-        }
+            case TOUCHTYPE:  return e.pointerType === TOUCHTYPE;
+            default: 
+        } // prettier-ignore
         return e.pointerType === type;
     }
 
@@ -292,7 +221,7 @@ export namespace InteractionUtils {
      * @param pt2
      */
     export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number {
-        return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2));
+        return Math.sqrt((pt1.clientX - pt2.clientX) ** 2 + (pt1.clientY - pt2.clientY) ** 2);
     }
 
     /**
@@ -349,72 +278,4 @@ export namespace InteractionUtils {
         }
         return 0;
     }
-
-    export function IsDragging(oldTouches: Map<number, React.Touch>, newTouches: React.Touch[], leniency: number): boolean {
-        for (const touch of newTouches) {
-            if (touch) {
-                const oldTouch = oldTouches.get(touch.identifier);
-                if (oldTouch) {
-                    if (TwoPointEuclidist(touch, oldTouch) >= leniency) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    // These might not be very useful anymore, but I'll leave them here for now -syip2
-    {
-        /**
-         * Returns the type of Touch Interaction from a list of points.
-         * Also returns any data that is associated with a Touch Interaction
-         * @param pts - List of points
-         */
-        // export function InterpretPointers(pts: React.Touch[]): { type: Opt<TouchInteraction>, data?: any } {
-        //     const leniency = 200;
-        //     switch (pts.length) {
-        //         case 1:
-        //             return { type: OneFinger };
-        //         case 2:
-        //             return { type: TwoSeperateFingers };
-        //         case 3:
-        //             let pt1 = pts[0];
-        //             let pt2 = pts[1];
-        //             let pt3 = pts[2];
-        //             if (pt1 && pt2 && pt3) {
-        //                 let dist12 = TwoPointEuclidist(pt1, pt2);
-        //                 let dist23 = TwoPointEuclidist(pt2, pt3);
-        //                 let dist13 = TwoPointEuclidist(pt1, pt3);
-        //                 let dist12close = dist12 < leniency;
-        //                 let dist23close = dist23 < leniency;
-        //                 let dist13close = dist13 < leniency;
-        //                 let xor2313 = dist23close ? !dist13close : dist13close;
-        //                 let xor = dist12close ? !xor2313 : xor2313;
-        //                 // three input xor because javascript doesn't have logical xor's
-        //                 if (xor) {
-        //                     let points: number[] = [];
-        //                     let min = Math.min(dist12, dist23, dist13);
-        //                     switch (min) {
-        //                         case dist12:
-        //                             points = [0, 1, 2];
-        //                             break;
-        //                         case dist23:
-        //                             points = [1, 2, 0];
-        //                             break;
-        //                         case dist13:
-        //                             points = [0, 2, 1];
-        //                             break;
-        //                     }
-        //                     return { type: TwoToOneFingers, data: points };
-        //                 }
-        //                 else {
-        //                     return { type: ThreeSeperateFingers, data: null };
-        //                 }
-        //             }
-        //         default:
-        //             return { type: undefined };
-        //     }
-        // }
-    }
 }
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 6c0bf3242..ba715aa1f 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,5 +1,5 @@
 import { action, runInAction } from 'mobx';
-import { Doc, DocListCast, Field, FieldResult, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, FieldType, FieldResult, Opt } from '../../fields/Doc';
 import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
@@ -138,6 +138,6 @@ ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) {
 export function FollowLinkScript() {
     return ScriptField.MakeScript('return followLink(this,altKey)', { altKey: 'boolean' });
 }
-export function IsFollowLinkScript(field: FieldResult<Field>) {
+export function IsFollowLinkScript(field: FieldResult<FieldType>) {
     return ScriptCast(field)?.script.originalScript.includes('return followLink(');
 }
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 8972bf705..82cd791cc 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,12 +1,16 @@
-import { action, makeObservable, observable, observe, runInAction } from 'mobx';
+import { action, makeObservable, observable, observe } from 'mobx';
 import { computedFn } from 'mobx-utils';
-import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
+import * as rp from 'request-promise';
+import { ClientUtils } from '../../ClientUtils';
+import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from '../../fields/Doc';
 import { DirectLinks, DocData } from '../../fields/DocSymbols';
 import { FieldLoader } from '../../fields/FieldLoader';
+import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { ProxyField } from '../../fields/Proxy';
 import { Cast, DocCast, PromiseValue, StrCast } from '../../fields/Types';
 import { DocServer } from '../DocServer';
+import { DocumentType } from '../documents/DocumentTypes';
 import { ScriptingGlobals } from './ScriptingGlobals';
 /*
  * link doc:
@@ -45,6 +49,7 @@ export class LinkManager {
     constructor() {
         makeObservable(this);
         LinkManager._instance = this;
+        Doc.AddLink = this.addLink;
         this.createlink_relationshipLists();
         // since this is an action, not a reaction, we get only one shot to add this link to the Anchor docs
         // Thus make sure all promised values are resolved from link -> link.proto -> link.link_anchor_[1,2] -> link.link_anchor_[1,2].proto
@@ -84,7 +89,7 @@ export class LinkManager {
             );
 
         const watchUserLinkDB = (userLinkDBDoc: Doc) => {
-            const toRealField = (field: Field) => (field instanceof ProxyField ? field.value : field); // see List.ts.  data structure is not a simple list of Docs, but a list of ProxyField/Fields
+            const toRealField = (field: FieldType) => (field instanceof ProxyField ? field.value : field); // see List.ts.  data structure is not a simple list of Docs, but a list of ProxyField/Fields
             if (userLinkDBDoc.data) {
                 observe(
                     userLinkDBDoc.data,
@@ -148,7 +153,7 @@ export class LinkManager {
         Doc.AddDocToList(Doc.UserDoc(), 'links', linkDoc);
         if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
             Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc);
-            setTimeout(DocServer.UPDATE_SERVER_CACHE, 100);
+            setTimeout(UPDATE_SERVER_CACHE, 100);
         }
     }
     public deleteLink(linkDoc: Doc) {
@@ -240,6 +245,42 @@ export class LinkManager {
     }
 }
 
+let cacheDocumentIds = ''; // ; separate string of all documents ids in the user's working set (cached on the server)
+export function UPDATE_SERVER_CACHE() {
+    const prototypes = Object.values(DocumentType)
+        .filter(type => type !== DocumentType.NONE)
+        .map(type => DocServer._cache[type + 'Proto'])
+        .filter(doc => doc instanceof Doc)
+        .map(doc => doc as Doc);
+    const references = new Set<Doc>(prototypes);
+    Doc.FindReferences(Doc.UserDoc(), references, undefined);
+    DocListCast(DocCast(Doc.UserDoc().myLinkDatabase).data).forEach(link => {
+        if (!references.has(DocCast(link.link_anchor_1)) && !references.has(DocCast(link.link_anchor_2))) {
+            Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase), 'data', link);
+            Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
+        }
+    });
+    LinkManager.Instance.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
+    const filtered = Array.from(references);
+
+    const newCacheUpdate = filtered.map(doc => doc[Id]).join(';');
+    if (newCacheUpdate === cacheDocumentIds) return;
+    cacheDocumentIds = newCacheUpdate;
+
+    // print out cached docs
+    Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = ');
+    const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc));
+    const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
+    Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
+
+    rp.post(ClientUtils.prepend('/setCacheDocumentIds'), {
+        body: {
+            cacheDocumentIds,
+        },
+        json: true,
+    });
+}
+
 ScriptingGlobals.add(
     function links(doc: any) {
         return new List(LinkManager.Links(doc));
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 422e708bc..de5e8b92e 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -4,12 +4,14 @@
 // // @ts-ignore
 // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
 // @ts-ignore
-import * as typescriptlib from '!!raw-loader!./type_decls.d';
+// eslint-disable-next-line node/no-unpublished-import
 import * as ts from 'typescript';
-import { Doc, Field } from '../../fields/Doc';
+import * as typescriptlib from '!!raw-loader!./type_decls.d';
+import { Doc, FieldType } from '../../fields/Doc';
 import { RefField } from '../../fields/RefField';
 import { ScriptField } from '../../fields/ScriptField';
-import { scriptingGlobals, ScriptingGlobals } from './ScriptingGlobals';
+import { ScriptingGlobals, scriptingGlobals } from './ScriptingGlobals';
+
 export { ts };
 
 export interface ScriptSuccess {
@@ -30,6 +32,7 @@ export type ScriptParam = { [name: string]: string };
 export interface CompiledScript {
     readonly compiled: true;
     readonly originalScript: string;
+    // eslint-disable-next-line no-use-before-define
     readonly options: Readonly<ScriptOptions>;
     run(args?: { [name: string]: any }, onError?: (res: any) => void, errorVal?: any): ScriptResult;
 }
@@ -47,6 +50,7 @@ export function isCompileError(toBeDetermined: CompileResult): toBeDetermined is
     return false;
 }
 
+// eslint-disable-next-line no-use-before-define
 function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
     const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error);
     if ((options.typecheck !== false && errors.length) || !script) {
@@ -60,6 +64,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
     // let params: any[] = [Docs, ...fieldTypes];
     const compiledFunction = (() => {
         try {
+            // eslint-disable-next-line no-new-func
             return new Function(...paramNames, `return ${script}`);
         } catch (e) {
             console.log(e);
@@ -68,20 +73,17 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
     })();
     if (!compiledFunction) return { compiled: false, errors };
     const { capturedVariables = {} } = options;
+    // eslint-disable-next-line default-param-last
     const run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => {
         const argsArray: any[] = [];
+        // eslint-disable-next-line no-restricted-syntax
         for (const name of customParams) {
-            if (name === 'this') {
-                continue;
-            }
-            if (name in args) {
-                argsArray.push(args[name]);
-            } else {
-                argsArray.push(capturedVariables[name]);
+            if (name !== 'this') {
+                argsArray.push(name in args ? args[name] : capturedVariables[name]);
             }
         }
         const thisParam = args.this || capturedVariables.this;
-        let batch: { end(): void } | undefined = undefined;
+        let batch: { end(): void } | undefined;
         try {
             if (!options.editable) {
                 batch = Doc.MakeReadOnly();
@@ -109,7 +111,7 @@ class ScriptingCompilerHost {
     files: File[] = [];
 
     // getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): ts.SourceFile | undefined {
-    getSourceFile(fileName: string, languageVersion: any, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): any | undefined {
+    getSourceFile(fileName: string, languageVersion: any /* , onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined */): any | undefined {
         const contents = this.readFile(fileName);
         if (contents !== undefined) {
             return ts.createSourceFile(fileName, contents, languageVersion, true);
@@ -118,7 +120,7 @@ class ScriptingCompilerHost {
     }
 
     // getDefaultLibFileName(options: ts.CompilerOptions): string {
-    getDefaultLibFileName(options: any): string {
+    getDefaultLibFileName(/* options: any */): string {
         return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
     }
     writeFile(fileName: string, content: string) {
@@ -157,7 +159,7 @@ export type Traverser = (node: ts.Node, indentation: string) => boolean | void;
 export type TraverserParam = Traverser | { onEnter: Traverser; onLeave: Traverser };
 export type Transformer = {
     transformer: ts.TransformerFactory<ts.Node>;
-    getVars?: () => { [name: string]: Field };
+    getVars?: () => { [name: string]: FieldType };
 };
 export interface ScriptOptions {
     requiredType?: string; // does function required a typed return value
@@ -210,12 +212,13 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
         const newCaptures = options.transformer.getVars?.();
         if (Object.keys(newCaptures ?? {}).length) {
             // tslint:disable-next-line: prefer-object-spread
-            //options.capturedVariables = Object.assign(capturedVariables, newCaptures!) as any;
+            // options.capturedVariables = Object.assign(capturedVariables, newCaptures!) as any;
 
-            const transformed = result.transformed;
+            const { transformed } = result;
             const printer = ts.createPrinter({
                 newLine: ts.NewLineKind.LineFeed,
             });
+            // eslint-disable-next-line no-param-reassign
             script = printer.printFile(transformed[0].getSourceFile());
         }
         result.dispose();
@@ -224,19 +227,23 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
     if ('this' in params || 'this' in capturedVariables) {
         paramNames.push('this');
     }
+    // eslint-disable-next-line no-restricted-syntax
     for (const key in params) {
-        if (key === 'this') continue;
-        paramNames.push(key);
+        if (key !== 'this') {
+            paramNames.push(key);
+        }
     }
     const paramList = paramNames.map(key => {
         const val = params[key];
         return `${key}: ${val}`;
     });
+    // eslint-disable-next-line no-restricted-syntax
     for (const key in capturedVariables) {
-        if (key === 'this') continue;
-        const val = capturedVariables[key];
-        paramNames.push(key);
-        paramList.push(`${key}: ${typeof val === 'object' ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
+        if (key !== 'this') {
+            const val = capturedVariables[key];
+            paramNames.push(key);
+            paramList.push(`${key}: ${typeof val === 'object' ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
+        }
     }
     const paramString = paramList.join(', ');
     const body = addReturn && !script.startsWith('{ return') ? `return ${script};` : script;
@@ -261,6 +268,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
 }
 
 ScriptingGlobals.add(CompileScript);
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function runScript(doc: Doc, script: ScriptField) {
     return script?.script.run({ this: doc }).result;
 });
diff --git a/src/client/util/ScriptingGlobals.ts b/src/client/util/ScriptingGlobals.ts
index f151acd81..bc159ed65 100644
--- a/src/client/util/ScriptingGlobals.ts
+++ b/src/client/util/ScriptingGlobals.ts
@@ -1,8 +1,18 @@
+import ts from 'typescript';
 
-import * as ts from "typescript";
 export { ts };
 
+const _scriptingGlobals: { [name: string]: any } = {};
+const _scriptingDescriptions: { [name: string]: any } = {};
+const _scriptingParams: { [name: string]: any } = {};
+// eslint-disable-next-line import/no-mutable-exports
+export let scriptingGlobals: { [name: string]: any } = _scriptingGlobals;
 export namespace ScriptingGlobals {
+    export function getGlobals()      { return Object.keys(_scriptingGlobals); } // prettier-ignore
+    export function getGlobalObj()    { return _scriptingGlobals; } // prettier-ignore
+    export function getDescriptions() { return _scriptingDescriptions; } // prettier-ignore
+    export function getParameters()   { return _scriptingParams; } // prettier-ignore
+
     export function add(global: { name: string }): void;
     export function add(name: string, global: any): void;
     export function add(global: { name: string }, decription?: string, params?: string): void;
@@ -11,7 +21,7 @@ export namespace ScriptingGlobals {
         let obj: any;
 
         if (second !== undefined) {
-            if (typeof first === "string") {
+            if (typeof first === 'string') {
                 n = first;
                 obj = second;
             } else {
@@ -22,18 +32,20 @@ export namespace ScriptingGlobals {
                     _scriptingParams[n] = third;
                 }
             }
-        } else if (first && typeof first.name === "string") {
+        } else if (first && typeof first.name === 'string') {
             n = first.name;
             obj = first;
         } else {
-            throw new Error("Must either register an object with a name, or give a name and an object");
+            throw new Error('Must either register an object with a name, or give a name and an object');
         }
-        if (n === undefined || n === "undefined") {
+        if (n === undefined || n === 'undefined') {
             return false;
-        } else if (_scriptingGlobals.hasOwnProperty(n)) {
+        }
+        if (_scriptingGlobals.hasOwnProperty(n)) {
             throw new Error(`Global with name ${n} is already registered, choose another name`);
         }
         _scriptingGlobals[n] = obj;
+        return true;
     }
     export function makeMutableGlobalsCopy(globals?: { [name: string]: any }) {
         return { ..._scriptingGlobals, ...(globals || {}) };
@@ -57,25 +69,16 @@ export namespace ScriptingGlobals {
         return false;
     }
 
-    export function resetScriptingGlobals() { scriptingGlobals = _scriptingGlobals; }
+    export function resetScriptingGlobals() {
+        scriptingGlobals = _scriptingGlobals;
+    }
 
     // const types = Object.keys(ts.SyntaxKind).map(kind => ts.SyntaxKind[kind]);
-    export function printNodeType(node: any, indentation = "") { console.log(indentation + ts.SyntaxKind[node.kind]); }
-
-    export function getGlobals() { return Object.keys(_scriptingGlobals); }
-
-    export function getGlobalObj() { return _scriptingGlobals; }
-
-    export function getDescriptions() { return _scriptingDescriptions; }
-
-    export function getParameters() { return _scriptingParams; }
+    export function printNodeType(node: any, indentation = '') {
+        console.log(indentation + ts.SyntaxKind[node.kind]);
+    }
 }
 
-export function scriptingGlobal(constructor: { new(...args: any[]): any }) {
+export function scriptingGlobal(constructor: { new (...args: any[]): any }) {
     ScriptingGlobals.add(constructor);
 }
-
-const _scriptingGlobals: { [name: string]: any } = {};
-export let scriptingGlobals: { [name: string]: any } = _scriptingGlobals;
-const _scriptingDescriptions: { [name: string]: any } = {};
-const _scriptingParams: { [name: string]: any } = {};
\ No newline at end of file
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index fff2737b6..65b9a977d 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -1,5 +1,5 @@
 import { ObservableMap } from 'mobx';
-import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { StrCast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
@@ -30,7 +30,7 @@ export namespace SearchUtil {
                     (onlyKeys ?? SearchUtil.documentKeys(doc)).forEach(
                         key =>
                             (val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))(
-                            matchKeyNames ? key : Field.toString(doc[key] as Field)) 
+                            matchKeyNames ? key : Field.toString(doc[key] as FieldType)) 
                         && hlights.add(key)
                     ); // prettier-ignore
                     blockedKeys.forEach(key => hlights.delete(key));
@@ -71,7 +71,7 @@ export namespace SearchUtil {
             docs.filter(d => d && !visited.includes(d)).forEach(d => {
                 visited.push(d);
                 const fieldKey = Doc.LayoutFieldKey(d);
-                const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
+                const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView');
                 const data = d[annos ? fieldKey + '_annotations' : fieldKey];
                 data && newarray.push(...DocListCast(data));
                 const sidebar = d[fieldKey + '_sidebar'];
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 8daa69890..fa1a911f7 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,5 +1,5 @@
 import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from 'serializr';
-import { Field } from '../../fields/Doc';
+// import { Field } from '../../fields/Doc';
 
 let serializing = 0;
 export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) {
@@ -7,12 +7,16 @@ export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any,
     cb(err, newValue);
     serializing--;
 }
+
+const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
+const reverseMap: { [ctor: string]: string } = {};
+
 export namespace SerializationHelper {
     export function IsSerializing() {
         return serializing > 0;
     }
 
-    export function Serialize(obj: Field): any {
+    export function Serialize(obj: any /* Field */): any {
         if (obj === undefined || obj === null) {
             return null;
         }
@@ -24,7 +28,7 @@ export namespace SerializationHelper {
         serializing++;
         if (!(obj.constructor.name in reverseMap)) {
             serializing--;
-            throw Error('Error: ' + `type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
+            throw Error(`Error: type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
         }
 
         const json = serialize(obj);
@@ -52,25 +56,24 @@ export namespace SerializationHelper {
         }
 
         const type = serializationTypes[obj.__type];
-        const value = await new Promise(res => deserialize(type.ctor, obj, (err, result) => res(result)));
+        const value = await new Promise(res => {
+            deserialize(type.ctor, obj, (err, result) => res(result));
+        });
         type.afterDeserialize?.(value);
 
         return value;
     }
 }
 
-const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
-const reverseMap: { [ctor: string]: string } = {};
-
 export function Deserializable(className: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void {
-    function addToMap(className: string, ctor: { new (...args: any[]): any }) {
-        const schema = getDefaultModelSchema(ctor) as any;
-        if (schema.targetClass !== ctor || constructorArgs) {
-            setDefaultModelSchema(ctor, { ...schema, factory: (context: any) => new ctor(...(constructorArgs ?? [])?.map(arg => context.json[arg])) });
+    function addToMap(className: string, Ctor: { new (...args: any[]): any }) {
+        const schema = getDefaultModelSchema(Ctor) as any;
+        if (schema.targetClass !== Ctor || constructorArgs) {
+            setDefaultModelSchema(Ctor, { ...schema, factory: (context: any) => new Ctor(...(constructorArgs ?? []).map(arg => context.json[arg])) });
         }
         if (!(className in serializationTypes)) {
-            serializationTypes[className] = { ctor, afterDeserialize };
-            reverseMap[ctor.name] = className;
+            serializationTypes[className] = { ctor: Ctor, afterDeserialize };
+            reverseMap[Ctor.name] = className;
         } else {
             throw new Error(`Name ${className} has already been registered as deserializable`);
         }
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 8594a1c92..e2971895a 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,11 +1,11 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { BsGoogle } from 'react-icons/bs';
 import { FaFillDrip, FaPalette } from 'react-icons/fa';
-import { Utils, addStyleSheet, addStyleSheetRule } from '../../Utils';
+import { ClientUtils, addStyleSheet, addStyleSheetRule } from '../../ClientUtils';
 import { Doc, Opt } from '../../fields/Doc';
 import { DashVersion } from '../../fields/DocSymbols';
 import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types';
@@ -18,6 +18,7 @@ import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
 import { GroupManager } from './GroupManager';
 import './SettingsManager.scss';
 import { undoBatch } from './UndoManager';
+import { SnappingManager } from './SnappingManager';
 
 export enum ColorScheme {
     Dark = 'Dark',
@@ -34,6 +35,7 @@ export enum freeformScrollMode {
 
 @observer
 export class SettingsManager extends React.Component<{}> {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: SettingsManager;
     static _settingsStyle = addStyleSheet();
     @observable public isOpen = false;
@@ -60,6 +62,16 @@ export class SettingsManager extends React.Component<{}> {
             }
             // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
         });
+        reaction(
+            () => [SettingsManager.userBackgroundColor, SettingsManager.userColor, SettingsManager.userVariantColor],
+            ([back, user, variant]) => {
+                SnappingManager.userBackgroundColor = back;
+                SnappingManager.userVariantColor = variant;
+                SnappingManager.userColor = user;
+            },
+            { fireImmediately: true }
+        );
+        SnappingManager.SettingsStyle = SettingsManager._settingsStyle;
     }
     matchSystem = () => {
         if (Doc.UserDoc().userThemeSystem) {
@@ -116,7 +128,7 @@ export class SettingsManager extends React.Component<{}> {
         if (this.playgroundMode) {
             DocServer.Control.makeReadOnly();
             addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
-        } else Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
+        } else ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
     });
 
     @undoBatch
@@ -176,7 +188,7 @@ export class SettingsManager extends React.Component<{}> {
                 {userTheme === ColorScheme.Custom && (
                     <Group formLabel="Custom Theme">
                         <ColorPicker
-                            tooltip={'User Color'} //
+                            tooltip="User Color" //
                             color={SettingsManager.userColor}
                             type={Type.SEC}
                             icon={<FaFillDrip />}
@@ -185,7 +197,7 @@ export class SettingsManager extends React.Component<{}> {
                             setFinalColor={this.switchUserColor}
                         />
                         <ColorPicker
-                            tooltip={'User Background Color'}
+                            tooltip="User Background Color"
                             color={SettingsManager.userColor}
                             type={Type.SEC}
                             icon={<FaPalette />}
@@ -194,7 +206,7 @@ export class SettingsManager extends React.Component<{}> {
                             setFinalColor={this.switchUserBackgroundColor}
                         />
                         <ColorPicker
-                            tooltip={'User Variant Color'}
+                            tooltip="User Variant Color"
                             color={SettingsManager.userColor}
                             type={Type.SEC}
                             icon={<FaPalette />}
@@ -212,8 +224,8 @@ export class SettingsManager extends React.Component<{}> {
         return (
             <div className="prefs-content">
                 <Toggle
-                    formLabel={'Show document header'}
-                    formLabelPlacement={'right'}
+                    formLabel="Show document header"
+                    formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
                     onClick={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')}
                     toggleStatus={Doc.UserDoc().layout_showTitle !== undefined}
@@ -221,8 +233,8 @@ export class SettingsManager extends React.Component<{}> {
                     color={SettingsManager.userColor}
                 />
                 <Toggle
-                    formLabel={'Show Full Toolbar'}
-                    formLabelPlacement={'right'}
+                    formLabel="Show Full Toolbar"
+                    formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
                     onClick={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])}
                     toggleStatus={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])}
@@ -230,8 +242,8 @@ export class SettingsManager extends React.Component<{}> {
                     color={SettingsManager.userColor}
                 />
                 <Toggle
-                    formLabel={'Show Button Labels'}
-                    formLabelPlacement={'right'}
+                    formLabel="Show Button Labels"
+                    formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
                     onClick={e => (FontIconBox.ShowIconLabels = !FontIconBox.ShowIconLabels)}
                     toggleStatus={FontIconBox.ShowIconLabels}
@@ -239,8 +251,8 @@ export class SettingsManager extends React.Component<{}> {
                     color={SettingsManager.userColor}
                 />
                 <Toggle
-                    formLabel={'Recognize Ink Gestures'}
-                    formLabelPlacement={'right'}
+                    formLabel="Recognize Ink Gestures"
+                    formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
                     onClick={e => (GestureOverlay.RecognizeGestures = !GestureOverlay.RecognizeGestures)}
                     toggleStatus={GestureOverlay.RecognizeGestures}
@@ -248,8 +260,8 @@ export class SettingsManager extends React.Component<{}> {
                     color={SettingsManager.userColor}
                 />
                 <Toggle
-                    formLabel={'Hide Labels In Ink Shapes'}
-                    formLabelPlacement={'right'}
+                    formLabel="Hide Labels In Ink Shapes"
+                    formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
                     onClick={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)}
                     toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)}
@@ -257,8 +269,8 @@ export class SettingsManager extends React.Component<{}> {
                     color={SettingsManager.userColor}
                 />
                 <Toggle
-                    formLabel={'Open Ink Docs in Lightbox'}
-                    formLabelPlacement={'right'}
+                    formLabel="Open Ink Docs in Lightbox"
+                    formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
                     onClick={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)}
                     toggleStatus={BoolCast(Doc.UserDoc().openInkInLightbox)}
@@ -266,8 +278,8 @@ export class SettingsManager extends React.Component<{}> {
                     color={SettingsManager.userColor}
                 />
                 <Toggle
-                    formLabel={'Show Link Lines'}
-                    formLabelPlacement={'right'}
+                    formLabel="Show Link Lines"
+                    formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
                     onClick={e => (Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines)}
                     toggleStatus={BoolCast(Doc.UserDoc().showLinkLines)}
@@ -278,12 +290,12 @@ export class SettingsManager extends React.Component<{}> {
                     <NumberDropdown
                         number={NumCast(Doc.UserDoc().headerHeight, 30)}
                         color={SettingsManager.userColor}
-                        numberDropdownType={'slider'}
+                        numberDropdownType="slider"
                         min={6}
                         max={60}
                         step={2}
                         type={Type.TERT}
-                        unit={'px'}
+                        unit="px"
                         setNumber={val => console.log('GOT: ' + (Doc.UserDoc().headerHeight = val))}
                     />
                 </Group>
@@ -316,7 +328,7 @@ export class SettingsManager extends React.Component<{}> {
                     <div className="tab-column-title">Text</div>
                     <div className="tab-column-content">
                         {/* <NumberInput/> */}
-                        <Group formLabel={'Default Font'}>
+                        <Group formLabel="Default Font">
                             <NumberDropdown
                                 color={SettingsManager.userColor}
                                 numberDropdownType="slider"
@@ -325,7 +337,7 @@ export class SettingsManager extends React.Component<{}> {
                                 step={2}
                                 type={Type.PRIM}
                                 number={NumCast(Doc.UserDoc().fontSize, Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')))}
-                                unit={'px'}
+                                unit="px"
                                 setNumber={val => (Doc.UserDoc().fontSize = val + 'px')}
                             />
                             <Dropdown
@@ -373,12 +385,12 @@ export class SettingsManager extends React.Component<{}> {
     @computed get passwordContent() {
         return (
             <div className="password-content">
-                <EditableText placeholder="Current password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'curr')} fillWidth password />
-                <EditableText placeholder="New password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'new')} fillWidth password />
-                <EditableText placeholder="Confirm new password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'conf')} fillWidth password />
+                <EditableText placeholder="Current password" type={Type.SEC} color={SettingsManager.userColor} val="" setVal={val => this.changeVal(val as string, 'curr')} fillWidth password />
+                <EditableText placeholder="New password" type={Type.SEC} color={SettingsManager.userColor} val="" setVal={val => this.changeVal(val as string, 'new')} fillWidth password />
+                <EditableText placeholder="Confirm new password" type={Type.SEC} color={SettingsManager.userColor} val="" setVal={val => this.changeVal(val as string, 'conf')} fillWidth password />
                 {!this.passwordResultText ? null : <div className={`${this.passwordResultText.startsWith('Error') ? 'error' : 'success'}-text`}>{this.passwordResultText}</div>}
-                <Button type={Type.SEC} text={'Forgot Password'} color={SettingsManager.userColor} />
-                <Button type={Type.TERT} text={'Submit'} onClick={this.changePassword} color={SettingsManager.userColor} />
+                <Button type={Type.SEC} text="Forgot Password" color={SettingsManager.userColor} />
+                <Button type={Type.TERT} text="Submit" onClick={this.changePassword} color={SettingsManager.userColor} />
             </div>
         );
     }
@@ -386,7 +398,7 @@ export class SettingsManager extends React.Component<{}> {
     @computed get accountOthersContent() {
         return (
             <div className="account-others-content">
-                <Button type={Type.TERT} text={'Connect to Google'} iconPlacement="left" icon={<BsGoogle />} onClick={() => this.googleAuthorize()} />
+                <Button type={Type.TERT} text="Connect to Google" iconPlacement="left" icon={<BsGoogle />} onClick={() => this.googleAuthorize()} />
             </div>
         );
     }
@@ -417,7 +429,7 @@ export class SettingsManager extends React.Component<{}> {
                     <div className="tab-column-title">Modes</div>
                     <div className="tab-column-content">
                         <Dropdown
-                            formLabel={'Mode'}
+                            formLabel="Mode"
                             closeOnSelect={true}
                             items={[
                                 {
@@ -442,7 +454,7 @@ export class SettingsManager extends React.Component<{}> {
                             color={SettingsManager.userColor}
                             fillWidth
                         />
-                        <Toggle formLabel={'Playground Mode'} toggleType={ToggleType.SWITCH} toggleStatus={this.playgroundMode} onClick={this.playgroundModeToggle} color={SettingsManager.userColor} />
+                        <Toggle formLabel="Playground Mode" toggleType={ToggleType.SWITCH} toggleStatus={this.playgroundMode} onClick={this.playgroundModeToggle} color={SettingsManager.userColor} />
                     </div>
                     <div className="tab-column-title" style={{ marginTop: 20, marginBottom: 10 }}>
                         Freeform Navigation
@@ -475,10 +487,10 @@ export class SettingsManager extends React.Component<{}> {
                 <div className="tab-column">
                     <div className="tab-column-title">Permissions</div>
                     <div className="tab-column-content">
-                        <Button text={'Manage Groups'} type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={SettingsManager.userColor} />
+                        <Button text="Manage Groups" type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={SettingsManager.userColor} />
                         <Toggle
                             toggleType={ToggleType.SWITCH}
-                            formLabel={'Default access private'}
+                            formLabel="Default access private"
                             color={SettingsManager.userColor}
                             toggleStatus={BoolCast(Doc.defaultAclPrivate)}
                             onClick={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))}
@@ -525,14 +537,14 @@ export class SettingsManager extends React.Component<{}> {
                     <div className="settings-user">
                         <div style={{ color: SettingsManager.userBackgroundColor }}>{DashVersion}</div>
                         <div className="settings-username" style={{ color: SettingsManager.userBackgroundColor }}>
-                            {Doc.CurrentUserEmail}
+                            {ClientUtils.CurrentUserEmail}
                         </div>
-                        <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.userVariantColor} onClick={() => window.location.assign(Utils.prepend('/logout'))} />
+                        <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.userVariantColor} onClick={() => window.location.assign(ClientUtils.prepend('/logout'))} />
                     </div>
                 </div>
 
                 <div className="close-button">
-                    <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={SettingsManager.userColor} />
+                    <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.close} color={SettingsManager.userColor} />
                 </div>
 
                 <div className="settings-content" style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}>
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index fddf735e3..03f7e9b71 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -6,13 +6,14 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import Select from 'react-select';
 import * as RequestPromise from 'request-promise';
+import { ClientUtils } from '../../ClientUtils';
+import { Utils } from '../../Utils';
 import { Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
 import { AclAdmin, AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols';
 import { FieldLoader } from '../../fields/FieldLoader';
 import { Id } from '../../fields/FieldSymbols';
 import { StrCast } from '../../fields/Types';
-import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util';
-import { Utils } from '../../Utils';
+import { GetEffectiveAcl, SharingPermissions, TraceMobx, distributeAcls, normalizeEmail } from '../../fields/util';
 import { DocServer } from '../DocServer';
 import { DictationOverlay } from '../views/DictationOverlay';
 import { MainViewModal } from '../views/MainViewModal';
@@ -84,7 +85,6 @@ export class SharingManager extends React.Component<{}> {
     @observable private upgradeNested: boolean = false; // whether child docs in a collection/dashboard should be changed to be less private - initially selected so default is upgrade all
     @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
     @observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not
-    @observable private _buttonDown = false;
 
     // private get linkVisible() {
     //     return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
@@ -136,9 +136,11 @@ export class SharingManager extends React.Component<{}> {
     populateUsers = async () => {
         if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) {
             this.populating = true;
-            const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
-            const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== Doc.CurrentUserEmail);
-            runInAction(() => (FieldLoader.ServerLoadStatus.message = 'users'));
+            const userList = await RequestPromise.get(ClientUtils.prepend('/getUsers'));
+            const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== ClientUtils.CurrentUserEmail);
+            runInAction(() => {
+                FieldLoader.ServerLoadStatus.message = 'users';
+            });
             const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[]));
             raw.map(
                 action((newUser: User) => {
@@ -191,7 +193,8 @@ export class SharingManager extends React.Component<{}> {
                 this.users
                     .filter(({ user: { email } }) => JSON.parse(StrCast(group.members)).includes(email))
                     .forEach(({ user, sharingDoc }) => {
-                        if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); // add the doc to the sharingDoc if it hasn't already been added
+                        if (permission !== SharingPermissions.None)
+                            Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); // add the doc to the sharingDoc if it hasn't already been added
                         else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists
                     });
             }
@@ -231,7 +234,7 @@ export class SharingManager extends React.Component<{}> {
     shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
         if (layout) this.layoutDocAcls = true;
         if (shareWith !== 'Guest') {
-            const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith));
+            const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? ClientUtils.CurrentUserEmail : shareWith));
             docs.forEach(doc => {
                 if (user) this.setInternalSharing(user, permission, doc);
                 else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true);
@@ -300,7 +303,7 @@ export class SharingManager extends React.Component<{}> {
      * Copies the Public sharing url to the user's clipboard.
      */
     private copyURL = (e: any) => {
-        Utils.CopyText(Utils.shareUrl(this.targetDoc![Id]));
+        ClientUtils.CopyText(ClientUtils.shareUrl(this.targetDoc![Id]));
     };
 
     /**
@@ -496,19 +499,19 @@ export class SharingManager extends React.Component<{}> {
         const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author);
 
         // the owner of the doc and the current user are placed at the top of the user list.
-        const userKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
+        const userKey = `acl-${normalizeEmail(ClientUtils.CurrentUserEmail)}`;
         const curUserPermission = StrCast(targetDoc[userKey]);
         // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name
         userListContents.unshift(
             sameAuthor ? (
                 <div key={'owner'} className={'container'}>
-                    <span className="padding">{targetDoc?.author === Doc.CurrentUserEmail ? 'Me' : StrCast(targetDoc?.author)}</span>
+                    <span className="padding">{targetDoc?.author === ClientUtils.CurrentUserEmail ? 'Me' : StrCast(targetDoc?.author)}</span>
                     <div className="edit-actions">
                         <div className={'permissions-dropdown'}>Owner</div>
                     </div>
                 </div>
             ) : null,
-            sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? (
+            sameAuthor && targetDoc?.author !== ClientUtils.CurrentUserEmail ? (
                 <div key={'me'} className={'container'}>
                     <span className={'padding'}>Me</span>
                     <div className="edit-actions">
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 359140732..eb47bbe88 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,7 +1,7 @@
-import { observable, action, runInAction, reaction, makeObservable } from 'mobx';
-import { Doc, Opt } from '../../fields/Doc';
+import { observable, action, runInAction, makeObservable } from 'mobx';
 
 export class SnappingManager {
+    // eslint-disable-next-line no-use-before-define
     private static _manager: SnappingManager;
     private static get Instance() {
         return SnappingManager._manager ?? new SnappingManager();
@@ -12,7 +12,7 @@ export class SnappingManager {
     @observable _metaKey = false;
     @observable _isLinkFollowing = false;
     @observable _isDragging: boolean = false;
-    @observable _isResizing: Doc | undefined = undefined;
+    @observable _isResizing: string | undefined = undefined; // the string is the Id of the document being resized
     @observable _canEmbed: boolean = false;
     @observable _horizSnapLines: number[] = [];
     @observable _vertSnapLines: number[] = [];
@@ -23,7 +23,9 @@ export class SnappingManager {
         makeObservable(this);
     }
 
-    @action public static clearSnapLines = () => (this.Instance._vertSnapLines.length = this.Instance._horizSnapLines.length = 0);
+    @action public static clearSnapLines = () => {
+        this.Instance._vertSnapLines.length = this.Instance._horizSnapLines.length = 0;
+    };
     @action public static addSnapLines = (horizLines: number[], vertLines: number[]) => {
         this.Instance._horizSnapLines.push(...horizLines);
         this.Instance._vertSnapLines.push(...vertLines);
@@ -39,12 +41,17 @@ export class SnappingManager {
     public static get IsResizing()     { return this.Instance._isResizing; } // prettier-ignore
     public static get CanEmbed()       { return this.Instance._canEmbed; } // prettier-ignore
     public static get ExploreMode()    { return this.Instance._exploreMode; } // prettier-ignore
-    public static SetShiftKey       = (down: boolean) => runInAction(() => (this.Instance._shiftKey = down)); // prettier-ignore
-    public static SetCtrlKey        = (down: boolean) => runInAction(() => (this.Instance._ctrlKey = down)); // prettier-ignore
-    public static SetMetaKey        = (down: boolean) => runInAction(() => (this.Instance._metaKey = down)); // prettier-ignore
-    public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => (this.Instance._isLinkFollowing = follow)); // prettier-ignore
-    public static SetIsDragging     = (drag: boolean) => runInAction(() => (this.Instance._isDragging = drag)); // prettier-ignore
-    public static SetIsResizing     = (doc: Opt<Doc>) => runInAction(() => (this.Instance._isResizing = doc)); // prettier-ignore
-    public static SetCanEmbed       = (embed:boolean) => runInAction(() => (this.Instance._canEmbed = embed)); // prettier-ignore
-    public static SetExploreMode    = (state:boolean) => runInAction(() => (this.Instance._exploreMode = state)); // prettier-ignore
+    public static SetShiftKey       = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore
+    public static SetCtrlKey        = (down: boolean) => runInAction(() => {this.Instance._ctrlKey = down}); // prettier-ignore
+    public static SetMetaKey        = (down: boolean) => runInAction(() => {this.Instance._metaKey = down}); // prettier-ignore
+    public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => {this.Instance._isLinkFollowing = follow}); // prettier-ignore
+    public static SetIsDragging     = (drag: boolean) => runInAction(() => {this.Instance._isDragging = drag}); // prettier-ignore
+    public static SetIsResizing     = (docid?:string) => runInAction(() => {this.Instance._isResizing = docid}); // prettier-ignore
+    public static SetCanEmbed       = (embed:boolean) => runInAction(() => {this.Instance._canEmbed = embed}); // prettier-ignore
+    public static SetExploreMode    = (state:boolean) => runInAction(() => {this.Instance._exploreMode = state}); // prettier-ignore
+
+    public static userColor: string | undefined;
+    public static userVariantColor: string | undefined;
+    public static userBackgroundColor: string | undefined;
+    public static SettingsStyle: any;
 }
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 857ca852f..4e941508d 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,7 +1,8 @@
-import { observable, action, runInAction } from 'mobx';
-import { Doc, Field } from '../../fields/Doc';
-import { RichTextField } from '../../fields/RichTextField';
+import { action, observable, runInAction } from 'mobx';
 import { Without } from '../../Utils';
+import { RichTextField } from '../../fields/RichTextField';
+
+export let printToConsole = false; // Doc.MyDockedBtns.linearView_IsOpen
 
 function getBatchName(target: any, key: string | symbol): string {
     const keyName = key.toString();
@@ -97,7 +98,7 @@ export namespace UndoManager {
 
     export function AddEvent(event: UndoEvent, value?: any): void {
         if (currentBatch && batchCounter.get() && !undoing) {
-            Doc.MyDockedBtns.linearView_IsOpen &&
+            printToConsole &&
                 console.log(
                     '                         '.slice(0, batchCounter.get()) +
                         'UndoEvent : ' +
@@ -172,7 +173,7 @@ export namespace UndoManager {
     }
 
     export function StartBatch(batchName: string): Batch {
-        Doc.MyDockedBtns.linearView_IsOpen && console.log('                          '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
+        printToConsole && console.log('                          '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
         runInAction(() => batchCounter.set(batchCounter.get() + 1));
         if (currentBatch === undefined) {
             currentBatch = [];
@@ -182,7 +183,7 @@ export namespace UndoManager {
 
     const EndBatch = action((batchName: string, cancel: boolean = false) => {
         runInAction(() => batchCounter.set(batchCounter.get() - 1));
-        Doc.MyDockedBtns.linearView_IsOpen && console.log('                          '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')');
+        printToConsole && console.log('                          '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')');
         if (batchCounter.get() === 0 && currentBatch?.length) {
             if (!cancel) {
                 undoStack.push(currentBatch);
diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index 8fc6de6f9..6bbf55e5a 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -1,4 +1,7 @@
-import { Point } from "../../pen-gestures/ndollar";
+/* eslint-disable prefer-destructuring */
+/* eslint-disable no-param-reassign */
+/* eslint-disable camelcase */
+import { Point } from '../../pen-gestures/ndollar';
 
 class SmartRect {
     minx: number = 0;
@@ -6,20 +9,43 @@ class SmartRect {
     maxx: number = 0;
     maxy: number = 0;
 
-    constructor(mix: number = 0, miy: number = 0, max: number = 0, may: number = 0) { this.minx = mix; this.miny = miy; this.maxx = max; this.maxy = may; }
+    constructor(mix: number = 0, miy: number = 0, max: number = 0, may: number = 0) {
+        this.minx = mix;
+        this.miny = miy;
+        this.maxx = max;
+        this.maxy = may;
+    }
 
-    public get Center() { return new Point((this.maxx + this.minx) / 2.0, (this.maxy + this.miny) / 2.0); }
-    public get TopLeft() { return new Point(this.minx, this.miny); }
-    public get TopRight() { return new Point(this.maxx, this.miny); }
-    public get BotLeft() { return new Point(this.minx, this.maxy); }
-    public get BotRight() { return new Point(this.maxx, this.maxy); }
-    public get Width() { return this.maxx - this.minx; }
-    public get Height() { return this.maxy - this.miny; }
-    public static Intersect(a: SmartRect, b: SmartRect) { return a.Intersect(b); }
-    public Intersect(b: SmartRect) { return !((this.minx > b.maxx) || (this.miny > b.maxy) || (b.minx > this.maxx) || (b.miny > this.maxy)); }
+    public get Center() {
+        return new Point((this.maxx + this.minx) / 2.0, (this.maxy + this.miny) / 2.0);
+    }
+    public get TopLeft() {
+        return new Point(this.minx, this.miny);
+    }
+    public get TopRight() {
+        return new Point(this.maxx, this.miny);
+    }
+    public get BotLeft() {
+        return new Point(this.minx, this.maxy);
+    }
+    public get BotRight() {
+        return new Point(this.maxx, this.maxy);
+    }
+    public get Width() {
+        return this.maxx - this.minx;
+    }
+    public get Height() {
+        return this.maxy - this.miny;
+    }
+    public static Intersect(a: SmartRect, b: SmartRect) {
+        return a.Intersect(b);
+    }
+    public Intersect(b: SmartRect) {
+        return !(this.minx > b.maxx || this.miny > b.maxy || b.minx > this.maxx || b.miny > this.maxy);
+    }
 
     public ContainsPercentage(other: SmartRect, axis: Point) {
-        var ret = 0;
+        let ret = 0;
         const minx = Math.max(other.TopLeft.X * axis.X + other.TopLeft.Y * axis.Y, this.TopLeft.X * axis.X + this.TopLeft.Y * axis.Y);
         const maxx = Math.max(other.BotRight.X * axis.X + other.BotRight.Y * axis.Y, this.BotRight.X * axis.X + this.BotRight.Y * axis.Y);
         ret = maxx > minx ? (maxx - minx) / (axis === new Point(1, 0) ? other.Width : other.Height) : 0;
@@ -36,7 +62,7 @@ class SmartRect {
             if (r.minx > r.maxx) [r.minx, r.maxx] = [r.maxx, r.minx];
             if (r.miny > r.maxy) [r.miny, r.maxy] = [r.maxy, r.miny];
 
-            for (const pt of p) {
+            p.forEach(pt => {
                 if (pt.X < r.minx) {
                     r.minx = pt.X;
                 } else if (pt.X > r.maxx) {
@@ -47,7 +73,7 @@ class SmartRect {
                 } else if (pt.Y > r.maxy) {
                     r.maxy = pt.Y;
                 }
-            }
+            });
         }
         return r;
     }
@@ -64,18 +90,18 @@ export function Normalize(p: Point) {
 function ReparameterizeBezier(d: Point[], first: number, last: number, u: number[], bezCurve: Point[]) {
     const uPrime = new Array<number>(last - first + 1); //  New parameter values
 
-    for (var i = first; i <= last; i++) {
+    for (let i = first; i <= last; i++) {
         uPrime[i - first] = NewtonRaphsonRootFind(bezCurve, d[i], u[i - first]);
     }
     return uPrime;
 }
 function ComputeMaxError(d: Point[], first: number, last: number, bezCurve: Point[], u: number[]) {
-    var maxError = 0; //  Maximum error
-    var splitPoint2D = (last - first + 1) / 2;
-    for (var i = first + 1; i < last; i++) {
-        const P = [0, 0];  //  point on curve
+    let maxError = 0; //  Maximum error
+    let splitPoint2D = (last - first + 1) / 2;
+    for (let i = first + 1; i < last; i++) {
+        const P = [0, 0]; //  point on curve
         EvalBezierFast(bezCurve, u[i - first], P);
-        const dx = P[0] - d[i].X;//  offset from point to curve
+        const dx = P[0] - d[i].X; //  offset from point to curve
         const dy = P[1] - d[i].Y;
         const dist = Math.sqrt(dx * dx + dy * dy); //  Current error
         if (dist >= maxError) {
@@ -88,11 +114,11 @@ function ComputeMaxError(d: Point[], first: number, last: number, bezCurve: Poin
     return { maxError, splitPoint2D };
 }
 function ChordLengthParameterize(d: Point[], first: number, last: number) {
-    const u = new Array<number>(last - first + 1);//  Parameterization
+    const u = new Array<number>(last - first + 1); //  Parameterization
 
-    var prev = 0.0;
+    let prev = 0.0;
     u[0] = prev;
-    for (var i = first + 1; i <= last; i++) {
+    for (let i = first + 1; i <= last; i++) {
         const lastd = d[i - 1];
         const curd = d[i];
         const dx = lastd.X - curd.X;
@@ -101,27 +127,38 @@ function ChordLengthParameterize(d: Point[], first: number, last: number) {
     }
 
     const ulastfirst = u[last - first];
-    for (var i = first + 1; i <= last; i++) {
+    for (let i = first + 1; i <= last; i++) {
         u[i - first] /= ulastfirst;
     }
 
     return u;
 }
 /*
-*  B0, B1, B2, B3 :
-*  Bezier multipliers
-*/
-function B0(u: number) { const tmp = 1.0 - u; return tmp * tmp * tmp; }
-function B1(u: number) { const tmp = 1.0 - u; return 3 * u * tmp * tmp; }
-function B2(u: number) { const tmp = 1.0 - u; return 3 * u * u * tmp; }
-function B3(u: number) { return u * u * u; }
+ *  B0, B1, B2, B3 :
+ *  Bezier multipliers
+ */
+function B0(u: number) {
+    const tmp = 1.0 - u;
+    return tmp * tmp * tmp;
+}
+function B1(u: number) {
+    const tmp = 1.0 - u;
+    return 3 * u * tmp * tmp;
+}
+function B2(u: number) {
+    const tmp = 1.0 - u;
+    return 3 * u * u * tmp;
+}
+function B3(u: number) {
+    return u * u * u;
+}
 function bounds(p: Point[]) {
     const r = new SmartRect(p[0].X, p[0].Y, p[3].X, p[3].Y); // These are the most likely to be extremal
 
-    if (r.minx > r.maxx) (r.minx, r.maxx);
+    if (r.minx > r.maxx) [r.minx, r.maxx] = [r.maxx, r.minx];
     if (r.miny > r.maxy) [r.miny, r.maxy] = [r.maxy, r.miny]; // swap min & max
 
-    for (var i = 1; i < 3; i++) {
+    for (let i = 1; i < 3; i++) {
         if (p[i].X < r.minx) r.minx = p[i].X;
         else if (p[i].X > r.maxx) r.maxx = p[i].X;
 
@@ -131,37 +168,36 @@ function bounds(p: Point[]) {
     return r;
 }
 
-
 function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
     const sz = 4;
     const Vtemp = new Array<Array<Point>>(4);
-    for (var i = 0; i < 4; i++) Vtemp[i] = new Array<Point>(4);
+    for (let i = 0; i < 4; i++) Vtemp[i] = new Array<Point>(4);
 
     /* Copy control points */
     // std::copy(p.begin(), p.end(), Vtemp[0]);
-    for (var i = 0; i < sz; i++) {
+    for (let i = 0; i < sz; i++) {
         Vtemp[0][i].X = p[i].X;
         Vtemp[0][i].Y = p[i].Y;
     }
 
     /* Triangle computation */
-    for (var i = 1; i < sz; i++) {
-        for (var j = 0; j < sz - i; j++) {
+    for (let i = 1; i < sz; i++) {
+        for (let j = 0; j < sz - i; j++) {
             const a = Vtemp[i - 1][j];
             const b = Vtemp[i - 1][j + 1];
             Vtemp[i][j].X = b.X * t + a.X * (1 - t);
-            Vtemp[i][j].Y = b.Y * t + a.Y * (1 - t);  // Vtemp[i][j] = Point2D::Lerp(Vtemp[i - 1][j], Vtemp[i - 1][j + 1], t);
+            Vtemp[i][j].Y = b.Y * t + a.Y * (1 - t); // Vtemp[i][j] = Point2D::Lerp(Vtemp[i - 1][j], Vtemp[i - 1][j + 1], t);
         }
     }
 
     if (left) {
-        for (var j = 0; j < sz; j++) {
+        for (let j = 0; j < sz; j++) {
             left[j].X = Vtemp[j][0].X;
             left[j].Y = Vtemp[j][0].Y;
         }
     }
     if (right) {
-        for (var j = 0; j < sz; j++) {
+        for (let j = 0; j < sz; j++) {
             right[j].X = Vtemp[sz - 1 - j][j].X;
             right[j].Y = Vtemp[sz - 1 - j][j].Y;
         }
@@ -169,51 +205,53 @@ function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
 }
 
 /*
-* Recursively intersect two curves keeping track of their real parameters
-* and depths of intersection.
-* The results are returned in a 2-D array of doubles indicating the parameters
-* for which intersections are found.  The parameters are in the order the
-* intersections were found, which is probably not in sorted order.
-* When an intersection is found, the parameter value for each of the two
-* is stored in the index elements array, and the index is incremented.
-*
-* If either of the curves has subdivisions left before it is straight
-* (depth > 0)
-* that curve (possibly both) is (are) subdivided at its (their) midpoint(s).
-* the depth(s) is (are) decremented, and the parameter value(s) corresponding
-* to the midpoints(s) is (are) computed.
-* Then each of the subcurves of one curve is intersected with each of the
-* subcurves of the other curve, first by testing the bounding boxes for
-* interference.  If there is any bounding box interference, the corresponding
-* subcurves are recursively intersected.
-*
-* If neither curve has subdivisions left, the line segments from the first
-* to last control point of each segment are intersected.  (Actually the
-* only the parameter value corresponding to the intersection point is found).
-*
-* The apriori flatness test is probably more efficient than testing at each
-* level of recursion, although a test after three or four levels would
-* probably be worthwhile, since many curves become flat faster than their
-* asymptotic rate for the first few levels of recursion.
-*
-* The bounding box test fails much more frequently than it succeeds, providing
-* substantial pruning of the search space.
-*
-* Each (sub)curve is subdivided only once, hence it is not possible that for
-* one final line intersection test the subdivision was at one level, while
-* for another final line intersection test the subdivision (of the same curve)
-* was at another.  Since the line segments share endpoints, the intersection
-* is robust: a near-tangential intersection will yield zero or two
-* intersections.
-*/
+ * Recursively intersect two curves keeping track of their real parameters
+ * and depths of intersection.
+ * The results are returned in a 2-D array of doubles indicating the parameters
+ * for which intersections are found.  The parameters are in the order the
+ * intersections were found, which is probably not in sorted order.
+ * When an intersection is found, the parameter value for each of the two
+ * is stored in the index elements array, and the index is incremented.
+ *
+ * If either of the curves has subdivisions left before it is straight
+ * (depth > 0)
+ * that curve (possibly both) is (are) subdivided at its (their) midpoint(s).
+ * the depth(s) is (are) decremented, and the parameter value(s) corresponding
+ * to the midpoints(s) is (are) computed.
+ * Then each of the subcurves of one curve is intersected with each of the
+ * subcurves of the other curve, first by testing the bounding boxes for
+ * interference.  If there is any bounding box interference, the corresponding
+ * subcurves are recursively intersected.
+ *
+ * If neither curve has subdivisions left, the line segments from the first
+ * to last control point of each segment are intersected.  (Actually the
+ * only the parameter value corresponding to the intersection point is found).
+ *
+ * The apriori flatness test is probably more efficient than testing at each
+ * level of recursion, although a test after three or four levels would
+ * probably be worthwhile, since many curves become flat faster than their
+ * asymptotic rate for the first few levels of recursion.
+ *
+ * The bounding box test fails much more frequently than it succeeds, providing
+ * substantial pruning of the search space.
+ *
+ * Each (sub)curve is subdivided only once, hence it is not possible that for
+ * one final line intersection test the subdivision was at one level, while
+ * for another final line intersection test the subdivision (of the same curve)
+ * was at another.  Since the line segments share endpoints, the intersection
+ * is robust: a near-tangential intersection will yield zero or two
+ * intersections.
+ */
 function recursively_intersect(a: Point[], t0: number, t1: number, deptha: number, b: Point[], u0: number, u1: number, depthb: number, parameters: number[][]) {
     if (deptha > 0) {
-        const a1 = new Array<Point>(4), a2 = new Array<Point>(4);
+        const a1 = new Array<Point>(4);
+        const a2 = new Array<Point>(4);
         splitCubic(a, 0.5, a1, a2);
         const tmid = (t0 + t1) * 0.5;
         deptha--;
         if (depthb > 0) {
-            const b1 = new Array<Point>(4), b2 = new Array<Point>(4);
+            const b1 = new Array<Point>(4);
+            const b2 = new Array<Point>(4);
             splitCubic(b, 0.5, b1, b2);
             const umid = (u0 + u1) * 0.5;
             depthb--;
@@ -229,8 +267,7 @@ function recursively_intersect(a: Point[], t0: number, t1: number, deptha: numbe
             if (SmartRect.Intersect(bounds(a2), bounds(b2))) {
                 recursively_intersect(a2, tmid, t1, deptha, b2, umid, u1, depthb, parameters);
             }
-        }
-        else {
+        } else {
             if (SmartRect.Intersect(bounds(a1), bounds(b))) {
                 recursively_intersect(a1, t0, tmid, deptha, b, u0, u1, depthb, parameters);
             }
@@ -238,50 +275,45 @@ function recursively_intersect(a: Point[], t0: number, t1: number, deptha: numbe
                 recursively_intersect(a2, tmid, t1, deptha, b, u0, u1, depthb, parameters);
             }
         }
-    }
+    } else if (depthb > 0) {
+        const b1 = new Array<Point>(4);
+        const b2 = new Array<Point>(4);
+        splitCubic(b, 0.5, b1, b2);
+        const umid = (u0 + u1) * 0.5;
+        depthb--;
+        if (SmartRect.Intersect(bounds(a), bounds(b1))) {
+            recursively_intersect(a, t0, t1, deptha, b1, u0, umid, depthb, parameters);
+        }
+        if (SmartRect.Intersect(bounds(a), bounds(b2))) {
+            recursively_intersect(a, t0, t1, deptha, b2, umid, u1, depthb, parameters);
+        }
+    } // Both segments are fully subdivided; now do line segments
     else {
-        if (depthb > 0) {
-            const b1 = new Array<Point>(4), b2 = new Array<Point>(4);
-            splitCubic(b, 0.5, b1, b2);
-            const umid = (u0 + u1) * 0.5;
-            depthb--;
-            if (SmartRect.Intersect(bounds(a), bounds(b1))) {
-                recursively_intersect(a, t0, t1, deptha, b1, u0, umid, depthb, parameters);
-            }
-            if (SmartRect.Intersect(bounds(a), bounds(b2))) {
-                recursively_intersect(a, t0, t1, deptha, b2, umid, u1, depthb, parameters);
-            }
+        const xlk = a[3].X - a[0].X;
+        const ylk = a[3].Y - a[0].Y;
+        const xnm = b[3].X - b[0].X;
+        const ynm = b[3].Y - b[0].Y;
+        const xmk = b[0].X - a[0].X;
+        const ymk = b[0].Y - a[0].Y;
+        const det = xnm * ylk - ynm * xlk;
+        if (1.0 + det === 1.0) {
+            return;
         }
-        else // Both segments are fully subdivided; now do line segments
-        {
-            const xlk = a[3].X - a[0].X;
-            const ylk = a[3].Y - a[0].Y;
-            const xnm = b[3].X - b[0].X;
-            const ynm = b[3].Y - b[0].Y;
-            const xmk = b[0].X - a[0].X;
-            const ymk = b[0].Y - a[0].Y;
-            const det = xnm * ylk - ynm * xlk;
-            if (1.0 + det === 1.0) {
-                return;
-            }
-            else {
-                const detinv = 1.0 / det;
-                const s = (xnm * ymk - ynm * xmk) * detinv;
-                const t = (xlk * ymk - ylk * xmk) * detinv;
-                if ((s < 0.0) || (s > 1.0) || (t < 0.0) || (t > 1.0) || Number.isNaN(s) || Number.isNaN(t)) {
-                    return;
-                }
-                parameters.push([t0 + s * (t1 - t0), u0 + t * (u1 - u0)]);
-            }
+        const detinv = 1.0 / det;
+        const s = (xnm * ymk - ynm * xmk) * detinv;
+        const t = (xlk * ymk - ylk * xmk) * detinv;
+        if (s < 0.0 || s > 1.0 || t < 0.0 || t > 1.0 || isNaN(s) || isNaN(t)) {
+            return;
         }
+        parameters.push([t0 + s * (t1 - t0), u0 + t * (u1 - u0)]);
     }
 }
 
 /*
-*  EvalBezier :
-*      Evaluate a Bezier curve at a particular parameter value
-*
-*/
+ *  EvalBezier :
+ *      Evaluate a Bezier curve at a particular parameter value
+ *
+ */
 const MAX_DEGREE = 5;
 function EvalBezier(V: Point[], degree: number, t: number, result: number[]) {
     if (degree + 1 > MAX_DEGREE) {
@@ -293,35 +325,36 @@ function EvalBezier(V: Point[], degree: number, t: number, result: number[]) {
     const Vtemp = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)]; // Local copy of control points
 
     /* Copy array   */
-    for (var i = 0; i <= degree; i++) {
+    for (let i = 0; i <= degree; i++) {
         Vtemp[i].X = V[i].X;
         Vtemp[i].Y = V[i].Y;
     }
 
     /* Triangle computation */
-    for (var i = 1; i <= degree; i++) {
-        for (var j = 0; j <= degree - i; j++) {
+    for (let i = 1; i <= degree; i++) {
+        for (let j = 0; j <= degree - i; j++) {
             Vtemp[j].X = (1.0 - t) * Vtemp[j].X + t * Vtemp[j + 1].X;
             Vtemp[j].Y = (1.0 - t) * Vtemp[j].Y + t * Vtemp[j + 1].Y;
         }
     }
 
     result[0] = Vtemp[0].X;
-    result[1] = Vtemp[0].Y;// Point on curve at parameter t
+    result[1] = Vtemp[0].Y; // Point on curve at parameter t
 }
 
 function EvalBezierFast(p: Point[], t: number, result: number[]) {
     const n = 3;
     const u = 1.0 - t;
-    var bc = 1, tn = 1;
-    var tmpX = p[0].X * u;
-    var tmpY = p[0].Y * u;
-    tn = tn * t;
-    bc = bc * (n - 1 + 1) / 1;
+    let bc = 1;
+    let tn = 1;
+    let tmpX = p[0].X * u;
+    let tmpY = p[0].Y * u;
+    tn *= t;
+    bc = (bc * (n - 1 + 1)) / 1;
     tmpX = (tmpX + tn * bc * p[1].X) * u;
     tmpY = (tmpY + tn * bc * p[1].Y) * u;
-    tn = tn * t;
-    bc = bc * (n - 2 + 1) / 2;
+    tn *= t;
+    bc = (bc * (n - 2 + 1)) / 2;
     tmpX = (tmpX + tn * bc * p[2].X) * u;
     tmpY = (tmpY + tn * bc * p[2].Y) * u;
 
@@ -329,9 +362,9 @@ function EvalBezierFast(p: Point[], t: number, result: number[]) {
     result[1] = tmpY + tn * t * p[3].Y;
 }
 /*
-* ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
-*Approximate unit tangents at endpoints and "center" of digitized curve
-*/
+ * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
+ *Approximate unit tangents at endpoints and "center" of digitized curve
+ */
 function ComputeLeftTangent(d: Point[], end: number) {
     const use = 1;
     const tHat1 = new Point(d[end + use].X - d[end].X, d[end + use].Y - d[end].Y);
@@ -348,19 +381,19 @@ function ComputeCenterTangent(d: Point[], center: number) {
     }
     const V1 = ComputeLeftTangent(d, center); // d[center] - d[center-1];
     const V2 = ComputeRightTangent(d, center); // d[center] - d[center + 1];
-    var tHatCenter = new Point((-V1.X + V2.X) / 2.0, (-V1.Y + V2.Y) / 2.0);
+    let tHatCenter = new Point((-V1.X + V2.X) / 2.0, (-V1.Y + V2.Y) / 2.0);
     if (tHatCenter === new Point(0, 0)) {
-        tHatCenter = new Point(-V1.Y, -V1.X);// V1.Perp();
+        tHatCenter = new Point(-V1.Y, -V1.X); // V1.Perp();
     }
     return Normalize(tHatCenter);
 }
 function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[], tHat1: Point, tHat2: Point, result: Point[] /* must be prealloacted to size 4 */) {
     const nPts = last - first + 1; // Number of pts in sub-curve
-    const Ax = new Array<number>(nPts * 2);// Precomputed rhs for eqn  //std::vector<Vector2D> A(nPts * 2);
-    const Ay = new Array<number>(nPts * 2);// Precomputed rhs for eqn  //std::vector<Vector2D> A(nPts * 2);
+    const Ax = new Array<number>(nPts * 2); // Precomputed rhs for eqn  //std::vector<Vector2D> A(nPts * 2);
+    const Ay = new Array<number>(nPts * 2); // Precomputed rhs for eqn  //std::vector<Vector2D> A(nPts * 2);
 
     /* Compute the A's  */
-    for (var i = 0; i < nPts; i++) {
+    for (let i = 0; i < nPts; i++) {
         const uprime = uPrime[i];
         const b1 = B1(uprime);
         const b2 = B2(uprime);
@@ -371,39 +404,42 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
     }
 
     /* Create the C and X matrices  */
-    const C = [[0, 0], [0, 0]];
+    const C = [
+        [0, 0],
+        [0, 0],
+    ];
     const df = d[first];
     const dl = d[last];
 
-    const X = [0, 0];    // Matrix X
-    for (var i = 0; i < nPts; i++) {
-        C[0][0] += Ax[i] * Ax[i] + Ay[i] * Ay[i];     //A[i+0*nPts].Dot(A[i+0*nPts]);
-        C[0][1] += Ax[i] * Ax[i + nPts] + Ay[i] * Ay[i + nPts];//A[i+0*nPts].Dot(A[i+1*nPts]);
+    const X = [0, 0]; // Matrix X
+    for (let i = 0; i < nPts; i++) {
+        C[0][0] += Ax[i] * Ax[i] + Ay[i] * Ay[i]; // A[i+0*nPts].Dot(A[i+0*nPts]);
+        C[0][1] += Ax[i] * Ax[i + nPts] + Ay[i] * Ay[i + nPts]; // A[i+0*nPts].Dot(A[i+1*nPts]);
         C[1][0] = C[0][1];
-        C[1][1] += Ax[i + nPts] * Ax[i + nPts] + Ay[i + nPts] * Ay[i + nPts];// A[i+1*nPts].Dot(A[i+1*nPts]);
+        C[1][1] += Ax[i + nPts] * Ax[i + nPts] + Ay[i + nPts] * Ay[i + nPts]; // A[i+1*nPts].Dot(A[i+1*nPts]);
         const uprime = uPrime[i];
         const b0plb1 = B0(uprime) + B1(uprime);
         const b2plb3 = B2(uprime) + B3(uprime);
         const df1 = d[first + i];
-        const tmpX = df1.X - (df.X * b0plb1 + (dl.X * b2plb3));
-        const tmpY = df1.Y - (df.Y * b0plb1 + (dl.Y * b2plb3));
+        const tmpX = df1.X - (df.X * b0plb1 + dl.X * b2plb3);
+        const tmpY = df1.Y - (df.Y * b0plb1 + dl.Y * b2plb3);
 
         X[0] += Ax[i] * tmpX + Ay[i] * tmpY; // A[i+0*nPts].Dot(tmp)
-        X[1] += Ax[i + nPts] * tmpX + Ay[i + nPts] * tmpY; //A[i+1*nPts].Dot(tmp)
+        X[1] += Ax[i + nPts] * tmpX + Ay[i + nPts] * tmpY; // A[i+1*nPts].Dot(tmp)
     }
 
     /* Compute the determinants of C and X  */
-    const det_C0_C1 = (C[0][0] * C[1][1] - C[1][0] * C[0][1]) || (C[0][0] * C[1][1]) * 10e-12;
+    const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1] || C[0][0] * C[1][1] * 10e-12;
     const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
     const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
 
     /* Finally, derive alpha values */
-    var alpha_l = (det_C0_C1 === 0) ? 0.0 : det_X_C1 / det_C0_C1;
-    var alpha_r = (det_C0_C1 === 0) ? 0.0 : det_C0_X / det_C0_C1;
+    let alpha_l = det_C0_C1 === 0 ? 0.0 : det_X_C1 / det_C0_C1;
+    let alpha_r = det_C0_C1 === 0 ? 0.0 : det_C0_X / det_C0_C1;
 
     /* If alpha negative, use the Wu/Barsky heuristic (see text) */
     /* (if alpha is 0, you get coincident control points that lead to
-    * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
+     * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
     const segLength = Math.sqrt((df.X - dl.X) * (df.X - dl.X) + (df.Y - dl.Y) * (df.Y - dl.Y));
     const epsilon = 1.0e-6 * segLength;
     if (alpha_l < epsilon || alpha_r < epsilon) {
@@ -415,10 +451,10 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
     /*  positioned exactly at the first and last data points */
     /*  Control points 1 and 2 are positioned an alpha distance out */
     /*  on the tangent vectors, left and right, respectively */
-    result[0] = df;// RETURN bezier curve ctl pts
+    result[0] = df; // RETURN bezier curve ctl pts
     result[3] = dl;
-    result[1] = new Point(df.X + (tHat1.X * alpha_l), df.Y + (tHat1.Y * alpha_l));
-    result[2] = new Point(dl.X + (tHat2.X * alpha_r), dl.Y + (tHat2.Y * alpha_r));
+    result[1] = new Point(df.X + tHat1.X * alpha_l, df.Y + tHat1.Y * alpha_l);
+    result[2] = new Point(dl.X + tHat2.X * alpha_r, dl.Y + tHat2.Y * alpha_r);
 }
 
 /*
@@ -426,21 +462,24 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
  *  Use Newton-Raphson iteration to find better root.
  */
 function NewtonRaphsonRootFind(Q: Point[], P: Point, u: number) {
-    const Q1 = [new Point(0, 0), new Point(0, 0), new Point(0, 0)], Q2 = [new Point(0, 0), new Point(0, 0)]; //  Q' and Q''
-    const Q_u = [0, 0], Q1_u = [0, 0], Q2_u = [0, 0]; //u evaluated at Q, Q', & Q''
+    const Q1 = [new Point(0, 0), new Point(0, 0), new Point(0, 0)];
+    const Q2 = [new Point(0, 0), new Point(0, 0)]; //  Q' and Q''
+    const Q_u = [0, 0];
+    const Q1_u = [0, 0];
+    const Q2_u = [0, 0]; // u evaluated at Q, Q', & Q''
 
     /* Compute Q(u) */
-    var uPrime: number; //  Improved u
+    let uPrime: number; //  Improved u
     EvalBezierFast(Q, u, Q_u);
 
     /* Generate control vertices for Q' */
-    for (var i = 0; i <= 2; i++) {
+    for (let i = 0; i <= 2; i++) {
         Q1[i].X = (Q[i + 1].X - Q[i].X) * 3.0;
         Q1[i].Y = (Q[i + 1].Y - Q[i].Y) * 3.0;
     }
 
     /* Generate control vertices for Q'' */
-    for (var i = 0; i <= 1; i++) {
+    for (let i = 0; i <= 1; i++) {
         Q2[i].X = (Q1[i + 1].X - Q1[i].X) * 2.0;
         Q2[i].Y = (Q1[i + 1].Y - Q1[i].Y) * 2.0;
     }
@@ -450,20 +489,20 @@ function NewtonRaphsonRootFind(Q: Point[], P: Point, u: number) {
     EvalBezier(Q2, 1, u, Q2_u);
 
     /* Compute f(u)/f'(u) */
-    const numerator = (Q_u[0] - P.X) * (Q1_u[0]) + (Q_u[1] - P.Y) * (Q1_u[1]);
-    const denominator = (Q1_u[0]) * (Q1_u[0]) + (Q1_u[1]) * (Q1_u[1]) + (Q_u[0] - P.X) * (Q2_u[0]) + (Q_u[1] - P.Y) * (Q2_u[1]);
+    const numerator = (Q_u[0] - P.X) * Q1_u[0] + (Q_u[1] - P.Y) * Q1_u[1];
+    const denominator = Q1_u[0] * Q1_u[0] + Q1_u[1] * Q1_u[1] + (Q_u[0] - P.X) * Q2_u[0] + (Q_u[1] - P.Y) * Q2_u[1];
     if (denominator === 0.0) {
         uPrime = u;
-    } else uPrime = u - (numerator / denominator);/* u = u - f(u)/f'(u) */
+    } else uPrime = u - numerator / denominator; /* u = u - f(u)/f'(u) */
 
     return uPrime;
 }
 function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2: Point, error: number, result: Point[]) {
-    const bezCurve = new Array<Point>(4);          //  Control points of fitted Bezier curve
-    const maxIterations = 4;      //  Max times to try iterating
+    const bezCurve = new Array<Point>(4); //  Control points of fitted Bezier curve
+    const maxIterations = 4; //  Max times to try iterating
 
     const iterationError = error * error; //  Error below which you try iterating
-    const nPts = last - first + 1;        //  Number of points in subset
+    const nPts = last - first + 1; //  Number of points in subset
 
     /*  Use heuristic if region only has two points in it */
     if (nPts === 2) {
@@ -471,8 +510,8 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
 
         bezCurve[0] = d[first];
         bezCurve[3] = d[last];
-        bezCurve[1] = new Point(bezCurve[0].X + (tHat1.X * dist), bezCurve[0].Y + (tHat1.Y * dist));
-        bezCurve[2] = new Point(bezCurve[3].X + (tHat2.X * dist), bezCurve[3].Y + (tHat2.Y * dist));
+        bezCurve[1] = new Point(bezCurve[0].X + tHat1.X * dist, bezCurve[0].Y + tHat1.Y * dist);
+        bezCurve[2] = new Point(bezCurve[3].X + tHat2.X * dist, bezCurve[3].Y + tHat2.Y * dist);
 
         result.push(bezCurve[1]);
         result.push(bezCurve[2]);
@@ -481,11 +520,11 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
     }
 
     /*  Parameterize points, and attempt to fit curve */
-    var u = ChordLengthParameterize(d, first, last);
+    let u = ChordLengthParameterize(d, first, last);
     GenerateBezier(d, first, last, u, tHat1, tHat2, bezCurve);
 
     /*  Find max deviation of points to fitted curve */
-    const { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, u);  //  Maximum fitting error
+    const { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, u); //  Maximum fitting error
     if (maxError < Math.abs(error)) {
         result.push(bezCurve[1]);
         result.push(bezCurve[2]);
@@ -496,8 +535,8 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
     /*  If error not too large, try some reparameterization  */
     /*  and iteration */
     if (maxError < iterationError) {
-        for (var i = 0; i < maxIterations; i++) {
-            const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve);   //  Improved parameter values
+        for (let i = 0; i < maxIterations; i++) {
+            const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve); //  Improved parameter values
             GenerateBezier(d, first, last, uPrime, tHat1, tHat2, bezCurve);
             const { maxError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
             if (maxError < error) {
@@ -527,13 +566,13 @@ export function FitOneCurve(d: Point[], tHat1?: Point, tHat2?: Point) {
     tHat1 = tHat1 ?? Normalize(ComputeLeftTangent(d, 0));
     tHat2 = tHat2 ?? Normalize(ComputeRightTangent(d, d.length - 1));
     tHat2 = new Point(-tHat2.X, -tHat2.Y);
-    var u = ChordLengthParameterize(d, 0, d.length - 1);
+    let u = ChordLengthParameterize(d, 0, d.length - 1);
     const bezCurveCtrls = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)];
-    GenerateBezier(d, 0, d.length - 1, u, tHat1, tHat2, bezCurveCtrls);    /*  Find max deviation of points to fitted curve */
-    var finalCtrls = bezCurveCtrls.slice();
-    var { maxError: error } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, u);
-    for (var i = 0; i < 10; i++) {
-        const uPrime = ReparameterizeBezier(d, 0, d.length - 1, u, bezCurveCtrls);   //  Improved parameter values
+    GenerateBezier(d, 0, d.length - 1, u, tHat1, tHat2, bezCurveCtrls); /*  Find max deviation of points to fitted curve */
+    let finalCtrls = bezCurveCtrls.slice();
+    let { maxError: error } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, u);
+    for (let i = 0; i < 10; i++) {
+        const uPrime = ReparameterizeBezier(d, 0, d.length - 1, u, bezCurveCtrls); //  Improved parameter values
         GenerateBezier(d, 0, d.length - 1, uPrime, tHat1, tHat2, bezCurveCtrls);
         const { maxError } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, uPrime);
         if (maxError < error) {
@@ -1428,4 +1467,4 @@ spliceR[0] += v;
 }
 #endif
 
-*/
\ No newline at end of file
+*/
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
index 0c49aeed4..d6fd339a9 100644
--- a/src/client/util/reportManager/ReportManager.tsx
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -1,23 +1,24 @@
-import * as React from 'react';
-import * as uuid from 'uuid';
-import '.././SettingsManager.scss';
-import './ReportManager.scss';
-import ReactLoading from 'react-loading';
+import { Octokit } from '@octokit/core';
+import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
-import { BsX, BsArrowsAngleExpand, BsArrowsAngleContract } from 'react-icons/bs';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { BsArrowsAngleContract, BsArrowsAngleExpand, BsX } from 'react-icons/bs';
 import { CgClose } from 'react-icons/cg';
 import { HiOutlineArrowLeft } from 'react-icons/hi';
-import { Issue } from './reportManagerSchema';
-import { observer } from 'mobx-react';
+import { MdRefresh } from 'react-icons/md';
+import ReactLoading from 'react-loading';
+import * as uuid from 'uuid';
+import { ClientUtils } from '../../../ClientUtils';
 import { Doc } from '../../../fields/Doc';
-import { MainViewModal } from '../../views/MainViewModal';
-import { Octokit } from '@octokit/core';
-import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components';
-import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, darkColors, emptyReportForm, formatTitle, getAllIssues, isDarkMode, lightColors, passesTagFilter, priorityDropdownItems, uploadFilesToServer } from './reportManagerUtils';
-import { Filter, FormInput, FormTextArea, IssueCard, IssueView, Tag } from './ReportManagerComponents';
 import { StrCast } from '../../../fields/Types';
-import { MdRefresh } from 'react-icons/md';
+import { MainViewModal } from '../../views/MainViewModal';
+import '.././SettingsManager.scss';
 import { SettingsManager } from '../SettingsManager';
+import './ReportManager.scss';
+import { Filter, FormInput, FormTextArea, IssueCard, IssueView } from './ReportManagerComponents';
+import { Issue } from './reportManagerSchema';
+import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, darkColors, emptyReportForm, formatTitle, getAllIssues, isDarkMode, lightColors, passesTagFilter, priorityDropdownItems, uploadFilesToServer } from './reportManagerUtils';
 
 /**
  * Class for reporting and viewing Github issues within the app.
@@ -133,7 +134,7 @@ export class ReportManager extends React.Component<{}> {
         const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
             owner: 'brown-dash',
             repo: 'Dash-Web',
-            title: formatTitle(this.formData.title, Doc.CurrentUserEmail),
+            title: formatTitle(this.formData.title, ClientUtils.CurrentUserEmail),
             body: `${this.formData.description} ${formattedLinks.length > 0 ? `\n\nFiles:\n${formattedLinks.join('\n')}` : ''}`,
             labels: ['from-dash-app', this.formData.type, this.formData.priority],
         });
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index ca877b93e..18b433a77 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,16 +1,16 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { StrCast } from '../../fields/Types';
-import { SettingsManager } from '../util/SettingsManager';
+import { DivHeight, DivWidth } from '../../ClientUtils';
+import { SnappingManager } from '../util/SnappingManager';
 import './ContextMenu.scss';
 import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
 import { ObservableReactComponent } from './ObservableReactComponent';
-import { DivHeight, DivWidth } from '../../Utils';
 
 @observer
 export class ContextMenu extends ObservableReactComponent<{}> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: ContextMenu;
 
     private _ignoreUp = false;
@@ -124,8 +124,8 @@ export class ContextMenu extends ObservableReactComponent<{}> {
 
     @action
     displayMenu = (x: number, y: number, initSearch = '', showSearch = false, onDisplay?: () => void) => {
-        //maxX and maxY will change if the UI/font size changes, but will work for any amount
-        //of items added to the menu
+        // maxX and maxY will change if the UI/font size changes, but will work for any amount
+        // of items added to the menu
 
         this._showSearch = showSearch;
         this._pageX = x;
@@ -192,7 +192,7 @@ export class ContextMenu extends ObservableReactComponent<{}> {
                     key={index + value.join(' -> ')}
                     className="contextMenu-group"
                     style={{
-                        background: StrCast(SettingsManager.userVariantColor),
+                        background: SnappingManager.userVariantColor,
                     }}>
                     <div className="contextMenu-description">{value.join(' -> ')}</div>
                 </div>
@@ -224,11 +224,11 @@ export class ContextMenu extends ObservableReactComponent<{}> {
                     display: this._display ? '' : 'none',
                     left: this.pageX,
                     ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
-                    background: SettingsManager.userBackgroundColor,
-                    color: SettingsManager.userColor,
+                    background: SnappingManager.userBackgroundColor,
+                    color: SnappingManager.userColor,
                 }}>
                 {!this.itemsNeedSearch ? null : (
-                    <span className={'search-icon'}>
+                    <span className="search-icon">
                         <span className="icon-background">
                             <FontAwesomeIcon icon="search" size="lg" />
                         </span>
@@ -267,7 +267,7 @@ export class ContextMenu extends ObservableReactComponent<{}> {
             if (item) {
                 item.event({ x: this.pageX, y: this.pageY });
             } else {
-                //if (this._searchString.startsWith(this._defaultPrefix)) {
+                // if (this._searchString.startsWith(this._defaultPrefix)) {
                 this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length));
             }
             this.closeMenu();
@@ -281,12 +281,10 @@ export class ContextMenu extends ObservableReactComponent<{}> {
         this._searchString = e.target.value;
         if (!this._searchString) {
             this._selectedIndex = -1;
+        } else if (this._selectedIndex === -1) {
+            this._selectedIndex = 0;
         } else {
-            if (this._selectedIndex === -1) {
-                this._selectedIndex = 0;
-            } else {
-                this._selectedIndex = Math.min(this.flatItems.length - 1, this._selectedIndex);
-            }
+            this._selectedIndex = Math.min(this.flatItems.length - 1, this._selectedIndex);
         }
     };
 }
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index d15ab749c..5760872fb 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,17 +1,17 @@
-import * as React from 'react';
-import { observable, action, runInAction, makeObservable } from 'mobx';
-import { observer } from 'mobx-react';
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, makeObservable, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
-import { SettingsManager } from '../util/SettingsManager';
 import { ObservableReactComponent } from './ObservableReactComponent';
 
 export interface OriginalMenuProps {
     description: string;
     event: (stuff?: any) => void;
     undoable?: boolean;
-    icon: IconProp | JSX.Element; //maybe should be optional (icon?)
+    icon: IconProp | JSX.Element; // maybe should be optional (icon?)
     closeMenu?: () => void;
 }
 
@@ -20,7 +20,7 @@ export interface SubmenuProps {
     subitems: ContextMenuProps[];
     noexpand?: boolean;
     addDivider?: boolean;
-    icon: IconProp; //maybe should be optional (icon?)
+    icon: IconProp; // maybe should be optional (icon?)
     closeMenu?: () => void;
 }
 
@@ -67,7 +67,9 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
         this._overPosY = e.clientY;
         this._overPosX = e.clientX;
         this.currentTimeout = setTimeout(
-            action(() => (this.overItem = true)),
+            action(() => {
+                this.overItem = true;
+            }),
             ContextMenuItem.timeout
         );
     };
@@ -97,14 +99,15 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
                     {this._props.icon ? <span className="contextMenu-item-icon-background">{this.isJSXElement(this._props.icon) ? this._props.icon : <FontAwesomeIcon icon={this._props.icon} size="sm" />}</span> : null}
                     <div className="contextMenu-description">{this._props.description.replace(':', '')}</div>
                     <div
-                        className={`contextMenu-item-background`}
+                        className="contextMenu-item-background"
                         style={{
-                            background: SettingsManager.userColor,
+                            background: SnappingManager.userColor,
                         }}
                     />
                 </div>
             );
-        } else if ('subitems' in this._props) {
+        }
+        if ('subitems' in this._props) {
             const where = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center';
             const marginTop = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : '';
 
@@ -115,7 +118,7 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
                     style={{
                         marginLeft: window.innerWidth - this._overPosX - 50 > 0 ? '90%' : '20%',
                         marginTop,
-                        background: SettingsManager.userBackgroundColor,
+                        background: SnappingManager.userBackgroundColor,
                     }}>
                     {this._items.map(prop => (
                         <ContextMenuItem {...prop} key={prop.description} closeMenu={this._props.closeMenu} />
@@ -149,7 +152,7 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
                     <div
                         className={`contextMenu-item-background`}
                         style={{
-                            background: SettingsManager.userColor,
+                            background: SnappingManager.userColor,
                         }}
                     />
                     {submenu}
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index a4f598d1a..1d286c987 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -4,6 +4,7 @@ import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaPlus } from 'react-icons/fa';
+import { ClientUtils } from '../../ClientUtils';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
@@ -14,21 +15,19 @@ import { ScriptField } from '../../fields/ScriptField';
 import { Cast, ImageCast, StrCast } from '../../fields/Types';
 import { normalizeEmail } from '../../fields/util';
 import { DocServer } from '../DocServer';
-import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
+import { DocUtils, Docs, DocumentOptions } from '../documents/Documents';
+import { dropActionType } from '../util/DropActionTypes';
 import { HistoryUtil } from '../util/History';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { SettingsManager } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
-import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { CollectionView } from './collections/CollectionView';
+import { SnappingManager } from '../util/SnappingManager';
+import { undoBatch, undoable } from '../util/UndoManager';
 import { ContextMenu } from './ContextMenu';
 import './DashboardView.scss';
-import { Colors } from './global/globalEnums';
 import { MainViewModal } from './MainViewModal';
-import { ButtonType } from './nodes/FontIconBox/FontIconBox';
 import { ObservableReactComponent } from './ObservableReactComponent';
-import { dropActionType } from '../util/DragManager';
+import { Colors } from './global/globalEnums';
+import { ButtonType } from './nodes/FontIconBox/FontIconBox';
 
 enum DashboardGroup {
     MyDashboards,
@@ -65,7 +64,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
 
     getDashboards = (whichGroup: DashboardGroup) => {
         if (whichGroup === DashboardGroup.MyDashboards) {
-            return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === Doc.CurrentUserEmail);
+            return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === ClientUtils.CurrentUserEmail);
         }
         return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig);
     };
@@ -84,11 +83,11 @@ export class DashboardView extends ObservableReactComponent<{}> {
             <div
                 className="new-dashboard"
                 style={{
-                    background: SettingsManager.userBackgroundColor,
-                    color: SettingsManager.userColor,
+                    background: SnappingManager.userBackgroundColor,
+                    color: SnappingManager.userColor,
                 }}>
                 <div className="header">Create New Dashboard</div>
-                <EditableText formLabel="Title" placeholder={this.newDashboardName} type={Type.SEC} color={SettingsManager.userColor} setVal={val => this.setNewDashboardName(val as string)} fillWidth />
+                <EditableText formLabel="Title" placeholder={this.newDashboardName} type={Type.SEC} color={SnappingManager.userColor} setVal={val => this.setNewDashboardName(val as string)} fillWidth />
                 <ColorPicker
                     formLabel="Background" //
                     colorPickerType="github"
@@ -98,8 +97,8 @@ export class DashboardView extends ObservableReactComponent<{}> {
                     setSelectedColor={this.setNewDashboardColor}
                 />
                 <div className="button-bar">
-                    <Button text="Cancel" color={SettingsManager.userColor} onClick={this.abortCreateNewDashboard} />
-                    <Button text="Create" color={SettingsManager.userVariantColor} type={Type.TERT} onClick={() => this.createNewDashboard(this.newDashboardName, this.newDashboardColor)} />
+                    <Button text="Cancel" color={SnappingManager.userColor} onClick={this.abortCreateNewDashboard} />
+                    <Button text="Create" color={SnappingManager.userVariantColor} type={Type.TERT} onClick={() => this.createNewDashboard(this.newDashboardName, this.newDashboardColor)} />
                 </div>
             </div>
         );
@@ -133,8 +132,8 @@ export class DashboardView extends ObservableReactComponent<{}> {
     };
 
     render() {
-        const color = SettingsManager.userColor;
-        const variant = SettingsManager.userVariantColor;
+        const color = SnappingManager.userColor;
+        const variant = SnappingManager.userVariantColor;
         return (
             <>
                 <div className="dashboard-view">
@@ -156,7 +155,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
                             : this.getDashboards(this.selectedDashboardGroup).map(dashboard => {
                                   const href = ImageCast(dashboard.thumb)?.url?.href;
                                   const shared = Object.keys(dashboard[DocAcl])
-                                      .filter(key => key !== `acl-${normalizeEmail(Doc.CurrentUserEmail)}` && !['acl-Me', 'acl-Guest'].includes(key))
+                                      .filter(key => key !== `acl-${normalizeEmail(ClientUtils.CurrentUserEmail)}` && !['acl-Me', 'acl-Guest'].includes(key))
                                       .some(key => dashboard[DocAcl][key] !== AclPrivate);
                                   return (
                                       <div
@@ -187,7 +186,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
                                           <div
                                               className="background"
                                               style={{
-                                                  background: SettingsManager.userColor,
+                                                  background: SnappingManager.userColor,
                                                   filter: 'opacity(0.2)',
                                               }}
                                           />
@@ -201,7 +200,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
                                 <div
                                     className="background"
                                     style={{
-                                        background: SettingsManager.userColor,
+                                        background: SnappingManager.userColor,
                                         filter: 'opacity(0.2)',
                                     }}
                                 />
@@ -217,9 +216,6 @@ export class DashboardView extends ObservableReactComponent<{}> {
     public static closeActiveDashboard() {
         Doc.ActiveDashboard = undefined;
     }
-    public static snapshotDashboard() {
-        return CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard);
-    }
 
     public static openSharedDashboard = (dashboard: Doc) => {
         Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard);
@@ -250,16 +246,16 @@ export class DashboardView extends ObservableReactComponent<{}> {
                 });
             if (state.readonly === true || state.readonly === null) {
                 DocServer.Control.makeReadOnly();
-            } else if (state.safe) {
-                if (!state.nro) {
-                    DocServer.Control.makeReadOnly();
-                }
-                CollectionView.SetSafeMode(true);
+                // } else if (state.safe) {
+                //     if (!state.nro) {
+                //         DocServer.Control.makeReadOnly();
+                //     }
+                //     CollectionView.SetSafeMode(true);
             } else if (state.nro || state.nro === null || state.readonly === false) {
             } else if (doc.readOnly) {
                 DocServer.Control.makeReadOnly();
             } else {
-                Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
+                ClientUtils.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
             }
         }
 
@@ -473,23 +469,23 @@ export class DashboardView extends ObservableReactComponent<{}> {
     }
 }
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function createNewDashboard() {
     return DashboardView.createNewDashboard();
 }, 'creates a new dashboard when called');
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function shareDashboard(dashboard: Doc) {
     SharingManager.Instance.open(undefined, dashboard);
 }, 'opens sharing dialog for Dashboard');
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function removeDashboard(dashboard: Doc) {
     DashboardView.removeDashboard(dashboard);
 }, 'Remove Dashboard from Dashboards');
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function resetDashboard(dashboard: Doc) {
     DashboardView.resetDashboard(dashboard);
 }, 'move all dashboard tabs to single stack');
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function addToDashboards(dashboard: Doc) {
     DashboardView.openDashboard(Doc.MakeEmbedding(dashboard));
 }, 'adds Dashboard to set of Dashboards');
-ScriptingGlobals.add(async function snapshotDashboard() {
-    const batch = UndoManager.StartBatch('snapshot');
-    await DashboardView.snapshotDashboard();
-    batch.end();
-}, 'creates a snapshot copy of a dashboard');
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index de4df1830..348b18129 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,22 +1,52 @@
 import { action, computed, makeObservable, observable } from 'mobx';
 import * as React from 'react';
-import { returnFalse } from '../../Utils';
+import { returnFalse } from '../../ClientUtils';
 import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
+import { Doc, DocListCast, FieldType, Opt } from '../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData, DocViews } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
 import { RefField } from '../../fields/RefField';
 import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
 import { DocumentType } from '../documents/DocumentTypes';
-import { DocUtils } from '../documents/Documents';
-import { DocumentManager } from '../util/DocumentManager';
 import { DragManager } from '../util/DragManager';
 import { ObservableReactComponent } from './ObservableReactComponent';
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { DocumentView, OpenWhere } from './nodes/DocumentView';
 import { FieldViewProps, FocusViewOptions } from './nodes/FieldView';
-import { PinProps } from './nodes/trails';
+// import { DocUtils } from '../documents/Documents';
 
+export interface pinDataTypes {
+    scrollable?: boolean;
+    dataviz?: number[];
+    pannable?: boolean;
+    type_collection?: boolean;
+    inkable?: boolean;
+    filters?: boolean;
+    pivot?: boolean;
+    temporal?: boolean;
+    clippable?: boolean;
+    datarange?: boolean;
+    dataview?: boolean;
+    poslayoutview?: boolean;
+    dataannos?: boolean;
+    map?: boolean;
+}
+
+export interface MarqueeViewBounds {
+    left: number;
+    top: number;
+    width: number;
+    height: number;
+}
+export interface PinProps {
+    audioRange?: boolean;
+    activeFrame?: number;
+    currentFrame?: number;
+    hidePresBox?: boolean;
+    pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected)
+    pinDocLayout?: boolean; // pin layout info (width/height/x/y)
+    pinAudioPlay?: boolean; // pin audio annotation
+    pinData?: pinDataTypes;
+}
 /**
  * Shared interface among all viewBox'es (ie, react classes that render the contents of a Doc)
  * Many of these methods only make sense for specific viewBox'es, but they should be written to
@@ -25,6 +55,7 @@ import { PinProps } from './nodes/trails';
 export interface ViewBoxInterface {
     fieldKey?: string;
     annotationKey?: string;
+    promoteCollection?: () => void; // moves contents of collection to parent
     updateIcon?: () => void; // updates the icon representation of the document
     getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
     restoreView?: (viewSpec: Doc) => boolean;
@@ -46,7 +77,7 @@ export interface ViewBoxInterface {
     IsPlaying?: () => boolean; // is a media document playing
     TogglePause?: (keep?: boolean) => void; // toggle media document playing state
     setFocus?: () => void; // sets input focus to the componentView
-    setData?: (data: Field | Promise<RefField | undefined>) => boolean;
+    setData?: (data: FieldType | Promise<RefField | undefined>) => boolean;
     componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
     dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
     dragConfig?: (dragData: DragManager.DocumentDragData) => void; // function to setup dragData in custom way (see TreeViews which add a tree view flag)
@@ -228,7 +259,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
             if (toRemove.length !== 0) {
                 const recentlyClosed = this.Document !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined;
                 toRemove.forEach(doc => {
-                    leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
+                    // leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
                     Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc, true);
                     doc.embedContainer = undefined;
                     if (recentlyClosed && !dontAddToRemoved && doc.type !== DocumentType.LOADING) {
@@ -237,7 +268,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
                     }
                 });
                 if (targetDataDoc.isGroup && DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]).length < 2) {
-                    (DocumentManager.Instance.getFirstDocumentView(targetDataDoc)?.ComponentView as CollectionFreeFormView)?.promoteCollection();
+                    Array.from(targetDataDoc[DocViews])[0]?.ComponentView?.promoteCollection?.();
                 } else {
                     this.isAnyChildContentActive() && this._props.select(false);
                 }
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 15ce4c15f..16586d922 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,32 +1,34 @@
 import { IconLookup, IconProp } from '@fortawesome/fontawesome-svg-core';
+import { faCalendarDays } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { Popup } from 'browndash-components';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
+import { FaEdit } from 'react-icons/fa';
+import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
 import { Doc } from '../../fields/Doc';
 import { Cast, DocCast } from '../../fields/Types';
 import { DocUtils } from '../documents/Documents';
 import { CalendarManager } from '../util/CalendarManager';
-import { DragManager, dropActionType } from '../util/DragManager';
+import { DragManager } from '../util/DragManager';
+import { dropActionType } from '../util/DropActionTypes';
 import { IsFollowLinkScript } from '../util/LinkFollower';
 import { SelectionManager } from '../util/SelectionManager';
 import { SharingManager } from '../util/SharingManager';
 import { UndoManager, undoBatch } from '../util/UndoManager';
+import { PinProps } from './DocComponent';
 import './DocumentButtonBar.scss';
 import { ObservableReactComponent } from './ObservableReactComponent';
+import { TemplateMenu } from './TemplateMenu';
 import { TabDocView } from './collections/TabDocView';
 import { Colors } from './global/globalEnums';
 import { LinkPopup } from './linking/LinkPopup';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { DocumentView, DocumentViewInternal, OpenWhere } from './nodes/DocumentView';
 import { DashFieldView } from './nodes/formattedText/DashFieldView';
-import { PinProps } from './nodes/trails';
-import { faCalendarDays } from '@fortawesome/free-solid-svg-icons';
-import { Popup } from 'browndash-components';
-import { TemplateMenu } from './TemplateMenu';
-import { FaEdit } from 'react-icons/fa';
 
 @observer
 export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: any }> {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2a44a9739..009097b3b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -5,9 +5,10 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx'
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaUndo } from 'react-icons/fa';
-import { Utils, emptyFunction, lightOrDark, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { lightOrDark, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
+import { Utils, numberValue, emptyFunction } from '../../Utils';
 import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols';
 import { InkField } from '../../fields/InkField';
 import { ScriptField } from '../../fields/ScriptField';
@@ -34,7 +35,7 @@ import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
 import { ImageBox } from './nodes/ImageBox';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { identity } from 'lodash';
+import { Id } from '../../fields/FieldSymbols';
 
 interface DocumentDecorationsProps {
     PanelWidth: number;
@@ -44,6 +45,7 @@ interface DocumentDecorationsProps {
 }
 @observer
 export class DocumentDecorations extends ObservableReactComponent<DocumentDecorationsProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: DocumentDecorations;
     private _resizeHdlId = '';
     private _keyinput = React.createRef<HTMLInputElement>();
@@ -123,7 +125,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             this._titleControlString = this._accumulatedTitle;
         } else if (this._titleControlString.startsWith('$')) {
             if (this._accumulatedTitle.startsWith('-->#')) {
-                SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`)));
+                SelectionManager.Docs.forEach(doc => {
+                    doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`);
+                });
             }
             const titleFieldKey = this._titleControlString.substring(1);
             UndoManager.RunInBatch(
@@ -149,7 +153,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
 
     onContainerDown = (e: React.PointerEvent) => {
         const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document);
-        if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
+        if (effectiveLayoutAcl === AclAdmin || effectiveLayoutAcl === AclEdit || effectiveLayoutAcl === AclAugment) {
             setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), emptyFunction, emptyFunction);
             e.stopPropagation();
         }
@@ -157,13 +161,13 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
 
     onTitleDown = (e: React.PointerEvent) => {
         const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document);
-        if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
+        if (effectiveLayoutAcl === AclAdmin || effectiveLayoutAcl === AclEdit || effectiveLayoutAcl === AclAugment) {
             setupMoveUpEvents(
                 this,
                 e,
                 e => this.onBackgroundMove(true, e),
                 emptyFunction,
-                action(e => {
+                action(() => {
                     const selected = SelectionManager.Views.length === 1 ? SelectionManager.Docs[0] : undefined;
                     !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString);
                     this._editingTitle = true;
@@ -182,7 +186,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
         const dragDocView = SelectionManager.Views[0];
         const effectiveLayoutAcl = GetEffectiveAcl(dragDocView.Document);
-        if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment) {
+        if (effectiveLayoutAcl !== AclAdmin && effectiveLayoutAcl !== AclEdit && effectiveLayoutAcl !== AclAugment) {
             return false;
         }
         const containers = new Set<Doc | undefined>();
@@ -205,7 +209,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             e.x,
             e.y,
             {
-                dragComplete: action(e => (this._hidden = false)),
+                dragComplete: action(() => {
+                    this._hidden = false;
+                }),
                 hideSource: true,
             }
         );
@@ -217,15 +223,16 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
         const views = SelectionManager.Views.filter(v => v && v._props.renderDepth > 0);
         if (forceDeleteOrIconify === false && this._iconifyBatch) return;
-        this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false;
-        var iconifyingCount = views.length;
+        this._deleteAfterIconify = !!(forceDeleteOrIconify || this._iconifyBatch);
+        let iconifyingCount = views.length;
         const finished = action((force?: boolean) => {
             if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
                 if (this._deleteAfterIconify) {
                     views.forEach(iconView => {
-                        Doc.setNativeView(iconView.Document);
-                        if (iconView.Document.activeFrame) {
-                            iconView.Document.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
+                        const iconViewDoc = iconView.Document;
+                        Doc.setNativeView(iconViewDoc);
+                        if (iconViewDoc.activeFrame) {
+                            iconViewDoc.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
                         } else {
                             iconView._props.removeDocument?.(iconView.Document);
                         }
@@ -240,6 +247,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             (document.activeElement as any).blur?.();
             this._iconifyBatch = UndoManager.StartBatch(forceDeleteOrIconify ? 'delete selected docs' : 'iconifying');
         } else {
+            // eslint-disable-next-line no-param-reassign
             forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes
         }
 
@@ -268,7 +276,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 // open same document in new tab
                 CollectionDockingView.ToggleSplit(selectedDocs[0].Document, OpenWhereMod.right);
             } else {
-                var openDoc = selectedDocs[0].Document;
+                let openDoc = selectedDocs[0].Document;
                 if (openDoc.layout_fieldKey === 'layout_icon') {
                     openDoc = Doc.GetEmbeddings(openDoc).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc);
                     Doc.deiconifyView(openDoc);
@@ -294,7 +302,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
      */
     @action
     onRadiusDown = (e: React.PointerEvent): void => {
-        SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement());
+        SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()?.[Id]);
         this._isRounding = true;
         this._resizeUndo = UndoManager.StartBatch('DocDecs set radius');
         setupMoveUpEvents(
@@ -304,14 +312,14 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 const [x, y] = [this.Bounds.x + 3, this.Bounds.y + 3];
                 const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
                 const dist = e.clientX < x && e.clientY < y ? 0 : Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y));
-                SelectionManager.Docs.map(doc => {
+                SelectionManager.Docs.forEach(doc => {
                     const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2);
                     const radius = Math.min(1, dist / maxDist) * docMax; // set radius based on ratio of drag distance to half diagonal distance of bounding box
                     doc._layout_borderRounding = `${radius}px`;
                 });
                 return false;
             },
-            action(e => {
+            action(() => {
                 SnappingManager.SetIsResizing(undefined);
                 this._isRounding = false;
                 this._resizeUndo?.end();
@@ -329,17 +337,18 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             e,
             returnFalse, // don't care about move or up event,
             emptyFunction, // just care about whether we get a click event
-            e => UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => Doc.toggleLockedPosition(doc)), 'toggleBackground')
+            () => UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => Doc.toggleLockedPosition(doc)), 'toggleBackground')
         );
         e.stopPropagation();
     };
 
     setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
+        const selDoc = seldocview.Document;
         const newloccentern = seldocview.screenToContentsTransform().transformPoint(rotCenter[0], rotCenter[1]);
         const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
         const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI);
-        seldocview.Document._rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
-        seldocview.Document._rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
+        selDoc._rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
+        selDoc._rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
     };
 
     @action
@@ -351,8 +360,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             e,
             (e: PointerEvent, down: number[], delta: number[]) => // return false to keep getting events
                 this.setRotateCenter(seldocview, [this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]]) as any as boolean, 
-            action(e => (this._isRotating = false)), // upEvent
-            action(e => (seldocview.Document._rotation_centerX = seldocview.Document._rotation_centerY = 0)),
+            action(() => { this._isRotating = false; }), // upEvent
+            action(() => { seldocview.Document._rotation_centerX = seldocview.Document._rotation_centerY = 0; }),
             true
         ); // prettier-ignore
         e.stopPropagation();
@@ -418,13 +427,15 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 this._isRotating = false;
                 rotateUndo?.end();
             }), // upEvent
-            action(e => (this._showRotCenter = !this._showRotCenter)) // clickEvent
+            action(() => {
+                this._showRotCenter = !this._showRotCenter;
+            }) // clickEvent
         );
     };
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
+        SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()?.[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
         setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
         e.stopPropagation();
         const id = (this._resizeHdlId = e.currentTarget.className);
@@ -453,12 +464,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const tl = docView.screenToContentsTransform().inverse().transformPoint(0, 0);
         return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]);
     };
-    onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
+    onPointerMove = (e: PointerEvent): boolean => {
         const first = SelectionManager.Views[0];
         const effectiveAcl = GetEffectiveAcl(first.Document);
-        if (!(effectiveAcl == AclAdmin || effectiveAcl == AclEdit || effectiveAcl == AclAugment)) return false;
+        if (!(effectiveAcl === AclAdmin || effectiveAcl === AclEdit || effectiveAcl === AclAugment)) return false;
         if (!first) return false;
-        var fixedAspect = Doc.NativeAspect(first.layoutDoc);
+        const fixedAspect = Doc.NativeAspect(first.layoutDoc);
         const dragHdl = this._resizeHdlId.split(' ')[0].replace('documentDecorations-', '').replace('Resizer', '');
 
         const thisPt = // do snapping of drag point
@@ -476,7 +487,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             const scaleAspect = {x:scale.x === 1 && fixedAspect ? scale.y : scale.x, y: scale.x !== 1 && fixedAspect ? scale.x : scale.y};
             SelectionManager.Views.forEach(docView => 
                 this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey:e.ctrlKey })); // prettier-ignore
-            await new Promise<any>(res => setTimeout(() => res(this._interactionLock = undefined)));
+            await new Promise<any>(res => { setTimeout(() => { res(this._interactionLock = undefined)})});
         }); // prettier-ignore
 
         return false;
@@ -553,10 +564,11 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
 
             doc._layout_modificationDate = new DateField();
             if (scale.y !== 1) {
-                docView.layoutDoc._layout_autoHeight = undefined;
+                const docLayout = docView.layoutDoc;
+                docLayout._layout_autoHeight = undefined;
                 if (docView.layoutDoc._layout_autoHeight) {
                     // if autoHeight is still on because of a prototype
-                    docView.layoutDoc._layout_autoHeight = false; // then don't inherit, but explicitly set it to false
+                    docLayout._layout_autoHeight = false; // then don't inherit, but explicitly set it to false
                 }
             }
         }
@@ -586,15 +598,17 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     };
 
     @action
-    onPointerUp = (e: PointerEvent): void => {
+    onPointerUp = (): void => {
         SnappingManager.SetIsResizing(undefined);
         SnappingManager.clearSnapLines();
         this._resizeHdlId = '';
         this._resizeUndo?.end();
 
         // detect layout_autoHeight gesture and apply
-        SelectionManager.Views.forEach(view => NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true));
-        //need to change points for resize, or else rotation/control points will fail.
+        SelectionManager.Views.forEach(view => {
+            NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true);
+        });
+        // need to change points for resize, or else rotation/control points will fail.
         this._inkDragDocs
             .map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
             .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
@@ -613,7 +627,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         if (SelectionManager.Views.length === 1) {
             const selected = SelectionManager.Views[0];
             if (this._titleControlString.startsWith('$')) {
-                return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
+                return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as FieldType) || '-unset-';
             }
             return this._accumulatedTitle;
         }
@@ -648,7 +662,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const acl = GetEffectiveAcl(!this._showLayoutAcl ? Doc.GetProto(seldocview.Document) : seldocview.Document);
         const docShareMode = HierarchyMapping.get(acl)!.name;
         const shareMode = StrCast(docShareMode);
-        var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
+        const shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
 
         // hide the decorations if the parent chooses to hide it or if the document itself hides it
         const hideDecorations = SnappingManager.IsResizing || seldocview._props.hideDecorations || seldocview.Document.layout_hideDecorations;
@@ -687,7 +701,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const bounds = this.ClippedBounds;
         const useLock = bounds.r - bounds.x > 135;
         const useRotation = !hideResizers && seldocview.Document.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
-        const rotation = SelectionManager.Views.length == 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0;
+        const rotation = SelectionManager.Views.length === 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0;
 
         // Radius constants
         const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
@@ -727,11 +741,13 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                     name="dynbox"
                     autoComplete="on"
                     value={hideTitle ? '' : this._accumulatedTitle}
-                    onBlur={action((e: React.FocusEvent) => {
+                    onBlur={action(() => {
                         this._editingTitle = false;
                         this.titleBlur();
                     })}
-                    onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
+                    onChange={action(e => {
+                        !hideTitle && (this._accumulatedTitle = e.target.value);
+                    })}
                     onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
                     onPointerDown={e => e.stopPropagation()}
                 />
@@ -789,8 +805,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                     display: hideDeleteButton && hideTitle && hideOpenButton ? 'none' : undefined,
                                 }}
                                 onPointerDown={this.onContainerDown}>
-                                {hideDeleteButton ? null : topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')}
-                                {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')}
+                                {hideDeleteButton ? null : topBtn('close', 'times', undefined, () => this.onCloseClick(true), 'Close')}
+                                {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, () => this.onCloseClick(undefined), 'Minimize')}
                                 {titleArea}
                                 {hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')}
                             </div>
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 0521c4a4b..d3cebd1c3 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -1,10 +1,12 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { action, computed, makeObservable, observable, ObservableMap } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
 import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai';
 import { CiCircleRemove } from 'react-icons/ci';
-import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, LinkedTo, StrListCast } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
@@ -58,7 +60,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
 
     @computed get mapActiveFiltersToFacets() {
         const filters = new Map<string, string>();
-        //this.targetDoc.docFilters
+        // this.targetDoc.docFilters
         this.activeFilters.map(filter => filters.set(filter.split(Doc.FilterSep)[1], filter.split(Doc.FilterSep)[0]));
         return filters;
     }
@@ -73,7 +75,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
     // ["#tags", "width", "height"]
     //
     @computed get activeFacetHeaders() {
-        const activeHeaders = new Array();
+        const activeHeaders = [] as string[];
         this.activeFilters.map(filter => activeHeaders.push(filter.split(Doc.FilterSep)[0]));
 
         return activeHeaders;
@@ -83,19 +85,20 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         const valueSet = new Set<string>(childFilters.map(filter => filter.split(Doc.FilterSep)[1]));
         let rtFields = 0;
         let subDocs = childDocs;
-        let gatheredDocs = [] as Doc[];
+        const gatheredDocs = [] as Doc[];
         if (subDocs.length > 0) {
             let newarray: Doc[] = [];
             while (subDocs.length > 0) {
                 newarray = [];
+                // eslint-disable-next-line no-loop-func
                 subDocs.forEach(t => {
                     gatheredDocs.push(t);
                     const facetVal = t[facetKey];
                     if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++;
-                    facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field));
-                    (facetVal === true || facetVal == false) && valueSet.add(Field.toString(!facetVal));
+                    facetVal !== undefined && valueSet.add(Field.toString(facetVal as FieldType));
+                    (facetVal === true || facetVal === false) && valueSet.add(Field.toString(!facetVal));
                     const fieldKey = Doc.LayoutFieldKey(t);
-                    const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView');
+                    const annos = !Field.toString(Doc.LayoutField(t) as FieldType).includes('CollectionView');
                     DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
                     annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
                 });
@@ -115,7 +118,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
 
     // @observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>();
     @observable _chosenFacetsCollapse = new ObservableMap<string, boolean>();
-    @observable _collapseReturnKeys = new Array();
+    @observable _collapseReturnKeys = [] as string[];
 
     // this computed function gets the active filters and maps them to their headers
     //
@@ -130,11 +133,11 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                 const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.Document.childFilters));
 
                 let nonNumbers = 0;
-                let minVal = Number.MAX_VALUE,
-                    maxVal = -Number.MAX_VALUE;
-                facetValues.strings.map(val => {
+                let minVal = Number.MAX_VALUE;
+                let maxVal = -Number.MAX_VALUE;
+                facetValues.strings.forEach(val => {
                     const num = val ? Number(val) : Number.NaN;
-                    if (Number.isNaN(num)) {
+                    if (isNaN(num)) {
                         val && nonNumbers++;
                     } else {
                         minVal = Math.min(num, minVal);
@@ -144,14 +147,14 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
 
                 if (facetHeader === 'text') {
                     return { facetHeader, renderType: 'text' };
-                } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
+                }
+                if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
                     const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1));
                     const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05)));
-                    const ranged = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter
-                    return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] };
-                } else {
-                    return { facetHeader, renderType: 'checkbox' };
+                    const ranged: number[] | undefined = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter
+                    return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged || [extendedMinVal, extendedMaxVal] };
                 }
+                return { facetHeader, renderType: 'checkbox' };
             })
         );
     }
@@ -171,17 +174,17 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
     sortingCurrentFacetValues = (facetHeader: string) => {
         this._collapseReturnKeys.splice(0);
 
-        Array.from(this.activeRenderedFacetInfos.keys()).map(renderInfo => {
+        Array.from(this.activeRenderedFacetInfos.keys()).forEach(renderInfo => {
             if (renderInfo.renderType === 'range' && renderInfo.facetHeader === facetHeader && renderInfo.range) {
-                this._collapseReturnKeys.push(renderInfo.range.map(number => number.toFixed(2)));
+                this._collapseReturnKeys.push(...renderInfo.range.map(number => number.toFixed(2)));
             }
         });
 
-        for (var key of this.facetValues(facetHeader)) {
+        this.facetValues(facetHeader).forEach(key => {
             if (this.mapActiveFiltersToFacets.get(key)) {
                 this._collapseReturnKeys.push(key);
             }
-        }
+        });
 
         return <div className=" filterbox-collpasedAndActive">{this._collapseReturnKeys.join(',  ')}</div>;
     };
@@ -198,7 +201,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
             );
         else
             allCollectionDocs.forEach(child => {
-                const fieldVal = child[facetHeader] as Field;
+                const fieldVal = child[facetHeader] as FieldType;
                 if (!(fieldVal instanceof List)) {
                     // currently we have no good way of filtering based on a field that is a list
                     set.add(Field.toString(fieldVal));
@@ -209,7 +212,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
 
         let nonNumbers = 0;
 
-        facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++);
+        facetValues.map(val => isNaN(Number(val)) && nonNumbers++);
         return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2));
     };
 
@@ -218,7 +221,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
             <div className="filterBox-treeView">
                 <div className="filterBox-select">
                     <div style={{ width: '100%' }}>
-                        <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder={true} placeholder="add a filter" addedFields={['acl-Guest', LinkedTo]} />
+                        <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl-Guest', LinkedTo]} />
                     </div>
                     {/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */}
                     {/* <div className="filterBox-select-bool"> 
@@ -234,34 +237,31 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
 
                 <div className="filterBox-tree" key="tree">
                     {Array.from(this.activeRenderedFacetInfos.keys()).map(
-                        (
-                            renderInfo // iterato over activeFacetRenderInfos  ==> renderInfo  which you can renderInfo.facetHeader
-                        ) => (
+                        // iterate over activeFacetRenderInfos  ==> renderInfo  which you can renderInfo.facetHeader
+                        renderInfo => (
                             <div>
                                 <div className="filterBox-facetHeader">
                                     <div className="filterBox-facetHeader-Header"> </div>
                                     {renderInfo.facetHeader.charAt(0).toUpperCase() + renderInfo.facetHeader.slice(1)}
-
                                     <div
                                         className="filterBox-facetHeader-collapse"
-                                        onClick={action(e => {
+                                        onClick={action(() => {
                                             const collapseBoolValue = this._chosenFacetsCollapse.get(renderInfo.facetHeader);
                                             this._chosenFacetsCollapse.set(renderInfo.facetHeader, !collapseBoolValue);
                                         })}>
                                         {this._chosenFacetsCollapse.get(renderInfo.facetHeader) ? <AiOutlinePlusSquare /> : <AiOutlineMinusSquare />}
                                     </div>
-
                                     <div
                                         className="filterBox-facetHeader-remove"
-                                        onClick={action(e => {
+                                        onClick={action(() => {
                                             if (renderInfo.facetHeader === 'text') {
                                                 Doc.setDocFilter(this.Document, renderInfo.facetHeader, 'match', 'remove');
                                             } else {
-                                                for (var key of this.facetValues(renderInfo.facetHeader)) {
+                                                this.facetValues(renderInfo.facetHeader).forEach((key: string) => {
                                                     if (this.mapActiveFiltersToFacets.get(key)) {
                                                         Doc.setDocFilter(this.Document, renderInfo.facetHeader, key, 'remove');
                                                     }
-                                                }
+                                                });
                                             }
                                             this._selectedFacetHeaders.delete(renderInfo.facetHeader);
                                             this._chosenFacetsCollapse.delete(renderInfo.facetHeader);
@@ -292,7 +292,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                 return (
                     <input
                         key={this.Document[Id]}
-                        placeholder={'enter text to match'}
+                        placeholder="enter text to match"
                         defaultValue={
                             StrListCast(this.Document._childFilters)
                                 .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader)
@@ -312,7 +312,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                                 style={{ width: 20, marginLeft: 20 }}
                                 checked={['check', 'exists'].includes(
                                     StrListCast(this.Document._childFilters)
-                                        .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue)
+                                        .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] === facetValue)
                                         ?.split(Doc.FilterSep)[2] ?? ''
                                 )}
                                 type={type}
@@ -324,55 +324,56 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                 });
 
             case 'range':
-                const domain = renderInfoDomain;
-                const range = renderInfoRange;
-                if (domain) {
-                    return (
-                        <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}>
-                            <Slider
-                                mode={2}
-                                step={Math.min(1, 0.1 * (domain[1] - domain[0]))}
-                                domain={[domain[0], domain[1]]} // -1000, 1000
-                                rootStyle={{ position: 'relative', width: '100%' }}
-                                onChange={values => Doc.setDocRangeFilter(this.Document, facetHeader, values)}
-                                values={renderInfoRange!}>
-                                <Rail>{railProps => <TooltipRail {...railProps} />}</Rail>
-                                <Handles>
-                                    {({ handles, activeHandleID, getHandleProps }) => (
-                                        <div className="slider-handles">
-                                            {handles.map((handle, i) => {
-                                                // const value = i === 0 ? defaultValues[0] : defaultValues[1];
-                                                return (
+                {
+                    const domain = renderInfoDomain;
+                    if (domain) {
+                        return (
+                            <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}>
+                                <Slider
+                                    mode={2}
+                                    step={Math.min(1, 0.1 * (domain[1] - domain[0]))}
+                                    domain={[domain[0], domain[1]]} // -1000, 1000
+                                    rootStyle={{ position: 'relative', width: '100%' }}
+                                    onChange={values => Doc.setDocRangeFilter(this.Document, facetHeader, values)}
+                                    values={renderInfoRange!}>
+                                    <Rail>{railProps => <TooltipRail {...railProps} />}</Rail>
+                                    <Handles>
+                                        {({ handles, activeHandleID, getHandleProps }) => (
+                                            <div className="slider-handles">
+                                                {handles.map(handle => (
+                                                    // const value = i === 0 ? defaultValues[0] : defaultValues[1];
                                                     <div>
                                                         <Handle key={handle.id} handle={handle} domain={domain} isActive={handle.id === activeHandleID} getHandleProps={getHandleProps} />
                                                     </div>
-                                                );
-                                            })}
-                                        </div>
-                                    )}
-                                </Handles>
-                                <Tracks left={false} right={false}>
-                                    {({ tracks, getTrackProps }) => (
-                                        <div className="slider-tracks">
-                                            {tracks.map(({ id, source, target }) => (
-                                                <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} />
-                                            ))}
-                                        </div>
-                                    )}
-                                </Tracks>
-                                <Ticks count={5}>
-                                    {({ ticks }) => (
-                                        <div className="slider-ticks">
-                                            {ticks.map(tick => (
-                                                <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} />
-                                            ))}
-                                        </div>
-                                    )}
-                                </Ticks>
-                            </Slider>
-                        </div>
-                    );
+                                                ))}
+                                            </div>
+                                        )}
+                                    </Handles>
+                                    <Tracks left={false} right={false}>
+                                        {({ tracks, getTrackProps }) => (
+                                            <div className="slider-tracks">
+                                                {tracks.map(({ id, source, target }) => (
+                                                    <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} />
+                                                ))}
+                                            </div>
+                                        )}
+                                    </Tracks>
+                                    <Ticks count={5}>
+                                        {({ ticks }) => (
+                                            <div className="slider-ticks">
+                                                {ticks.map(tick => (
+                                                    <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} />
+                                                ))}
+                                            </div>
+                                        )}
+                                    </Ticks>
+                                </Slider>
+                            </div>
+                        );
+                    }
                 }
+                break;
+            default:
 
             // case 'range'
             // return <Slider ...
@@ -386,5 +387,6 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
             //     <dimain changing handles >
             //  <?div
         }
+        return undefined;
     }
 }
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 86b9f5e40..46fa04d72 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -2,11 +2,13 @@ import * as fitCurve from 'fit-curve';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
 import { Doc, Opt } from '../../fields/Doc';
 import { InkData, InkTool } from '../../fields/InkField';
 import { BoolCast, NumCast } from '../../fields/Types';
 import MobileInkOverlay from '../../mobile/MobileInkOverlay';
+import { Gestures } from '../../pen-gestures/GestureTypes';
 import { GestureUtils } from '../../pen-gestures/GestureUtils';
 import { MobileInkOverlayContent } from '../../server/Message';
 import { InteractionUtils } from '../util/InteractionUtils';
@@ -29,15 +31,22 @@ import {
     SetActiveInkWidth,
 } from './InkingStroke';
 import { ObservableReactComponent } from './ObservableReactComponent';
-import { checkInksToGroup } from './global/globalScripts';
 import { DocumentView } from './nodes/DocumentView';
 
+export enum ToolglassTools {
+    InkToText = 'inktotext',
+    IgnoreGesture = 'ignoregesture',
+    RadialMenu = 'radialmenu',
+    None = 'none',
+}
 interface GestureOverlayProps {
     isActive: boolean;
 }
 @observer
 export class GestureOverlay extends ObservableReactComponent<React.PropsWithChildren<GestureOverlayProps>> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: GestureOverlay;
+    // eslint-disable-next-line no-use-before-define
     static Instances: GestureOverlay[] = [];
 
     public static set RecognizeGestures(active) {
@@ -47,7 +56,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         return BoolCast(Doc.UserDoc().recognizeGestures);
     }
 
-    @observable public InkShape: Opt<GestureUtils.Gestures> = undefined;
+    @observable public InkShape: Opt<Gestures> = undefined;
     @observable public SavedColor?: string = undefined;
     @observable public SavedWidth?: number = undefined;
     @observable public Tool: ToolglassTools = ToolglassTools.None;
@@ -55,9 +64,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
 
     @observable private _thumbX?: number = undefined;
     @observable private _thumbY?: number = undefined;
-    @observable private _selectedIndex: number = -1;
-    @observable private _menuX: number = -300;
-    @observable private _menuY: number = -300;
     @observable private _pointerY?: number = undefined;
     @observable private _points: { X: number; Y: number }[] = [];
     @observable private _strokes: InkData[] = [];
@@ -117,9 +123,9 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
             const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
             if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
                 switch (this.Tool) {
-                    case ToolglassTools.RadialMenu:
-                        return true;
-                }
+                    case ToolglassTools.RadialMenu: return true;
+                    default: 
+                } // prettier-ignore
             }
         }
         return false;
@@ -128,8 +134,8 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     @action primCreated() {
         if (!this.KeepPrimitiveMode) {
             this.InkShape = undefined;
-            //get out of ink mode after each stroke=
-            //if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
+            // get out of ink mode after each stroke=
+            // if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
             Doc.ActiveTool = InkTool.None;
             // SetActiveArrowStart('none');
             // SetActiveArrowEnd('none');
@@ -142,7 +148,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
             const B = this.svgBounds;
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
 
-            //if any of the shape is activated in the CollectionFreeFormViewChrome
+            // if any of the shape is activated in the CollectionFreeFormViewChrome
             if (this.InkShape) {
                 this.makeBezierPolygon(this.InkShape, false);
                 this.dispatchGesture(this.InkShape);
@@ -155,16 +161,17 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                 let actionPerformed = false;
                 if (GestureOverlay.RecognizeGestures && result && result.Score > 0.7) {
                     switch (result.Name) {
-                        case GestureUtils.Gestures.Line:
-                        case GestureUtils.Gestures.Triangle:
-                        case GestureUtils.Gestures.Rectangle:
-                        case GestureUtils.Gestures.Circle:
+                        case Gestures.Line:
+                        case Gestures.Triangle:
+                        case Gestures.Rectangle:
+                        case Gestures.Circle:
                             this.makeBezierPolygon(result.Name, true);
                             actionPerformed = this.dispatchGesture(result.Name);
                             break;
-                        case GestureUtils.Gestures.Scribble:
+                        case Gestures.Scribble:
                             console.log('scribble');
                             break;
+                        default:
                     }
                 }
 
@@ -178,21 +185,20 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                     const controlPoints: { X: number; Y: number }[] = [];
 
                     const bezierCurves = (fitCurve as any)(newPoints, 10);
-                    for (const curve of bezierCurves) {
+                    Array.from(bezierCurves).forEach((curve: any) => {
                         controlPoints.push({ X: curve[0][0], Y: curve[0][1] });
                         controlPoints.push({ X: curve[1][0], Y: curve[1][1] });
                         controlPoints.push({ X: curve[2][0], Y: curve[2][1] });
                         controlPoints.push({ X: curve[3][0], Y: curve[3][1] });
-                    }
+                    });
                     const dist = Math.sqrt(
                         (controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)
                     );
+                    // eslint-disable-next-line prefer-destructuring
                     if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0];
                     this._points.length = 0;
                     this._points.push(...controlPoints);
-                    this.dispatchGesture(GestureUtils.Gestures.Stroke);
-                    // TODO: nda - check inks to group here
-                    checkInksToGroup();
+                    this.dispatchGesture(Gestures.Stroke);
                 }
             }
         }
@@ -202,34 +208,34 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     makeBezierPolygon = (shape: string, gesture: boolean) => {
         const xs = this._points.map(p => p.X);
         const ys = this._points.map(p => p.Y);
-        var right = Math.max(...xs);
-        var left = Math.min(...xs);
-        var bottom = Math.max(...ys);
-        var top = Math.min(...ys);
+        let right = Math.max(...xs);
+        let left = Math.min(...xs);
+        let bottom = Math.max(...ys);
+        let top = Math.min(...ys);
         const firstx = this._points[0].X;
         const firsty = this._points[0].Y;
-        var lastx = this._points[this._points.length - 2].X;
-        var lasty = this._points[this._points.length - 2].Y;
-        var fourth = (lastx - firstx) / 4;
+        let lastx = this._points[this._points.length - 2].X;
+        let lasty = this._points[this._points.length - 2].Y;
+        let fourth = (lastx - firstx) / 4;
         if (isNaN(fourth) || fourth === 0) {
             fourth = 0.01;
         }
-        var m = (lasty - firsty) / (lastx - firstx);
+        let m = (lasty - firsty) / (lastx - firstx);
         if (isNaN(m) || m === 0) {
             m = 0.01;
         }
-        const b = firsty - m * firstx;
+        // const b = firsty - m * firstx;
         if (shape === 'noRec') {
             return false;
         }
         if (!gesture) {
-            //if shape options is activated in inkOptionMenu
-            //take second to last point because _point[length-1] is _points[0]
+            // if shape options is activated in inkOptionMenu
+            // take second to last point because _point[length-1] is _points[0]
             right = this._points[this._points.length - 2].X;
             left = this._points[0].X;
             bottom = this._points[this._points.length - 2].Y;
             top = this._points[0].Y;
-            if (shape !== GestureUtils.Gestures.Arrow && shape !== GestureUtils.Gestures.Line && shape !== GestureUtils.Gestures.Circle) {
+            if (shape !== Gestures.Arrow && shape !== Gestures.Line && shape !== Gestures.Circle) {
                 if (left > right) {
                     const temp = right;
                     right = left;
@@ -244,7 +250,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         }
         this._points.length = 0;
         switch (shape) {
-            case GestureUtils.Gestures.Rectangle:
+            case Gestures.Rectangle:
                 this._points.push({ X: left, Y: top });
                 this._points.push({ X: left, Y: top });
                 this._points.push({ X: right, Y: top });
@@ -267,7 +273,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
 
                 break;
 
-            case GestureUtils.Gestures.Triangle:
+            case Gestures.Triangle:
                 this._points.push({ X: left, Y: bottom });
                 this._points.push({ X: left, Y: bottom });
 
@@ -285,39 +291,40 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                 this._points.push({ X: left, Y: bottom });
 
                 break;
-            case GestureUtils.Gestures.Circle:
-                // Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%,
-                // making the curves indistinguishable from a circle.
-                // Source: https://spencermortensen.com/articles/bezier-circle/
-                const c = 0.551915024494;
-                const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
-                const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
-                const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
-
-                // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve.
-                this._points.push({ X: centerX, Y: centerY + radius });
-                this._points.push({ X: centerX + c * radius, Y: centerY + radius });
-                this._points.push({ X: centerX + radius, Y: centerY + c * radius });
-                this._points.push({ X: centerX + radius, Y: centerY });
-
-                this._points.push({ X: centerX + radius, Y: centerY });
-                this._points.push({ X: centerX + radius, Y: centerY - c * radius });
-                this._points.push({ X: centerX + c * radius, Y: centerY - radius });
-                this._points.push({ X: centerX, Y: centerY - radius });
-
-                this._points.push({ X: centerX, Y: centerY - radius });
-                this._points.push({ X: centerX - c * radius, Y: centerY - radius });
-                this._points.push({ X: centerX - radius, Y: centerY - c * radius });
-                this._points.push({ X: centerX - radius, Y: centerY });
-
-                this._points.push({ X: centerX - radius, Y: centerY });
-                this._points.push({ X: centerX - radius, Y: centerY + c * radius });
-                this._points.push({ X: centerX - c * radius, Y: centerY + radius });
-                this._points.push({ X: centerX, Y: centerY + radius });
-
+            case Gestures.Circle:
+                {
+                    // Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%,
+                    // making the curves indistinguishable from a circle.
+                    // Source: https://spencermortensen.com/articles/bezier-circle/
+                    const c = 0.551915024494;
+                    const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
+                    const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
+                    const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
+
+                    // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve.
+                    this._points.push({ X: centerX, Y: centerY + radius });
+                    this._points.push({ X: centerX + c * radius, Y: centerY + radius });
+                    this._points.push({ X: centerX + radius, Y: centerY + c * radius });
+                    this._points.push({ X: centerX + radius, Y: centerY });
+
+                    this._points.push({ X: centerX + radius, Y: centerY });
+                    this._points.push({ X: centerX + radius, Y: centerY - c * radius });
+                    this._points.push({ X: centerX + c * radius, Y: centerY - radius });
+                    this._points.push({ X: centerX, Y: centerY - radius });
+
+                    this._points.push({ X: centerX, Y: centerY - radius });
+                    this._points.push({ X: centerX - c * radius, Y: centerY - radius });
+                    this._points.push({ X: centerX - radius, Y: centerY - c * radius });
+                    this._points.push({ X: centerX - radius, Y: centerY });
+
+                    this._points.push({ X: centerX - radius, Y: centerY });
+                    this._points.push({ X: centerX - radius, Y: centerY + c * radius });
+                    this._points.push({ X: centerX - c * radius, Y: centerY + radius });
+                    this._points.push({ X: centerX, Y: centerY + radius });
+                }
                 break;
 
-            case GestureUtils.Gestures.Line:
+            case Gestures.Line:
                 if (Math.abs(firstx - lastx) < 10 && Math.abs(firsty - lasty) > 10) {
                     lastx = firstx;
                 }
@@ -330,28 +337,32 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                 this._points.push({ X: lastx, Y: lasty });
                 this._points.push({ X: lastx, Y: lasty });
                 break;
-            case GestureUtils.Gestures.Arrow:
-                const x1 = left;
-                const y1 = top;
-                const x2 = right;
-                const y2 = bottom;
-                const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2));
-                const L2 = L1 / 5;
-                const angle = 0.785398;
-                const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle));
-                const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle));
-                const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle));
-                const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle));
-                this._points.push({ X: x1, Y: y1 });
-                this._points.push({ X: x2, Y: y2 });
-                this._points.push({ X: x3, Y: y3 });
-                this._points.push({ X: x4, Y: y4 });
-                this._points.push({ X: x2, Y: y2 });
+            case Gestures.Arrow:
+                {
+                    const x1 = left;
+                    const y1 = top;
+                    const x2 = right;
+                    const y2 = bottom;
+                    const L1 = Math.sqrt(Math.abs(x1 - x2) ** 2 + Math.abs(y1 - y2) ** 2);
+                    const L2 = L1 / 5;
+                    const angle = 0.785398;
+                    const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle));
+                    const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle));
+                    const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle));
+                    const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle));
+                    this._points.push({ X: x1, Y: y1 });
+                    this._points.push({ X: x2, Y: y2 });
+                    this._points.push({ X: x3, Y: y3 });
+                    this._points.push({ X: x4, Y: y4 });
+                    this._points.push({ X: x2, Y: y2 });
+                }
+                break;
+            default:
         }
         return false;
     };
 
-    dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, text?: any) => {
+    dispatchGesture = (gesture: Gestures, stroke?: InkData, text?: any) => {
         const points = (stroke ?? this._points).slice();
         return (
             document.elementFromPoint(points[0].X, points[0].Y)?.dispatchEvent(
@@ -387,11 +398,11 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         const selView = GestureOverlay.DownDocView;
         const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?.screenToViewTransform().Scale || 1);
         const rect = this._overlayRef.current?.getBoundingClientRect();
-        const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true);
-        B.left = B.left - width / 2;
-        B.right = B.right + width / 2;
+        const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(this._points, true);
+        B.left -= width / 2;
+        B.right += width / 2;
         B.top = B.top - width / 2 - (rect?.y || 0);
-        B.bottom = B.bottom + width / 2;
+        B.bottom += width / 2;
         B.width += width;
         B.height += width;
         const fillColor = ActiveFillColor();
@@ -401,7 +412,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
             this._palette,
             [
                 this._strokes.map((l, i) => {
-                    const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(l, true);
+                    const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; // this.getBounds(l, true);
                     return (
                         <svg key={i} width={b.width} height={b.height} style={{ top: 0, left: 0, transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
                             {InteractionUtils.CreatePolyline(
@@ -414,7 +425,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                                 'miter',
                                 'round',
                                 ActiveInkBezierApprox(),
-                                'none' /*ActiveFillColor()*/,
+                                'none' /* ActiveFillColor() */,
                                 ActiveArrowStart(),
                                 ActiveArrowEnd(),
                                 ActiveArrowScale(),
@@ -441,7 +452,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                             'miter',
                             'round',
                             '',
-                            'none' /*ActiveFillColor()*/,
+                            'none' /* ActiveFillColor() */,
                             ActiveArrowStart(),
                             ActiveArrowEnd(),
                             ActiveArrowScale(),
@@ -529,19 +540,14 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     }
 }
 
-// export class
-
-export enum ToolglassTools {
-    InkToText = 'inktotext',
-    IgnoreGesture = 'ignoregesture',
-    RadialMenu = 'radialmenu',
-    None = 'none',
-}
-
 ScriptingGlobals.add('GestureOverlay', GestureOverlay);
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setToolglass(tool: any) {
-    runInAction(() => (GestureOverlay.Instance.Tool = tool));
+    runInAction(() => {
+        GestureOverlay.Instance.Tool = tool;
+    });
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) {
     runInAction(() => {
         GestureOverlay.Instance.SavedColor = ActiveInkColor();
@@ -554,6 +560,7 @@ ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowSta
         SetActiveDash(dash);
     });
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function resetPen() {
     runInAction(() => {
         SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? 'rgb(0, 0, 0)');
@@ -561,8 +568,9 @@ ScriptingGlobals.add(function resetPen() {
     });
 }, 'resets the pen tool');
 ScriptingGlobals.add(
+    // eslint-disable-next-line prefer-arrow-callback
     function createText(text: any, x: any, y: any) {
-        GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: x, Y: y }], text);
+        GestureOverlay.Instance.dispatchGesture(Gestures.Text, [{ X: x, Y: y }], text);
     },
     'creates a text document with inputted text and coordinates',
     '(text: any, x: any, y: any)'
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 2f64ea28c..5c923d301 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -67,7 +67,6 @@ export class KeyManager {
         // accumulate buffer of characters to insert in a new text note.  once the note is created, it will stop keyboard events from reaching this function.
         if (FormattedTextBox.SelectOnLoadChar) FormattedTextBox.SelectOnLoadChar = FormattedTextBox.SelectOnLoadChar + (e.key === 'Enter' ? '\n' : e.key);
         e.key === 'Control' && (CtrlKey = true);
-        //if (!Doc.noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true);
         const keyname = e.key && e.key.toLowerCase();
         this.handleGreedy(keyname);
 
@@ -89,10 +88,7 @@ export class KeyManager {
         control.preventDefault && e.preventDefault();
     });
 
-    private handleGreedy = action((keyname: string) => {
-        switch (keyname) {
-        }
-    });
+    private handleGreedy = action((/* keyname: string */) => {});
 
     nudge = (x: number, y: number, label: string) => {
         const nudgeable = SelectionManager.Views.some(dv => dv.CollectionFreeFormDocumentView?.nudge);
@@ -105,7 +101,7 @@ export class KeyManager {
             case 'u':
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
                     const ungroupings = SelectionManager.Views;
-                    UndoManager.RunInBatch(() => ungroupings.map(dv => (dv.layoutDoc.group = undefined)), 'ungroup');
+                    UndoManager.RunInBatch(() => ungroupings.forEach(dv => { dv.layoutDoc.group = undefined; }), 'ungroup');
                     SelectionManager.DeselectAll();
                 }
                 break;
@@ -124,41 +120,41 @@ export class KeyManager {
             case ' ':
                 // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
                 break;
-            case 'escape':
-                DocumentLinksButton.StartLink = undefined;
-                DocumentLinksButton.StartLinkView = undefined;
-                InkStrokeProperties.Instance._controlButton = false;
-                Doc.ActiveTool = InkTool.None;
-                DragManager.CompleteWindowDrag?.(true);
-                var doDeselect = true;
-                if (SnappingManager.IsDragging) {
-                    DragManager.AbortDrag();
-                } 
-                if (CollectionDockingView.Instance?.HasFullScreen) {
-                    CollectionDockingView.Instance?.CloseFullScreen();
-                } 
-                if (CollectionStackedTimeline.SelectingRegions.size) {
-                    CollectionStackedTimeline.StopSelecting();
-                    doDeselect = false;
-                } else {
-                    doDeselect = !ContextMenu.Instance.closeMenu();
-                }
-                if (doDeselect) {
-                    SelectionManager.DeselectAll();
-                    LightboxView.Instance.SetLightboxDoc(undefined);
+            case 'escape': {
+                    DocumentLinksButton.StartLink = undefined;
+                    DocumentLinksButton.StartLinkView = undefined;
+                    InkStrokeProperties.Instance._controlButton = false;
+                    Doc.ActiveTool = InkTool.None;
+                    DragManager.CompleteWindowDrag?.(true);
+                    let doDeselect = true;
+                    if (SnappingManager.IsDragging) {
+                        DragManager.AbortDrag();
+                    } 
+                    if (CollectionDockingView.Instance?.HasFullScreen) {
+                        CollectionDockingView.Instance?.CloseFullScreen();
+                    } 
+                    if (CollectionStackedTimeline.SelectingRegions.size) {
+                        CollectionStackedTimeline.StopSelecting();
+                        doDeselect = false;
+                    } else {
+                        doDeselect = !ContextMenu.Instance.closeMenu();
+                    }
+                    if (doDeselect) {
+                        SelectionManager.DeselectAll();
+                        LightboxView.Instance.SetLightboxDoc(undefined);
+                    }
+                    // DictationManager.Controls.stop();
+                    GoogleAuthenticationManager.Instance.cancel();
+                    SharingManager.Instance.close();
+                    if (!GroupManager.Instance.isOpen) SettingsManager.Instance.close();
+                    GroupManager.Instance.close();
+                    window.getSelection()?.empty();
+                    document.body.focus();
                 }
-                // DictationManager.Controls.stop();
-                GoogleAuthenticationManager.Instance.cancel();
-                SharingManager.Instance.close();
-                if (!GroupManager.Instance.isOpen) SettingsManager.Instance.close();
-                GroupManager.Instance.close();
-                window.getSelection()?.empty();
-                document.body.focus();
                 break;
-            case 'enter': {
-                //DocumentDecorations.Instance.onCloseClick(false);
+            case 'enter':
+                // DocumentDecorations.Instance.onCloseClick(false);
                 break;
-            }
             case 'delete':
             case 'backspace':
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
@@ -173,6 +169,7 @@ export class KeyManager {
             case 'arrowright': return this.nudge(1,0, 'nudge right');
             case 'arrowup':    return this.nudge(0, -1, 'nudge up');
             case 'arrowdown':  return this.nudge(0, 1, 'nudge down');
+            default:
         } // prettier-ignore
 
         return {
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index edc6b404b..0358344b9 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -6,7 +6,7 @@ import { ControlPoint, InkData, PointData } from '../../fields/InkField';
 import { List } from '../../fields/List';
 import { listSpec } from '../../fields/Schema';
 import { Cast } from '../../fields/Types';
-import { returnFalse, setupMoveUpEvents } from '../../Utils';
+import { returnFalse, setupMoveUpEvents } from '../../ClientUtils';
 import { SelectionManager } from '../util/SelectionManager';
 import { UndoManager } from '../util/UndoManager';
 import { Colors } from './global/globalEnums';
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index c20399698..f3a757f19 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -6,7 +6,8 @@ import { HandleLine, HandlePoint, InkData } from '../../fields/InkField';
 import { List } from '../../fields/List';
 import { listSpec } from '../../fields/Schema';
 import { Cast } from '../../fields/Types';
-import { emptyFunction, setupMoveUpEvents } from '../../Utils';
+import { emptyFunction } from '../../Utils';
+import { setupMoveUpEvents } from '../../ClientUtils';
 import { Transform } from '../util/Transform';
 import { UndoManager } from '../util/UndoManager';
 import { Colors } from './global/globalEnums';
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 51baaa23e..9f82fdb18 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -23,18 +23,18 @@
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { DashColor, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
 import { Doc } from '../../fields/Doc';
 import { InkData, InkField } from '../../fields/InkField';
 import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
-import { DashColor, returnFalse, setupMoveUpEvents } from '../../Utils';
 import { CognitiveServices } from '../cognitive_services/CognitiveServices';
 import { Docs } from '../documents/Documents';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
 import { ContextMenu } from './ContextMenu';
-import { ViewBoxAnnotatableComponent, ViewBoxBaseComponent, ViewBoxInterface } from './DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from './DocComponent';
 import { Colors } from './global/globalEnums';
 import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
 import './InkStroke.scss';
@@ -42,8 +42,9 @@ import { InkStrokeProperties } from './InkStrokeProperties';
 import { InkTangentHandles } from './InkTangentHandles';
 import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { PinProps, PresBox } from './nodes/trails';
+import { PresBox } from './nodes/trails';
 import { StyleProp } from './StyleProvider';
+
 const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 @observer
 export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -387,8 +388,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
             1.0,
             false,
             undefined,
-            undefined,
-            color === 'transparent' ? highlightColor : undefined
+            undefined
         );
         const higlightMargin = Math.min(12, Math.max(2, 0.3 * inkStrokeWidth));
         // Invisible polygonal line that enables the ink to be selected by the user.
@@ -415,8 +415,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 0.0,
                 false,
                 downHdlr,
-                mask,
-                highlightColor
+                mask
             );
         // bootsrap 3 style sheet sets line height to be 20px for default 14 point font size.
         // this attempts to figure out the lineHeight ratio by inquiring the body's lineHeight and dividing by the fontsize which should yield 1.428571429
@@ -438,7 +437,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 <svg
                     className="inkStroke"
                     style={{
-                        transform: isInkMask ? `rotate(-${NumCast(this._props.CollectionFreeFormDocumentView?.()._props.rotation ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+                        transform: isInkMask ? `rotate(-${NumCast(this._props.LocalRotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
                         // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
                         cursor: this._props.isSelected() ? 'default' : undefined,
                     }}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index ef4b5b4ca..c34554b56 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -4,7 +4,8 @@ import { Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils';
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
 import { Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { InkTool } from '../../fields/InkField';
@@ -127,7 +128,9 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
             DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
             if (this._history.lastElement().target !== target) this._history.push({ doc: lightDoc, target });
         } else if (!target && this._path.length) {
-            savedKeys.forEach(key => (lightDoc[key] = this._savedState[key]));
+            savedKeys.forEach(key => {
+                lightDoc[key] = this._savedState[key];
+            });
             this._path.pop();
         } else {
             this.SetLightboxDoc(target);
@@ -239,7 +242,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                     downx = e.clientX;
                     downy = e.clientY;
                 }}
-                onClick={e => Utils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}>
+                onClick={e => ClientUtils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}>
                 <div
                     className="lightboxView-contents"
                     style={{
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 58b8d255a..8a060d0e0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -7,7 +7,8 @@ import { action, computed, configure, makeObservable, observable, reaction, runI
 import { observer } from 'mobx-react';
 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 { ClientUtils, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { DocData } from '../../fields/DocSymbols';
 import { DocCast, StrCast } from '../../fields/Types';
@@ -18,10 +19,12 @@ import { Docs } from '../documents/Documents';
 import { CalendarManager } from '../util/CalendarManager';
 import { CaptureManager } from '../util/CaptureManager';
 import { DocumentManager } from '../util/DocumentManager';
-import { DragManager, dropActionType } from '../util/DragManager';
+import { DragManager } from '../util/DragManager';
+import { dropActionType } from '../util/DropActionTypes';
 import { GroupManager } from '../util/GroupManager';
 import { HistoryUtil } from '../util/History';
 import { Hypothesis } from '../util/HypothesisUtils';
+import { UPDATE_SERVER_CACHE } from '../util/LinkManager';
 import { RTFMarkup } from '../util/RTFMarkup';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { SelectionManager } from '../util/SelectionManager';
@@ -57,7 +60,7 @@ import { AudioBox } from './nodes/AudioBox';
 import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
 import { DocButtonState } from './nodes/DocumentLinksButton';
 import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from './nodes/DocumentView';
-import { ImageBox, ImageEditorData as ImageEditor } from './nodes/ImageBox';
+import { ImageEditorData as ImageEditor } from './nodes/ImageBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
 import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
@@ -71,11 +74,13 @@ import { PresBox } from './nodes/trails';
 import { AnchorMenu } from './pdf/AnchorMenu';
 import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
 import { TopBar } from './topbar/TopBar';
-const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
+
 const _global = (window /* browser */ || global) /* node */ as any;
+const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 
 @observer
 export class MainView extends ObservableReactComponent<{}> {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: MainView;
     public static Live: boolean = false;
     private _docBtnRef = React.createRef<HTMLDivElement>();
@@ -117,8 +122,12 @@ export class MainView extends ObservableReactComponent<{}> {
     }
     @observable mainDoc: Opt<Doc> = undefined;
     @computed private get mainContainer() {
-        if (window.location.pathname.startsWith('/doc/') && Doc.CurrentUserEmail === 'guest') {
-            DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => (this.mainDoc = main as Doc)));
+        if (window.location.pathname.startsWith('/doc/') && ClientUtils.CurrentUserEmail === 'guest') {
+            DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main =>
+                runInAction(() => {
+                    this.mainDoc = main as Doc;
+                })
+            );
             return this.mainDoc;
         }
         return this.userDoc ? Doc.ActiveDashboard : Doc.GuestDashboard;
@@ -165,7 +174,11 @@ export class MainView extends ObservableReactComponent<{}> {
         scriptTag.async = true;
         scriptTag.defer = true;
         document.body.appendChild(scriptTag);
-        document.getElementById('root')?.addEventListener('scroll', e => (ele => (ele.scrollLeft = ele.scrollTop = 0))(document.getElementById('root')!));
+        document.getElementById('root')?.addEventListener('scroll', () =>
+            (ele => {
+                ele.scrollLeft = ele.scrollTop = 0;
+            })(document.getElementById('root')!)
+        );
         const ele = document.getElementById('loader');
         const prog = document.getElementById('dash-progress');
         if (ele && prog) {
@@ -174,7 +187,9 @@ export class MainView extends ObservableReactComponent<{}> {
                 prog.style.transition = '1s';
                 prog.style.width = '100%';
             }, 0);
-            setTimeout(() => (ele.outerHTML = ''), 1000);
+            setTimeout(() => {
+                ele.outerHTML = '';
+            }, 1000);
         }
         this._sidebarContent.proto = undefined;
         if (!MainView.Live) {
@@ -202,7 +217,7 @@ export class MainView extends ObservableReactComponent<{}> {
                 'text_scrollHeight',
                 'text_height',
                 'hidden',
-                //'type_collection',
+                // 'type_collection',
                 'chromeHidden',
                 'currentFrame',
             ]); // can play with these fields on someone else's
@@ -530,7 +545,7 @@ export class MainView extends ObservableReactComponent<{}> {
     }
 
     private longPressTimer: NodeJS.Timeout | undefined;
-    globalPointerClick = action((e: any) => {
+    globalPointerClick = action(() => {
         this.longPressTimer && clearTimeout(this.longPressTimer);
         DocumentView.LongPress = false;
     });
@@ -540,7 +555,9 @@ export class MainView extends ObservableReactComponent<{}> {
     globalPointerDown = action((e: PointerEvent) => {
         DocumentView.LongPress = false;
         this.longPressTimer = setTimeout(
-            action(() => (DocumentView.LongPress = true)),
+            action(() => {
+                DocumentView.LongPress = true;
+            }),
             1000
         );
         DocumentManager.removeOverlayViews();
@@ -559,7 +576,7 @@ export class MainView extends ObservableReactComponent<{}> {
     });
 
     initEventListeners = () => {
-        window.addEventListener('beforeunload', DocServer.UPDATE_SERVER_CACHE);
+        window.addEventListener('beforeunload', UPDATE_SERVER_CACHE);
         window.addEventListener('drop', e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page
         window.addEventListener('dragover', e => e.preventDefault(), false);
         document.addEventListener('pointerdown', this.globalPointerDown, true);
@@ -625,10 +642,10 @@ export class MainView extends ObservableReactComponent<{}> {
                     isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
                     isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
                     ScreenToLocalTransform={this.headerBarScreenXf}
-                    childHideResizeHandles={true}
+                    childHideResizeHandles
                     childDragAction={dropActionType.move}
-                    dontRegisterView={true}
-                    hideResizeHandles={true}
+                    dontRegisterView
+                    hideResizeHandles
                     PanelWidth={this.headerBarDocWidth}
                     PanelHeight={this.headerBarDocHeight}
                     renderDepth={0}
@@ -664,7 +681,7 @@ export class MainView extends ObservableReactComponent<{}> {
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    suppressSetHeight={true}
+                    suppressSetHeight
                     renderDepth={this._hideUI ? 0 : -1}
                 />
             </>
@@ -673,7 +690,7 @@ export class MainView extends ObservableReactComponent<{}> {
 
     @computed get dockingContent() {
         return (
-            <GestureOverlay isActive={LightboxView.LightboxDoc ? false : true}>
+            <GestureOverlay isActive={!LightboxView.LightboxDoc}>
                 <div
                     key="docking"
                     className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? '-flyout' : ''}`}
@@ -697,9 +714,16 @@ export class MainView extends ObservableReactComponent<{}> {
         setupMoveUpEvents(
             this,
             e,
-            action(e => ((SettingsManager.Instance.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)),
-            action(() => SettingsManager.Instance.propertiesWidth < 5 && (SettingsManager.Instance.propertiesWidth = 0)),
-            action(() => (SettingsManager.Instance.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)),
+            action(() => {
+                SettingsManager.Instance.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX);
+                return !SettingsManager.Instance.propertiesWidth;
+            }),
+            action(() => {
+                SettingsManager.Instance.propertiesWidth < 5 && (SettingsManager.Instance.propertiesWidth = 0);
+            }),
+            action(() => {
+                SettingsManager.Instance.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0;
+            }),
             false
         );
     };
@@ -709,7 +733,10 @@ export class MainView extends ObservableReactComponent<{}> {
         setupMoveUpEvents(
             this,
             e,
-            action(e => ((this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false)),
+            action(e => {
+                this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0);
+                return false;
+            }),
             () => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(),
             this.closeFlyout
         );
@@ -734,7 +761,7 @@ export class MainView extends ObservableReactComponent<{}> {
 
     @computed get flyout() {
         return !this._leftMenuFlyoutWidth ? (
-            <div key="flyout" className={`mainView-libraryFlyout-out`}>
+            <div key="flyout" className="mainView-libraryFlyout-out">
                 {this.docButtons}
             </div>
         ) : (
@@ -837,11 +864,9 @@ export class MainView extends ObservableReactComponent<{}> {
                             </div>
                         )}
                         <div className="properties-container" style={{ width: this.propertiesWidth(), color: SettingsManager.userColor }}>
-                            {
-                                <div style={{ display: this.propertiesWidth() < 10 ? 'none' : undefined }}>
-                                    <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />
-                                </div>
-                            }
+                            <div style={{ display: this.propertiesWidth() < 10 ? 'none' : undefined }}>
+                                <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />
+                            </div>
                         </div>
                     </div>
                 </div>
@@ -878,7 +903,7 @@ export class MainView extends ObservableReactComponent<{}> {
         // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be.
         // interactively adjusting the flyout fixes the problem.  So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);)
         this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
-        //setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
+        // setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
 
         this._sidebarContent.proto = DocCast(button.target);
         SettingsManager.Instance.SetLastPressedBtn(button);
@@ -897,7 +922,7 @@ export class MainView extends ObservableReactComponent<{}> {
 
     buttonBarXf = () => {
         if (!this._docBtnRef.current) return Transform.Identity();
-        const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
+        const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._docBtnRef.current);
         return new Transform(-translateX, -translateY, 1 / scale);
     };
 
@@ -940,9 +965,11 @@ export class MainView extends ObservableReactComponent<{}> {
             <div className="mainView-snapLines">
                 <svg style={{ width: '100%', height: '100%' }}>
                     {SnappingManager.HorizSnapLines.map((l, i) => (
+                        // eslint-disable-next-line react/no-array-index-key
                         <line key={i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
                     ))}
                     {SnappingManager.VertSnapLines.map((l, i) => (
+                        // eslint-disable-next-line react/no-array-index-key
                         <line key={i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
                     ))}
                 </svg>
@@ -961,13 +988,14 @@ export class MainView extends ObservableReactComponent<{}> {
                             values="1 0 0 0 0
                 0 0 0 0 0
                 0 0 0 0 0
-                0 0 0 1 0"></feColorMatrix>
-                        <feGaussianBlur in="color" stdDeviation="4" result="blur"></feGaussianBlur>
-                        <feOffset in="blur" dx="0" dy="0" result="offset"></feOffset>
+                0 0 0 1 0"
+                        />
+                        <feGaussianBlur in="color" stdDeviation="4" result="blur" />
+                        <feOffset in="blur" dx="0" dy="0" result="offset" />
                         <feMerge>
-                            <feMergeNode in="bg"></feMergeNode>
-                            <feMergeNode in="offset"></feMergeNode>
-                            <feMergeNode in="SourceGraphic"></feMergeNode>
+                            <feMergeNode in="bg" />
+                            <feMergeNode in="offset" />
+                            <feMergeNode in="SourceGraphic" />
                         </feMerge>
                     </filter>
                 </defs>
@@ -984,7 +1012,11 @@ export class MainView extends ObservableReactComponent<{}> {
                     color: SettingsManager.userColor,
                     background: SettingsManager.userBackgroundColor,
                 }}
-                onScroll={() => (ele => (ele.scrollTop = ele.scrollLeft = 0))(document.getElementById('root')!)}
+                onScroll={() =>
+                    (ele => {
+                        ele.scrollTop = ele.scrollLeft = 0;
+                    })(document.getElementById('root')!)
+                }
                 ref={r => {
                     r &&
                         new _global.ResizeObserver(
@@ -1009,23 +1041,31 @@ export class MainView extends ObservableReactComponent<{}> {
                 <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
                 {this._hideUI ? null : <TopBar />}
                 <LinkDescriptionPopup />
-                {DocButtonState.Instance.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocButtonState.Instance.LinkEditorDocView = undefined))} docView={DocButtonState.Instance.LinkEditorDocView} /> : null}
-                {LinkInfo.Instance?.LinkInfo ? <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> : null}
-
+                {DocButtonState.Instance.LinkEditorDocView ? (
+                    <LinkMenu
+                        clearLinkEditor={action(() => {
+                            DocButtonState.Instance.LinkEditorDocView = undefined;
+                        })}
+                        docView={DocButtonState.Instance.LinkEditorDocView}
+                    />
+                ) : null}
+                {LinkInfo.Instance?.LinkInfo ? (
+                    // eslint-disable-next-line react/jsx-props-no-spreading
+                    <LinkDocPreview {...LinkInfo.Instance.LinkInfo} />
+                ) : null}
                 {((page: string) => {
                     // prettier-ignore
                     switch (page) {
-                        default:
-                        case 'dashboard': return (<>
+                        case 'home': return <DashboardView />;
+                        case 'dashboard':
+                        default: return (<>
                                         <div key="dashdiv" style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}>
                                             <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} toggleTopBar={this.toggleTopBar} topBarHeight={this.headerBarHeightFunc}/>
                                         </div>
                                         {this.mainDashboardArea}
                                     </> );
-                        case 'home': return <DashboardView />;
                     }
                 })(Doc.ActivePage)}
-
                 <PreviewCursor />
                 <TaskCompletionBox />
                 <ContextMenu />
@@ -1049,15 +1089,19 @@ export class MainView extends ObservableReactComponent<{}> {
     }
 }
 
-ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function selectMainMenu(doc: Doc) {
     MainView.Instance.selectMenu(doc);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function createNewPresentation() {
     return MainView.Instance.createNewPresentation();
 }, 'creates a new presentation when called');
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function openPresentation(pres: Doc) {
     return MainView.Instance.openPresentation(pres);
 }, 'creates a new presentation when called');
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function createNewFolder() {
     return MainView.Instance.createNewFolder();
 }, 'creates a new folder in myFiles when called');
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index af7f38937..e52562625 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -1,7 +1,7 @@
 import { isDark } from 'browndash-components';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { SettingsManager } from '../util/SettingsManager';
+import { SnappingManager } from '../util/SnappingManager';
 import './MainViewModal.scss';
 
 export interface MainViewOverlayProps {
@@ -43,7 +43,7 @@ export class MainViewModal extends React.Component<MainViewOverlayProps> {
                     className="overlay"
                     onClick={this.props?.closeOnExternalClick}
                     style={{
-                        backgroundColor: isDark(SettingsManager.userColor) ? '#DFDFDF30' : '#32323230',
+                        backgroundColor: isDark(SnappingManager.userColor) ? '#DFDFDF30' : '#32323230',
                         ...(p.overlayStyle || {}),
                     }}
                 />
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 9fa20f642..f92bd68a3 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -14,8 +14,8 @@ import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
 import './MarqueeAnnotator.scss';
 import { DocumentView } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { AnchorMenu } from './pdf/AnchorMenu';
 import { ObservableReactComponent } from './ObservableReactComponent';
+import { AnchorMenu } from './pdf/AnchorMenu';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export interface MarqueeAnnotatorProps {
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index 89c3c41f8..9626f1653 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as Autosuggest from 'react-autosuggest';
 import { emptyFunction, emptyPath } from '../../Utils';
-import { Doc, DocListCast, Field } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType } from '../../fields/Doc';
 import { undoBatch } from '../util/UndoManager';
 import './MetadataEntryMenu.scss';
 import { KeyValueBox } from './nodes/KeyValueBox';
@@ -12,7 +12,6 @@ export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
 export interface MetadataEntryProps {
     docs: Doc[];
     onError?: () => boolean;
-    suggestWithFunction?: boolean;
 }
 
 @observer
@@ -35,10 +34,10 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps> {
     };
 
     previewValue = async () => {
-        let field: Field | undefined | null = null;
+        let field: FieldType | undefined | null = null;
         let onProto: boolean = false;
-        let value: string | undefined = undefined;
-        const docs = this.props.docs;
+        let value: string | undefined;
+        const { docs } = this.props;
         for (const doc of docs) {
             const v = await doc[this._currentKey];
             onProto = onProto || !Object.keys(doc).includes(this._currentKey);
diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx
index 394d1eae2..266411770 100644
--- a/src/client/views/ObservableReactComponent.tsx
+++ b/src/client/views/ObservableReactComponent.tsx
@@ -14,7 +14,10 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, {}>
         makeObservable(this);
     }
     componentDidUpdate(prevProps: Readonly<T>): void {
-        Object.keys(prevProps).filter(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey]).forEach(action(pkey =>
-                ((this._props as any)[pkey] = (this.props as any)[pkey]))); // prettier-ignore
+        Object.keys(prevProps)
+            .filter(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey])
+            .forEach(action(pkey => {
+                (this._props as any)[pkey] = (this.props as any)[pkey];
+            })); // prettier-ignore
     }
 }
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 15b1f0275..6539c0834 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -3,13 +3,15 @@ import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../ClientUtils';
+import { Utils, emptyFunction } from '../../Utils';
 import { Doc } from '../../fields/Doc';
 import { Height, Width } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { NumCast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../util/DragManager';
+import { DragManager } from '../util/DragManager';
+import { dropActionType } from '../util/DropActionTypes';
 import { Transform } from '../util/Transform';
 import { LightboxView } from './LightboxView';
 import { ObservableReactComponent } from './ObservableReactComponent';
@@ -50,14 +52,14 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
         this.height = opts.height || 200;
     }
 
-    onPointerDown = (_: React.PointerEvent) => {
+    onPointerDown = () => {
         document.removeEventListener('pointermove', this.onPointerMove);
         document.removeEventListener('pointerup', this.onPointerUp);
         document.addEventListener('pointermove', this.onPointerMove);
         document.addEventListener('pointerup', this.onPointerUp);
     };
 
-    onResizerPointerDown = (_: React.PointerEvent) => {
+    onResizerPointerDown = () => {
         document.removeEventListener('pointermove', this.onResizerPointerMove);
         document.removeEventListener('pointerup', this.onResizerPointerUp);
         document.addEventListener('pointermove', this.onResizerPointerMove);
@@ -95,12 +97,12 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
             <div className="overlayWindow-outerDiv" style={{ transform: `translate(${this.x}px, ${this.y}px)`, width: this.width, height: this.height }}>
                 <div className="overlayWindow-titleBar" onPointerDown={this.onPointerDown}>
                     {this._props.overlayOptions.title || 'Untitled'}
-                    <button onClick={this._props.onClick} className="overlayWindow-closeButton">
+                    <button type="button" onClick={this._props.onClick} className="overlayWindow-closeButton">
                         X
                     </button>
                 </div>
                 <div className="overlayWindow-content">{this.props.children}</div>
-                <div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown}></div>
+                <div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown} />
             </div>
         );
     }
@@ -108,6 +110,7 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
 
 @observer
 export class OverlayView extends ObservableReactComponent<{}> {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: OverlayView;
     @observable.shallow _elements: JSX.Element[] = [];
 
@@ -118,8 +121,9 @@ export class OverlayView extends ObservableReactComponent<{}> {
             OverlayView.Instance = this;
             new _global.ResizeObserver(
                 action((entries: any) => {
-                    for (const entry of entries) {
-                        Doc.MyOverlayDocs.forEach(doc => {
+                    Array.from(entries).forEach((entry: any) => {
+                        Doc.MyOverlayDocs.forEach(docIn => {
+                            const doc = docIn;
                             if (NumCast(doc.overlayX) > entry.contentRect.width - 10) {
                                 doc.overlayX = entry.contentRect.width - 10;
                             }
@@ -127,7 +131,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
                                 doc.overlayY = entry.contentRect.height - 10;
                             }
                         });
-                    }
+                    });
                 })
             ).observe(window.document.body);
         }
@@ -162,7 +166,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
             const index = this._elements.indexOf(contents);
             if (index !== -1) this._elements.splice(index, 1);
         });
-        contents = (
+        const wincontents = (
             <OverlayWindow onClick={remove} key={Utils.GenerateGuid()} overlayOptions={options}>
                 {contents}
             </OverlayWindow>
@@ -177,15 +181,16 @@ export class OverlayView extends ObservableReactComponent<{}> {
     };
 
     docScreenToLocalXf = computedFn(
+        // eslint-disable-next-line prefer-arrow-callback
         function docScreenToLocalXf(this: any, doc: Doc) {
             return () => new Transform(-NumCast(doc.overlayX), -NumCast(doc.overlayY), 1);
-        }.bind(this)
+        }
     );
 
     @computed get overlayDocs() {
         return Doc.MyOverlayDocs.filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES).map(d => {
-            let offsetx = 0,
-                offsety = 0;
+            let offsetx = 0;
+            let offsety = 0;
             const dref = React.createRef<HTMLDivElement>();
             const onPointerMove = action((e: PointerEvent, down: number[]) => {
                 if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
@@ -198,9 +203,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
                     dragData.offset = [-offsetx, -offsety];
                     dragData.dropAction = dropActionType.move;
                     dragData.removeDocument = this.removeOverlayDoc;
-                    dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
-                        return dragData.removeDocument!(doc) ? addDocument(doc) : false;
-                    };
+                    dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => (dragData.removeDocument?.(doc) ? addDocument(doc) : false);
                     DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
                     return true;
                 }
@@ -227,7 +230,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
                         PanelHeight={d[Height]}
                         ScreenToLocalTransform={this.docScreenToLocalXf(d)}
                         renderDepth={1}
-                        hideDecorations={true}
+                        hideDecorations
                         isDocumentActive={returnTrue}
                         isContentActive={returnTrue}
                         whenChildContentsActiveChanged={emptyFunction}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 4b7771f27..6d03034ec 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -1,7 +1,7 @@
 import { action, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { lightOrDark, returnFalse } from '../../Utils';
+import { lightOrDark, returnFalse } from '../../ClientUtils';
 import { Doc, Opt } from '../../fields/Doc';
 import { DocUtils, Docs, DocumentOptions } from '../documents/Documents';
 import { ImageUtils } from '../util/Import & Export/ImageUtils';
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 517a80d63..c7720d834 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -46,7 +46,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
 
     propertyToggleBtn = (label: (on?: any) => string, property: string, tooltip: (on?: any) => string, icon: (on?: any) => any, onClick?: (dv: Opt<DocumentView>, doc: Doc, property: string) => void, useUserDoc?: boolean) => {
         const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedLayoutDoc;
-        const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => ((dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true);
+        const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => ((dv?.layoutDoc || doc)[prop] = !(dv?.layoutDoc || doc)[prop]);
         return !targetDoc ? null : (
             <Toggle
                 toggleStatus={BoolCast(targetDoc[property])}
@@ -103,7 +103,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             (dv, doc) => {
                 const tdoc = dv?.Document || doc;
                 const newtitle = !tdoc._layout_showTitle ? 'title' : tdoc._layout_showTitle === 'title' ? 'title:hover' : '';
-                tdoc._layout_showTitle = newtitle ? newtitle : undefined;
+                tdoc._layout_showTitle = newtitle || undefined;
             }
         );
     }
@@ -181,9 +181,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             on => (on ? 'DISABLE FLASHCARD' : 'ENABLE FLASHCARD'),
             'layout_textPainted',
             on => `${on ? 'Flashcard enabled' : 'Flashcard disabled'} `,
-            on => <MdTouchApp />,
+            () => <MdTouchApp />,
             (dv, doc) => {
-                const on = doc.onPaint ? true : false;
+                const on = !!doc.onPaint;
                 doc[DocData].onPaint = on ? undefined : ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, { documentView: 'any' });
                 doc[DocData].layout_textPainted = on ? undefined : `<ComparisonBox {...props} fieldKey={'${dv?.LayoutFieldKey ?? 'text'}'}/>`;
             }
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index ae29382c1..77e68866e 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable prettier/prettier */
 import { IconLookup } from '@fortawesome/fontawesome-svg-core';
 import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -9,8 +12,9 @@ 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, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
+import { Doc, Field, FieldType, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
 import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { InkField } from '../../fields/InkField';
@@ -54,6 +58,7 @@ interface PropertiesViewProps {
 export class PropertiesView extends ObservableReactComponent<PropertiesViewProps> {
     private _widthUndo?: UndoManager.Batch;
 
+    // eslint-disable-next-line no-use-before-define
     public static Instance: PropertiesView | undefined;
     constructor(props: any) {
         super(props);
@@ -101,7 +106,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @observable openTransform: boolean = true;
     @observable openFilters: boolean = false;
 
-    //Pres Trails booleans:
+    // Pres Trails booleans:
     @observable openPresTransitions: boolean = true;
     @observable openPresProgressivize: boolean = false;
     @observable openPresVisibilityAndDuration: boolean = false;
@@ -183,35 +188,37 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                     .forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key))
             );
 
-            // prettier-ignore
-            Array.from(ids).sort().map(key => {
-                const multiple = Array.from(docs.reduce((set,doc) => set.add(doc[key]), new Set<FieldResult>()).keys()).length > 1;
-                const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key);
-                const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as Field);
-                const contentElement = (
-                    <EditableView
-                        key="editableView"
-                        contents={displayContents}
-                        height={13}
-                        fontSize={10}
-                        GetValue={() => editableContents}
-                        SetValue={(value: string) => {
-                            value !== '-multiple-' && docs.map(doc => KeyValueBox.SetField(doc, key, value, true));
-                            return true;
-                        }}
-                    />);
-                rows.push(
-                    <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}>
-                        <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span>
-                        &nbsp;
-                        {contentElement}
-                    </div>
-                );
-            });
+            Array.from(ids)
+                .sort()
+                .forEach(key => {
+                    const multiple = Array.from(docs.reduce((set, doc) => set.add(doc[key]), new Set<FieldResult>()).keys()).length > 1;
+                    const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key);
+                    const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as FieldType);
+                    const contentElement = (
+                        <EditableView
+                            key="editableView"
+                            contents={displayContents}
+                            height={13}
+                            fontSize={10}
+                            GetValue={() => editableContents}
+                            SetValue={(value: string) => {
+                                value !== '-multiple-' && docs.map(doc => KeyValueBox.SetField(doc, key, value, true));
+                                return true;
+                            }}
+                        />
+                    );
+                    rows.push(
+                        <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}>
+                            <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span>
+                            &nbsp;
+                            {contentElement}
+                        </div>
+                    );
+                });
 
             rows.push(
                 <div className="propertiesView-field" key="newKeyValue" style={{ marginTop: '3px', backgroundColor: SettingsManager.userBackgroundColor, textAlign: 'center' }}>
-                    <EditableView key="editableView" oneLine contents={'add key:value or #tags'} height={13} fontSize={10} GetValue={() => ''} SetValue={this.setKeyValue} />
+                    <EditableView key="editableView" oneLine contents="add key:value or #tags" height={13} fontSize={10} GetValue={() => ''} SetValue={this.setKeyValue} />
                 </div>
             );
         }
@@ -240,7 +247,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                     KeyValueBox.SetField(doc, 'tags', `"${tags.replace(splits[0] + ':', '')}"`, true);
                 }
                 return true;
-            } else if (value[0] === '#') {
+            }
+            if (value[0] === '#') {
                 const tags = StrListCast(doc.tags);
                 if (!tags.includes(value)) {
                     tags.push(value);
@@ -248,6 +256,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                 }
                 return true;
             }
+            return undefined;
         });
         return false;
     };
@@ -256,7 +265,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     getTransform = () => this.transform;
     propertiesDocViewRef = (ref: HTMLDivElement) => {
         const observer = new _global.ResizeObserver(
-            action((entries: any) => {
+            action(() => {
                 const cliRect = ref.getBoundingClientRect();
                 this.transform = new Transform(-cliRect.x, -cliRect.y, 1);
             })
@@ -265,28 +274,27 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     };
 
     @computed get contexts() {
-        return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle={true} addDocTab={this._props.addDocTab} />;
+        return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle addDocTab={this._props.addDocTab} />;
     }
 
     @computed get contextCount() {
         if (this.selectedDocumentView) {
             const target = this.selectedDocumentView.Document;
             return Doc.GetEmbeddings(target).length - 1;
-        } else {
-            return 0;
         }
+        return 0;
     }
 
     @computed get links() {
         const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc;
-        return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this._props.addDocTab} />;
+        return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle addDocTab={this._props.addDocTab} />;
     }
 
     @computed get linkCount() {
         const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc;
-        var counter = 0;
+        let counter = 0;
 
-        LinkManager.Links(selAnchor).forEach((l, i) => counter++);
+        LinkManager.Links(selAnchor).forEach(() => counter++);
 
         return counter;
     }
@@ -308,7 +316,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                         fitContentsToBox={returnTrue}
                         styleProvider={DefaultStyleProvider}
                         containerViewPath={returnEmptyDoclist}
-                        dontCenter={'y'}
+                        dontCenter="y"
                         isDocumentActive={returnFalse}
                         isContentActive={emptyFunction}
                         NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
@@ -326,13 +334,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                         whenChildContentsActiveChanged={emptyFunction}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
-                        dontRegisterView={true}
+                        dontRegisterView
                     />
                 </div>
             );
-        } else {
-            return null;
         }
+        return null;
     }
 
     /**
@@ -381,7 +388,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         return (
             <div className="expansion-button">
                 <IconButton
-                    icon={<FontAwesomeIcon icon={'ellipsis-h'} />}
+                    icon={<FontAwesomeIcon icon="ellipsis-h" />}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                     onClick={action(() => {
@@ -398,7 +405,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
      * @returns a row of the permissions panel
      */
     sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) {
-        if (name == Doc.CurrentUserEmail) {
+        if (name === ClientUtils.CurrentUserEmail) {
             name = 'Me';
         }
         return (
@@ -415,7 +422,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                 {/* {name !== "Me" ? this.notifyIcon : null} */}
                 <div className="propertiesView-sharingTable-item-permission">
                     {this.colorACLDropDown(name, admin, permission, false)}
-                    {(permission === 'Owner' && name == 'Me') || showExpansionIcon ? this.expansionIcon : null}
+                    {(permission === 'Owner' && name === 'Me') || showExpansionIcon ? this.expansionIcon : null}
                 </div>
             </div>
         );
@@ -425,10 +432,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
      * @returns a colored dropdown bar reflective of the permission
      */
     colorACLDropDown(name: string, admin: boolean, permission: string, showGuestOptions: boolean) {
-        var shareImage = ReverseHierarchyMap.get(permission)?.image;
+        const shareImage = ReverseHierarchyMap.get(permission)?.image;
         return (
             <div>
-                <div className={'propertiesView-shareDropDown'}>
+                <div className="propertiesView-shareDropDown">
                     <div className={`propertiesView-shareDropDown${permission}`}>
                         <div>{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission, showGuestOptions) : concat(shareImage, ' ', permission)}</div>
                     </div>
@@ -440,9 +447,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     /**
      * Sorting algorithm to sort users.
      */
-    sortUsers = (u1: String, u2: String) => {
-        return u1 > u2 ? -1 : u1 === u2 ? 0 : 1;
-    };
+    sortUsers = (u1: String, u2: String) => (u1 > u2 ? -1 : u1 === u2 ? 0 : 1);
 
     /**
      * Sorting algorithm to sort groups.
@@ -468,35 +473,35 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         const seldoc = this.layoutDocAcls ? this.selectedLayoutDoc : this.selectedDoc?.[DocData];
         // adds each user to usersAdded
         SharingManager.Instance.users.forEach(eachUser => {
-            var userOnDoc = true;
+            let userOnDoc = true;
             if (seldoc) {
                 if (Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === '' || Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === undefined) {
                     userOnDoc = false;
                 }
             }
-            if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email !== 'guest' && eachUser.user.email != target.author) {
+            if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email !== 'guest' && eachUser.user.email !== target.author) {
                 usersAdded.push(eachUser.user.email);
             }
         });
 
         // sorts and then adds each user to the table
         usersAdded.sort(this.sortUsers);
-        usersAdded.map(userEmail => {
+        usersAdded.forEach(userEmail => {
             const userKey = `acl-${normalizeEmail(userEmail)}`;
-            var aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true);
-            var permission = StrCast(aclField);
+            const aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true);
+            const permission = StrCast(aclField);
             individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user
         });
 
         // adds current user
-        var userEmail = Doc.CurrentUserEmail;
-        if (userEmail == 'guest') userEmail = 'Guest';
+        let userEmail = ClientUtils.CurrentUserEmail;
+        if (userEmail === 'guest') userEmail = 'Guest';
         const userKey = `acl-${normalizeEmail(userEmail)}`;
-        if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail != target.author) {
-            var permission;
+        if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail !== target.author) {
+            let permission;
             if (this.layoutDocAcls) {
                 if (target[DocAcl][userKey]) permission = HierarchyMapping.get(target[DocAcl][userKey])?.name;
-                else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[userKey]);
+                else if (target.embedContainer) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer))[userKey]);
                 else permission = StrCast(Doc.GetProto(target)?.[userKey]);
             } else permission = StrCast(target[userKey]);
             individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user
@@ -509,15 +514,15 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         const groupTableEntries: JSX.Element[] = [];
         const groupList = GroupManager.Instance?.allGroups || [];
         groupList.sort(this.sortGroups);
-        groupList.map(group => {
-            if (group.title != 'Guest' && this.selectedDoc) {
+        groupList.forEach(group => {
+            if (group.title !== 'Guest' && this.selectedDoc) {
                 const groupKey = 'acl-' + normalizeEmail(StrCast(group.title));
-                if (this.selectedDoc[groupKey] != '' && this.selectedDoc[groupKey] != undefined) {
-                    var permission;
+                if (this.selectedDoc[groupKey] !== '' && this.selectedDoc[groupKey] !== undefined) {
+                    let permission;
                     if (this.layoutDocAcls) {
                         if (target[DocAcl][groupKey]) {
                             permission = HierarchyMapping.get(target[DocAcl][groupKey])?.name;
-                        } else if (target['embedContainer']) permission = StrCast(Doc.GetProto(DocCast(target['embedContainer']))[groupKey]);
+                        } else if (target.embedContainer) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer))[groupKey]);
                         else permission = StrCast(Doc.GetProto(target)?.[groupKey]);
                     } else permission = StrCast(target[groupKey]);
                     groupTableEntries.unshift(this.sharingItem(StrCast(group.title), showAdmin, permission!, false));
@@ -531,22 +536,22 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         return (
             <div>
                 <div>
-                    <br></br> Individuals with Access to this Document
+                    <br /> Individuals with Access to this Document
                 </div>
                 <div className="propertiesView-sharingTable" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
-                    {<div> {individualTableEntries}</div>}
+                    <div> {individualTableEntries}</div>
                 </div>
                 {groupTableEntries.length > 0 ? (
                     <div>
                         <div>
-                            <br></br> Groups with Access to this Document
+                            <br /> Groups with Access to this Document
                         </div>
                         <div className="propertiesView-sharingTable" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
-                            {<div> {groupTableEntries}</div>}
+                            <div> {groupTableEntries}</div>
                         </div>
                     </div>
                 ) : null}
-                <br></br> Guest
+                <br /> Guest
                 <div>{this.colorACLDropDown('Guest', showAdmin, guestPermission!, true)}</div>
             </div>
         );
@@ -558,7 +563,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
 
     @action
-    toggleCheckbox = () => (this.layoutFields = !this.layoutFields);
+    toggleCheckbox = () => {
+        this.layoutFields = !this.layoutFields;
+    };
 
     @computed get color() {
         return SettingsManager.userColor;
@@ -578,21 +585,17 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title);
         return (
             <div>
-                <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel={'Title'} fillWidth />
+                <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel="Title" fillWidth />
                 {LinkManager.Instance.currentLinkAnchor ? (
                     <p className="propertiesView-titleExtender">
-                        <>
-                            <b>Anchor:</b>
-                            {LinkManager.Instance.currentLinkAnchor.title}
-                        </>
+                        <b>Anchor:</b>
+                        {StrCast(LinkManager.Instance.currentLinkAnchor.title)}
                     </p>
                 ) : null}
                 {this.selectedLink?.title ? (
                     <p className="propertiesView-titleExtender">
-                        <>
-                            <b>Link:</b>
-                            {this.selectedLink.title}
-                        </>
+                        <b>Link:</b>
+                        {StrCast(this.selectedLink.title)}
                     </p>
                 ) : null}
             </div>
@@ -601,8 +604,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @computed get currentType() {
         const documentType = StrCast(this.selectedDoc?.type);
-        var currentType: string = documentType;
-        var capitalizedDocType = Utils.cleanDocumentType(currentType as DocumentType);
+        const currentType: string = documentType;
+        const capitalizedDocType = ClientUtils.cleanDocumentType(currentType as DocumentType);
 
         return (
             <div>
@@ -618,7 +621,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
 
     @computed get currentComponent() {
-        var iconName = StrCast(this.selectedDoc?.systemIcon);
+        const iconName = StrCast(this.selectedDoc?.systemIcon);
 
         if (iconName) {
             const Icon = Icons[iconName as keyof typeof Icons];
@@ -650,13 +653,11 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                     const ys = ink.map(p => p.Y);
                     const left = Math.min(...xs);
                     const top = Math.min(...ys);
-                    const right = Math.max(...xs);
-                    const bottom = Math.max(...ys);
                     _centerPoints.push({ X: left, Y: top });
                 }
             }
 
-            var index = 0;
+            let index = 0;
             if (doc.type === DocumentType.INK && doc.x && doc.y && layout._width && layout._height && doc.data) {
                 layout.rotation = NumCast(layout.rotation) + angle;
                 const inks = Cast(doc.stroke, InkField)?.inkData;
@@ -687,11 +688,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     get controlPointsButton() {
         return (
             <div className="inking-button">
-                <Tooltip title={<div className="dash-tooltip">{'Edit points'}</div>}>
+                <Tooltip title={<div className="dash-tooltip">Edit points</div>}>
                     <div
                         className="inking-button-points"
                         style={{ backgroundColor: InkStrokeProperties.Instance._controlButton ? 'black' : '' }}
-                        onPointerDown={action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton))}>
+                        onPointerDown={action(() => {
+                            InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton;
+                        })}>
                         <FontAwesomeIcon icon="bezier-curve" size="lg" />
                     </div>
                 </Tooltip>
@@ -699,152 +702,129 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         );
     }
 
-    inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => {
-        return (
-            <div
-                className="inputBox"
-                style={{
-                    marginRight: title === 'X:' ? '19px' : '',
-                    marginLeft: title === '∠:' ? '39px' : '',
-                }}>
-                <div className="inputBox-title"> {title} </div>
-                <input
-                    className="inputBox-input"
-                    type="text"
-                    value={value}
-                    style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
-                    onChange={e => setter(e.target.value)}
-                    onKeyDown={e => e.stopPropagation()}
-                />
-                <div className="inputBox-button">
-                    <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
-                        <FontAwesomeIcon icon="caret-up" size="sm" />
-                    </div>
-                    <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
-                        <FontAwesomeIcon icon="caret-down" size="sm" />
-                    </div>
+    inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => (
+        <div
+            className="inputBox"
+            style={{
+                marginRight: title === 'X:' ? '19px' : '',
+                marginLeft: title === '∠:' ? '39px' : '',
+            }}>
+            <div className="inputBox-title"> {title} </div>
+            <input className="inputBox-input" type="text" value={value} style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} onChange={e => setter(e.target.value)} onKeyDown={e => e.stopPropagation()} />
+            <div className="inputBox-button">
+                <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
+                    <FontAwesomeIcon icon="caret-up" size="sm" />
+                </div>
+                <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
+                    <FontAwesomeIcon icon="caret-down" size="sm" />
                 </div>
             </div>
-        );
-    };
+        </div>
+    );
 
-    inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => {
-        return (
-            <div className="inputBox-duo">
-                {this.inputBox(key, value, setter, title1)}
-                {title2 === '' ? null : this.inputBox(key2, value2, setter2, title2)}
-            </div>
-        );
-    };
+    inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => (
+        <div className="inputBox-duo">
+            {this.inputBox(key, value, setter, title1)}
+            {title2 === '' ? null : this.inputBox(key2, value2, setter2, title2)}
+        </div>
+    );
 
     @action
     upDownButtons = (dirs: string, field: string) => {
         const selDoc = this.selectedDoc;
         if (!selDoc) return;
-        //prettier-ignore
+        // prettier-ignore
         switch (field) {
             case 'Xps': selDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10); break;
             case 'Yps': selDoc.y = NumCast(this.selectedDoc?.y) + (dirs === 'up' ? 10 : -10); break;
             case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.[DocData].stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break;
-            case 'wid':
-                const oldWidth = NumCast(selDoc._width);
-                const oldHeight = NumCast(selDoc._height);
-                const oldX = NumCast(selDoc.x);
-                const oldY = NumCast(selDoc.y);
-                selDoc._width = oldWidth + (dirs === 'up' ? 10 : -10);
-                if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) {
-                    const ink = Cast(selDoc.data, InkField)?.inkData;
-                    if (ink) {
-                        const newPoints: { X: number; Y: number }[] = [];
-                        for (var j = 0; j < ink.length; j++) {
-                            // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
-                            const newX = NumCast(selDoc.x) - oldX + (ink[j].X * NumCast(selDoc._width)) / oldWidth;
-                            const newY = NumCast(selDoc.y) - oldY + (ink[j].Y * NumCast(selDoc._height)) / oldHeight;
-                            newPoints.push({ X: newX, Y: newY });
+            case 'wid': {
+                    const oldWidth = NumCast(selDoc._width);
+                    const oldHeight = NumCast(selDoc._height);
+                    const oldX = NumCast(selDoc.x);
+                    const oldY = NumCast(selDoc.y);
+                    selDoc._width = oldWidth + (dirs === 'up' ? 10 : -10);
+                    if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) {
+                        const ink = Cast(selDoc.data, InkField)?.inkData;
+                        if (ink) {
+                            const newPoints: { X: number; Y: number }[] = [];
+                            for (let j = 0; j < ink.length; j++) {
+                                // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+                                const newX = NumCast(selDoc.x) - oldX + (ink[j].X * NumCast(selDoc._width)) / oldWidth;
+                                const newY = NumCast(selDoc.y) - oldY + (ink[j].Y * NumCast(selDoc._height)) / oldHeight;
+                                newPoints.push({ X: newX, Y: newY });
+                            }
+                            selDoc.data = new InkField(newPoints);
                         }
-                        selDoc.data = new InkField(newPoints);
                     }
                 }
                 break;
-            case 'hgt':
-                const oWidth = NumCast(selDoc._width);
-                const oHeight = NumCast(selDoc._height);
-                const oX = NumCast(selDoc.x);
-                const oY = NumCast(selDoc.y);
-                selDoc._height = oHeight + (dirs === 'up' ? 10 : -10);
-                if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) {
-                    const ink = Cast(selDoc.data, InkField)?.inkData;
-                    if (ink) {
-                        const newPoints: { X: number; Y: number }[] = [];
-                        for (var j = 0; j < ink.length; j++) {
-                            // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
-                            const newX = NumCast(selDoc.x) - oX + (ink[j].X * NumCast(selDoc._width)) / oWidth;
-                            const newY = NumCast(selDoc.y) - oY + (ink[j].Y * NumCast(selDoc._height)) / oHeight;
-                            newPoints.push({ X: newX, Y: newY });
+            case 'hgt': {
+                    const oWidth = NumCast(selDoc._width);
+                    const oHeight = NumCast(selDoc._height);
+                    const oX = NumCast(selDoc.x);
+                    const oY = NumCast(selDoc.y);
+                    selDoc._height = oHeight + (dirs === 'up' ? 10 : -10);
+                    if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) {
+                        const ink = Cast(selDoc.data, InkField)?.inkData;
+                        if (ink) {
+                            const newPoints: { X: number; Y: number }[] = [];
+                            for (let j = 0; j < ink.length; j++) {
+                                // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+                                const newX = NumCast(selDoc.x) - oX + (ink[j].X * NumCast(selDoc._width)) / oWidth;
+                                const newY = NumCast(selDoc.y) - oY + (ink[j].Y * NumCast(selDoc._height)) / oHeight;
+                                newPoints.push({ X: newX, Y: newY });
+                            }
+                            selDoc.data = new InkField(newPoints);
                         }
-                        selDoc.data = new InkField(newPoints);
                     }
                 }
                 break;
+            default: { /* empty */ }
         }
     };
 
     getField(key: string) {
-        return Field.toString(this.selectedDoc?.[DocData][key] as Field);
+        return Field.toString(this.selectedDoc?.[DocData][key] as FieldType);
     }
 
-    @computed get shapeXps() {
-        return NumCast(this.selectedDoc?.x);
-    }
-    @computed get shapeYps() {
-        return NumCast(this.selectedDoc?.y);
-    }
-    @computed get shapeHgt() {
-        return NumCast(this.selectedDoc?._height);
-    }
-    @computed get shapeWid() {
-        return NumCast(this.selectedDoc?._width);
-    }
-    @computed get strokeThk() {
-        return NumCast(this.selectedDoc?.[DocData].stroke_width);
-    }
-    set shapeXps(value) {
-        this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100);
-    }
-    set shapeYps(value) {
-        this.selectedDoc && (this.selectedDoc.y = Math.round(value * 100) / 100);
-    }
-    set shapeWid(value) {
-        this.selectedDoc && (this.selectedDoc._width = Math.round(value * 100) / 100);
-    }
-    set shapeHgt(value) {
-        this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100);
-    }
-    set strokeThk(value) {
-        this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Math.round(value * 100) / 100);
-    }
+    @computed get shapeXps() { return NumCast(this.selectedDoc?.x); } // prettier-ignore
+    set shapeXps(value)  { this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100); } // prettier-ignore
+    @computed get shapeYps() { return NumCast(this.selectedDoc?.y); } // prettier-ignore
+    set shapeYps(value)  { this.selectedDoc && (this.selectedDoc.y = Math.round(value * 100) / 100); } // prettier-ignore
+    @computed get shapeWid() { return NumCast(this.selectedDoc?._width); } // prettier-ignore
+    set shapeWid(value)  { this.selectedDoc && (this.selectedDoc._width = Math.round(value * 100) / 100); } // prettier-ignore
+    @computed get shapeHgt() { return NumCast(this.selectedDoc?._height); } // prettier-ignore
+    set shapeHgt(value)  { this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100); } // prettier-ignore
+    @computed get strokeThk(){ return NumCast(this.selectedDoc?.[DocData].stroke_width); } // prettier-ignore
+    set strokeThk(value) { this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Math.round(value * 100) / 100); } // prettier-ignore
 
     @computed get hgtInput() {
         return this.inputBoxDuo(
             'hgt',
             this.shapeHgt,
-            undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = +val), 'set height'),
+            undoable((val: string) => {
+                !Number(val) && (this.shapeHgt = +val);
+            }, 'set height'),
             'H:',
             'wid',
             this.shapeWid,
-            undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = +val), 'set width'),
+            undoable((val: string) => {
+                !isNaN(Number(val)) && (this.shapeWid = +val);
+            }, 'set width'),
             'W:'
         );
     }
     @computed get XpsInput() {
+        // prettier-ignore
         return this.inputBoxDuo(
             'Xps',
             this.shapeXps,
-            undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeXps = +val), 'set x coord'),
+            undoable((val: string) => { val !== '0' && !isNaN(Number(val)) && (this.shapeXps = +val);  }, 'set x coord'),
             'X:',
             'Yps',
             this.shapeYps,
-            undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeYps = +val), 'set y coord'),
+            undoable((val: string) => { val !== '0' && !isNaN(Number(val)) && (this.shapeYps = +val); }, 'set y coord'),
             'Y:'
         );
     }
@@ -854,18 +834,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     private _lastDash: any = '2';
 
-    @computed get colorFil() {
-        return StrCast(this.selectedDoc?.[DocData].fillColor);
-    }
-    @computed get colorStk() {
-        return StrCast(this.selectedDoc?.[DocData].color);
-    }
-    set colorFil(value) {
-        this.selectedDoc && (this.selectedDoc[DocData].fillColor = value ? value : undefined);
-    }
-    set colorStk(value) {
-        this.selectedDoc && (this.selectedDoc[DocData].color = value ? value : undefined);
-    }
+    @computed get colorFil() { return StrCast(this.selectedDoc?.[DocData].fillColor); } // prettier-ignore
+    set colorFil(value) { this.selectedDoc && (this.selectedDoc[DocData].fillColor = value || undefined); } // prettier-ignore
+    @computed get colorStk() { return StrCast(this.selectedDoc?.[DocData].color); } // prettier-ignore
+    set colorStk(value) { this.selectedDoc && (this.selectedDoc[DocData].color = value || undefined); } // prettier-ignore
 
     colorButton(value: string, type: string, setter: () => void) {
         return (
@@ -886,11 +858,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     colorPicker(color: string, setter: (color: string) => void) {
         return (
+            // prettier-ignore
             <SketchPicker
-                onChange={undoable(
-                    action((color: ColorResult) => setter(color.hex)),
-                    'set stroke color property'
-                )}
+                onChange={undoable( action((color: ColorResult) => setter(color.hex)), 'set stroke color property'  )}
                 presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
                 color={color}
             />
@@ -911,10 +881,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
 
     @computed get fillPicker() {
-        return this.colorPicker(this.colorFil, (color: string) => (this.colorFil = color));
+        return this.colorPicker(this.colorFil, (color: string) => { this.colorFil = color;  }); // prettier-ignore
     }
     @computed get linePicker() {
-        return this.colorPicker(this.colorStk, (color: string) => (this.colorStk = color));
+        return this.colorPicker(this.colorStk, (color: string) => { this.colorStk = color; }); // prettier-ignore
     }
 
     @computed get strokeAndFill() {
@@ -936,60 +906,48 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         );
     }
 
-    @computed get dashdStk() {
-        return this.selectedDoc?.stroke_dash || '';
-    }
-    @computed get widthStk() {
-        return this.getField('stroke_width') || '1';
-    }
-    @computed get markScal() {
-        return Number(this.getField('stroke_markerScale') || '1');
-    }
-    @computed get markHead() {
-        return this.getField('stroke_startMarker') || '';
-    }
-    @computed get markTail() {
-        return this.getField('stroke_endMarker') || '';
-    }
+    @computed get dashdStk() {  return this.selectedDoc?.stroke_dash || ''; } // prettier-ignore
     set dashdStk(value) {
         value && (this._lastDash = value);
         this.selectedDoc && (this.selectedDoc[DocData].stroke_dash = value ? this._lastDash : undefined);
     }
-    set markScal(value) {
-        this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value));
-    }
+    @computed get widthStk() { return this.getField('stroke_width') || '1'; } // prettier-ignore
     set widthStk(value) {
         this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Number(value));
     }
+    @computed get markScal() { return Number(this.getField('stroke_markerScale') || '1'); } // prettier-ignore
+    set markScal(value) {
+        this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value));
+    }
+    @computed get markHead() { return this.getField('stroke_startMarker') || ''; } // prettier-ignore
     set markHead(value) {
         this.selectedDoc && (this.selectedDoc[DocData].stroke_startMarker = value);
     }
+    @computed get markTail() {  return this.getField('stroke_endMarker') || ''; } // prettier-ignore
     set markTail(value) {
         this.selectedDoc && (this.selectedDoc[DocData].stroke_endMarker = value);
     }
 
     @computed get stkInput() {
-        return this.regInput('stk', this.widthStk, (val: string) => (this.widthStk = val));
+        return this.regInput('stk', this.widthStk, (val: string) => { this.widthStk = val; }); // prettier-ignore
     }
     @computed get markScaleInput() {
-        return this.regInput('scale', this.markScal.toString(), (val: string) => (this.markScal = Number(val)));
+        return this.regInput('scale', this.markScal.toString(), (val: string) => { this.markScal = Number(val); }); // prettier-ignore
     }
 
-    regInput = (key: string, value: any, setter: (val: string) => {}) => {
-        return (
-            <div className="inputBox">
-                <input className="inputBox-input" type="text" value={value} style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} onChange={e => setter(e.target.value)} />
-                <div className="inputBox-button">
-                    <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
-                        <FontAwesomeIcon icon="caret-up" size="sm" />
-                    </div>
-                    <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
-                        <FontAwesomeIcon icon="caret-down" size="sm" />
-                    </div>
+    regInput = (key: string, value: any, setter: (val: string) => {}) => (
+        <div className="inputBox">
+            <input className="inputBox-input" type="text" value={value} style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} onChange={e => setter(e.target.value)} />
+            <div className="inputBox-button">
+                <div className="inputBox-button-up" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons('up', key)))}>
+                    <FontAwesomeIcon icon="caret-up" size="sm" />
+                </div>
+                <div className="inputbox-Button-down" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons('down', key)))}>
+                    <FontAwesomeIcon icon="caret-down" size="sm" />
                 </div>
             </div>
-        );
-    };
+        </div>
+    );
 
     @action
     CloseAll = () => {
@@ -1005,18 +963,55 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @computed get widthAndDash() {
         return (
+            // prettier-ignore
             <div className="widthAndDash">
-                <div className="width">{this.getNumber('Thickness', '', 0, Math.max(50, this.strokeThk), this.strokeThk, (val: number) => !isNaN(val) && (this.strokeThk = val), 50, 1)}</div>
-                <div className="width">{this.getNumber('Arrow Scale', '', 0, Math.max(10, this.markScal), this.markScal, (val: number) => !isNaN(val) && (this.markScal = val), 10, 1)}</div>
+                <div className="width">
+                    {this.getNumber(
+                        'Thickness',
+                        '',
+                        0,
+                        Math.max(50, this.strokeThk),
+                        this.strokeThk,
+                        (val: number) => { !isNaN(val) && (this.strokeThk = val); },
+                        50,
+                        1
+                    )}
+                </div>
+                <div className="width">
+                    {this.getNumber(
+                        'Arrow Scale',
+                        '',
+                        0,
+                        Math.max(10, this.markScal),
+                        this.markScal,
+                        (val: number) => {  !isNaN(val) && (this.markScal = val); },
+                        10,
+                        1
+                    )}
+                </div>
 
                 <div className="arrows">
                     <div className="arrows-head">
                         <div className="arrows-head-title">Arrow Head: </div>
-                        <input key="markHead" className="arrows-head-input" type="checkbox" checked={this.markHead !== ''} onChange={undoBatch(action(() => (this.markHead = this.markHead ? '' : 'arrow')))} />
+                        <input
+                            key="markHead"
+                            className="arrows-head-input"
+                            type="checkbox"
+                            checked={this.markHead !== ''}
+                            onChange={undoBatch(action(() => {  this.markHead = this.markHead ? '' : 'arrow';  }))}
+                        />
                     </div>
                     <div className="arrows-tail">
                         <div className="arrows-tail-title">Arrow End: </div>
-                        <input key="markTail" className="arrows-tail-input" type="checkbox" checked={this.markTail !== ''} onChange={undoBatch(action(() => (this.markTail = this.markTail ? '' : 'arrow')))} />
+                        <input
+                            key="markTail"
+                            className="arrows-tail-input"
+                            type="checkbox"
+                            checked={this.markTail !== ''}
+                            onChange={undoBatch(
+                                action(() => { this.markTail = this.markTail ? '' : 'arrow'; })
+                            )}
+                        />
                     </div>
                 </div>
                 <div className="dashed">
@@ -1045,50 +1040,53 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     setFinalNumber = () => {
         this._sliderBatch?.end();
     };
-    getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any, autorange?: number, autorangeMinVal?: number) => {
-        return (
-            <div key={label + this.selectedDoc?.title}>
-                <NumberInput formLabel={label} formLabelPlacement={'left'} type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} />
-                <Slider
-                    key={label}
-                    onPointerDown={e => (this._sliderBatch = UndoManager.StartBatch('slider ' + label))}
-                    multithumb={false}
-                    color={this.color}
-                    size={Size.XSMALL}
-                    min={min}
-                    max={max}
-                    autorangeMinVal={autorangeMinVal}
-                    autorange={autorange}
-                    number={number}
-                    unit={unit}
-                    decimals={1}
-                    setFinalNumber={this.setFinalNumber}
-                    setNumber={setNumber}
-                    fillWidth
-                />
-            </div>
-        );
-    };
+    getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any, autorange?: number, autorangeMinVal?: number) => (
+        <div key={label + (this.selectedDoc?.title ?? '')}>
+            <NumberInput formLabel={label} formLabelPlacement="left" type={Type.SEC} unit={unit} fillWidth color={this.color} number={number} setNumber={setNumber} min={min} max={max} />
+            <Slider
+                key={label}
+                onPointerDown={() => {
+                    this._sliderBatch = UndoManager.StartBatch('slider ' + label);
+                }}
+                multithumb={false}
+                color={this.color}
+                size={Size.XSMALL}
+                min={min}
+                max={max}
+                autorangeMinVal={autorangeMinVal}
+                autorange={autorange}
+                number={number}
+                unit={unit}
+                decimals={1}
+                setFinalNumber={this.setFinalNumber}
+                setNumber={setNumber}
+                fillWidth
+            />
+        </div>
+    );
 
+    setVal = (func: (doc: Doc, val: number) => void) => (val: number) => this.selectedDoc && !isNaN(val) && func(this.selectedDoc, val);
     @computed get transformEditor() {
         return (
+            // prettier-ignore
             <div className="transform-editor">
-                {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), (val: number) => !isNaN(val) && (this.selectedDoc!.gridGap = val))}
-                {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.xMargin = val))}
-                {!this.isStack ? null : this.getNumber('yMargin', ' px', 0, 500, NumCast(this.selectedDoc!.yMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.yMargin = val))}
-                {!this.isGroup ? null : this.getNumber('Padding', ' px', 0, 500, NumCast(this.selectedDoc!.xPadding), (val: number) => !isNaN(val) && (this.selectedDoc!.xPadding = this.selectedDoc!.yPadding = val))}
+                {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), this.setVal((doc: Doc, val: number) => { doc.gridGap = val; })) }
+                {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), this.setVal((doc: Doc, val: number) => { doc.xMargin = val; })) }
+                {!this.isStack ? null : this.getNumber('yMargin', ' px', 0, 500, NumCast(this.selectedDoc!.yMargin), this.setVal((doc: Doc, val: number) => { doc.yMargin = val; })) }
+                {!this.isGroup ? null : this.getNumber('Padding', ' px', 0, 500, NumCast(this.selectedDoc!.xPadding), this.setVal((doc: Doc, val: number) => { doc.xPadding = doc.yPadding = val; })) }
                 {this.isInk ? this.controlPointsButton : null}
-                {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, (val: number) => !isNaN(val) && (this.shapeWid = val), 1000, 1)}
-                {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, (val: number) => !isNaN(val) && (this.shapeHgt = val), 1000, 1)}
-                {this.getNumber('X', ' px', this.shapeXps - 500, this.shapeXps + 500, this.shapeXps, (val: number) => !isNaN(val) && (this.shapeXps = val), 1000)}
-                {this.getNumber('Y', ' px', this.shapeYps - 500, this.shapeYps + 500, this.shapeYps, (val: number) => !isNaN(val) && (this.shapeYps = val), 1000)}
+                {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, this.setVal((doc: Doc, val:number) => {this.shapeWid = val}), 1000, 1)}
+                {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, this.setVal((doc: Doc, val:number) => {this.shapeHgt = val}), 1000, 1)}
+                {this.getNumber('X', ' px', this.shapeXps - 500, this.shapeXps + 500, this.shapeXps, this.setVal((doc: Doc, val:number) =>  {this.shapeXps = val}), 1000)}
+                {this.getNumber('Y', ' px', this.shapeYps - 500, this.shapeYps + 500, this.shapeYps, this.setVal((doc: Doc, val:number) => {this.shapeYps = val}), 1000)}
             </div>
         );
     }
 
     @computed get optionsSubMenu() {
         return (
-            <PropertiesSection title="Options" inSection={this.inOptions} isOpen={this.openOptions} setInSection={bool => (this.inOptions = bool)} setIsOpen={bool => (this.openOptions = bool)} onDoubleClick={this.CloseAll}>
+            // prettier-ignore
+            <PropertiesSection title="Options" inSection={this.inOptions} isOpen={this.openOptions} setInSection={bool => { this.inOptions = bool; }} setIsOpen={bool => { this.openOptions = bool; }} onDoubleClick={this.CloseAll}>
                 <PropertiesButtons />
             </PropertiesSection>
         );
@@ -1096,12 +1094,23 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @computed get sharingSubMenu() {
         return (
-            <PropertiesSection title="Sharing and Permissions" isOpen={this.openSharing} setIsOpen={bool => (this.openSharing = bool)} onDoubleClick={() => this.CloseAll()}>
+            // prettier-ignore
+            <PropertiesSection
+                title="Sharing and Permissions"
+                isOpen={this.openSharing}
+                setIsOpen={bool => { this.openSharing = bool;  }}
+                onDoubleClick={this.CloseAll}>
                 <>
                     {/* <div className="propertiesView-buttonContainer"> */}
                     <div className="propertiesView-acls-checkbox">
                         Layout Permissions
-                        <Checkbox color="primary" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
+                        <Checkbox
+                            color="primary"
+                            onChange={action(() => {
+                                this.layoutDocAcls = !this.layoutDocAcls;
+                            })}
+                            checked={this.layoutDocAcls}
+                        />
                     </div>
                     {/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}>
                     <button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}>
@@ -1141,7 +1150,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @computed get filtersSubMenu() {
         return (
-            <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={bool => (this.openFilters = bool)} onDoubleClick={() => this.CloseAll()}>
+            // prettier-ignore
+            <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={bool => { this.openFilters = bool; }} onDoubleClick={this.CloseAll}>
                 <div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}>
                     <FilterPanel Document={this.selectedDoc ?? Doc.ActiveDashboard!} />
                 </div>
@@ -1151,11 +1161,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @computed get inkSubMenu() {
         return (
+            // prettier-ignore
             <>
-                <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => (this.openAppearance = bool)} onDoubleClick={() => this.CloseAll()}>
+                <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => { this.openAppearance = bool; }} onDoubleClick={this.CloseAll}>
                     {this.selectedLayoutDoc?.layout_isSvg ? this.appearanceEditor : null}
                 </PropertiesSection>
-                <PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => (this.openTransform = bool)} onDoubleClick={() => this.CloseAll()}>
+                <PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => { this.openTransform = bool; }} onDoubleClick={this.CloseAll}>
                     {this.transformEditor}
                 </PropertiesSection>
             </>
@@ -1164,7 +1175,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @computed get fieldsSubMenu() {
         return (
-            <PropertiesSection title="Fields & Tags" isOpen={this.openFields} setIsOpen={bool => (this.openFields = bool)} onDoubleClick={() => this.CloseAll()}>
+            <PropertiesSection
+                title="Fields & Tags"
+                isOpen={this.openFields}
+                setIsOpen={bool => {
+                    this.openFields = bool;
+                }}
+                onDoubleClick={this.CloseAll}>
                 <div className="propertiesView-content fields">{Doc.noviceMode ? this.noviceFields : this.expandedField}</div>
             </PropertiesSection>
         );
@@ -1172,7 +1189,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @computed get contextsSubMenu() {
         return (
-            <PropertiesSection title="Other Contexts" isOpen={this.openContexts} setIsOpen={bool => (this.openContexts = bool)} onDoubleClick={() => this.CloseAll()}>
+            <PropertiesSection
+                title="Other Contexts"
+                isOpen={this.openContexts}
+                setIsOpen={bool => {
+                    this.openContexts = bool;
+                }}
+                onDoubleClick={this.CloseAll}>
                 {this.contextCount > 0 ? this.contexts : 'There are no other contexts.'}
             </PropertiesSection>
         );
@@ -1180,7 +1203,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @computed get linksSubMenu() {
         return (
-            <PropertiesSection title="Linked To" isOpen={this.openLinks} setIsOpen={bool => (this.openLinks = bool)} onDoubleClick={this.CloseAll}>
+            <PropertiesSection
+                title="Linked To"
+                isOpen={this.openLinks}
+                setIsOpen={bool => {
+                    this.openLinks = bool;
+                }}
+                onDoubleClick={this.CloseAll}>
                 {this.linkCount > 0 ? this.links : 'There are no current links.'}
             </PropertiesSection>
         );
@@ -1188,14 +1217,20 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @computed get layoutSubMenu() {
         return (
-            <PropertiesSection title="Layout" isOpen={this.openLayout} setIsOpen={bool => (this.openLayout = bool)} onDoubleClick={this.CloseAll}>
+            <PropertiesSection
+                title="Layout"
+                isOpen={this.openLayout}
+                setIsOpen={bool => {
+                    this.openLayout = bool;
+                }}
+                onDoubleClick={this.CloseAll}>
                 {this.layoutPreview}
             </PropertiesSection>
         );
     }
 
     @computed get description() {
-        return Field.toString(this.selectedLink?.link_description as any as Field);
+        return Field.toString(this.selectedLink?.link_description as any as FieldType);
     }
     @computed get relationship() {
         return StrCast(this.selectedLink?.link_relationship);
@@ -1250,12 +1285,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                 // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes
             } else if (linkRelationshipList && value !== prevRelationship) {
                 const index = linkRelationshipList.indexOf(value);
-                //increment size of new relationship size
+                // increment size of new relationship size
                 if (index !== -1 && index < linkRelationshipSizes.length) {
                     const pvalue = linkRelationshipSizes[index];
                     linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1;
                 }
-                //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation)
+                // decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation)
                 if (linkRelationshipList.includes(prevRelationship)) {
                     const pindex = linkRelationshipList.indexOf(prevRelationship);
                     if (pindex !== -1 && pindex < linkRelationshipSizes.length) {
@@ -1265,21 +1300,25 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                 }
             }
             this.relationshipButtonColor = 'rgb(62, 133, 55)';
-            setTimeout(
-                action(() => (this.relationshipButtonColor = '')),
-                750
-            );
+            setTimeout(action(() => { this.relationshipButtonColor = ''; }),  750); // prettier-ignore
             return true;
         }
+        return undefined;
     });
 
-    changeFollowBehavior = undoable((loc: Opt<string>) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = loc), 'change follow behavior');
+    changeFollowBehavior = undoable((loc: Opt<string>) => {
+        this.sourceAnchor && (this.sourceAnchor.followLinkLocation = loc);
+    }, 'change follow behavior');
 
     @undoBatch
-    changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior));
+    changeAnimationBehavior = action((behavior: string) => {
+        this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior);
+    });
 
     @undoBatch
-    changeEffectDirection = action((effect: PresEffectDirection) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimDirection = effect));
+    changeEffectDirection = action((effect: PresEffectDirection) => {
+        this.sourceAnchor && (this.sourceAnchor.followLinkAnimDirection = effect);
+    });
 
     animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => {
         const lanch = this.sourceAnchor;
@@ -1300,7 +1339,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         document.getElementById('link_description_input')?.blur();
     };
 
-    onDescriptionKey = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
+    onDescriptionKey = () => {
         // if (e.key === 'Enter') {
         //     this.setDescripValue(this.description);
         //     document.getElementById('link_description_input')?.blur();
@@ -1320,7 +1359,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     };
 
     toggleLinkProp = (e: React.PointerEvent, prop: string) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedLink && (this.selectedLink[prop] = !this.selectedLink[prop]))));
+        setupMoveUpEvents(
+            this,
+            e,
+            returnFalse,
+            emptyFunction,
+            undoBatch(action(() => { this.selectedLink && (this.selectedLink[prop] = !this.selectedLink[prop]); })) // prettier-ignore
+        );
     };
 
     @computed get destinationAnchor() {
@@ -1343,12 +1388,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                 e,
                 returnFalse,
                 emptyFunction,
-                undoBatch(
-                    action(() => {
-                        anchor[prop] = anchor[prop] === value ? ovalue : value;
-                        this.selectedDoc && cb(anchor[prop]);
-                    })
-                )
+                undoBatch(action(() => {
+                    anchor[prop] = anchor[prop] === value ? ovalue : value;
+                    this.selectedDoc && cb(anchor[prop]);
+                })) // prettier-ignore
             );
     };
 
@@ -1357,7 +1400,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         return (
             <input
                 style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
-                autoComplete={'off'}
+                autoComplete="off"
                 id="link_relationship_input"
                 value={StrCast(this.selectedLink?.link_relationship)}
                 onKeyDown={this.onRelationshipKey}
@@ -1459,11 +1502,14 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                             '10',
                             NumCast(this.sourceAnchor?.followLinkTransitionTime) / 1000,
                             true,
-                            (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS)),
+                            (val: string) =>
+                                PresBox.SetTransitionTime(val, (timeInMS: number) => {
+                                    this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS);
+                                }),
                             indent
                         )}{' '}
                         <div
-                            className={'slider-headers'}
+                            className="slider-headers"
                             style={{
                                 display: 'grid',
                                 justifyContent: 'space-between',
@@ -1478,86 +1524,118 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                         </div>{' '}
                         <div className="propertiesView-input inline">
                             <p>Play Target Audio</p>
-                            <button
-                                style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }}
-                                onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)}
-                                onClick={e => e.stopPropagation()}
-                                className="propertiesButton">
-                                <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
-                            </button>
+                            {
+                                // eslint-disable-next-line jsx-a11y/control-has-associated-label
+                                <button
+                                    type="button"
+                                    style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }}
+                                    onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)}
+                                    onClick={e => e.stopPropagation()}
+                                    className="propertiesButton">
+                                    <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+                                </button>
+                            }
                         </div>
                         <div className="propertiesView-input inline">
                             <p>Play Target Video</p>
-                            <button
-                                style={{ background: !this.sourceAnchor?.followLinkVideo ? '' : '#4476f7', borderRadius: 3 }}
-                                onPointerDown={e => this.toggleAnchorProp(e, 'followLinkVideo', this.sourceAnchor)}
-                                onClick={e => e.stopPropagation()}
-                                className="propertiesButton">
-                                <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
-                            </button>
+                            {
+                                // eslint-disable-next-line jsx-a11y/control-has-associated-label
+                                <button
+                                    type="button"
+                                    style={{ background: !this.sourceAnchor?.followLinkVideo ? '' : '#4476f7', borderRadius: 3 }}
+                                    onPointerDown={e => this.toggleAnchorProp(e, 'followLinkVideo', this.sourceAnchor)}
+                                    onClick={e => e.stopPropagation()}
+                                    className="propertiesButton">
+                                    <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+                                </button>
+                            }
                         </div>
                         <div className="propertiesView-input inline">
                             <p>Zoom Text Selections</p>
-                            <button
-                                style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }}
-                                onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)}
-                                onClick={e => e.stopPropagation()}
-                                className="propertiesButton">
-                                <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
-                            </button>
+                            {
+                                // eslint-disable-next-line jsx-a11y/control-has-associated-label
+                                <button
+                                    type="button"
+                                    style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }}
+                                    onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)}
+                                    onClick={e => e.stopPropagation()}
+                                    className="propertiesButton">
+                                    <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+                                </button>
+                            }
                         </div>
                         <div className="propertiesView-input inline">
                             <p>Toggle Follow to Outer Context</p>
-                            <button
-                                style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }}
-                                onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)}
-                                onClick={e => e.stopPropagation()}
-                                className="propertiesButton">
-                                <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" />
-                            </button>
+                            {
+                                // eslint-disable-next-line jsx-a11y/control-has-associated-label
+                                <button
+                                    type="button"
+                                    style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }}
+                                    onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)}
+                                    onClick={e => e.stopPropagation()}
+                                    className="propertiesButton">
+                                    <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" />
+                                </button>
+                            }
                         </div>
                         <div className="propertiesView-input inline">
                             <p>Toggle Target (Show/Hide)</p>
-                            <button
-                                style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }}
-                                onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)}
-                                onClick={e => e.stopPropagation()}
-                                className="propertiesButton">
-                                <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
-                            </button>
+                            {
+                                // eslint-disable-next-line jsx-a11y/control-has-associated-label
+                                <button
+                                    type="button"
+                                    style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }}
+                                    onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)}
+                                    onClick={e => e.stopPropagation()}
+                                    className="propertiesButton">
+                                    <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+                                </button>
+                            }
                         </div>
                         <div className="propertiesView-input inline">
                             <p>Ease Transitions</p>
-                            <button
-                                style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }}
-                                onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')}
-                                onClick={e => e.stopPropagation()}
-                                className="propertiesButton">
-                                <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
-                            </button>
+                            {
+                                // eslint-disable-next-line jsx-a11y/control-has-associated-label
+                                <button
+                                    type="button"
+                                    style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }}
+                                    onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')}
+                                    onClick={e => e.stopPropagation()}
+                                    className="propertiesButton">
+                                    <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+                                </button>
+                            }
                         </div>
                         <div className="propertiesView-input inline">
                             <p>Capture Offset to Target</p>
-                            <button
-                                style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }}
-                                onPointerDown={e => {
-                                    this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined);
-                                    this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined);
-                                }}
-                                onClick={e => e.stopPropagation()}
-                                className="propertiesButton">
-                                <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
-                            </button>
+                            {
+                                // eslint-disable-next-line jsx-a11y/control-has-associated-label
+                                <button
+                                    type="button"
+                                    style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }}
+                                    onPointerDown={e => {
+                                        this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined);
+                                        this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined);
+                                    }}
+                                    onClick={e => e.stopPropagation()}
+                                    className="propertiesButton">
+                                    <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+                                </button>
+                            }
                         </div>
                         <div className="propertiesView-input inline">
                             <p>Center Target (no zoom)</p>
-                            <button
-                                style={{ background: this.sourceAnchor?.followLinkZoom ? '' : '#4476f7', borderRadius: 3 }}
-                                onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
-                                onClick={e => e.stopPropagation()}
-                                className="propertiesButton">
-                                <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
-                            </button>
+                            {
+                                // eslint-disable-next-line jsx-a11y/control-has-associated-label
+                                <button
+                                    type="button"
+                                    style={{ background: this.sourceAnchor?.followLinkZoom ? '' : '#4476f7', borderRadius: 3 }}
+                                    onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
+                                    onClick={e => e.stopPropagation()}
+                                    className="propertiesButton">
+                                    <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+                                </button>
+                            }
                         </div>
                         <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}>
                             <p>Zoom %</p>
@@ -1565,24 +1643,28 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                                 <input className="presBox-input" style={{ width: '100%', color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} readOnly={true} type="number" value={zoom} />
                                 <div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}>
                                     <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}>
-                                        <FontAwesomeIcon icon={'caret-up'} />
+                                        <FontAwesomeIcon icon="caret-up" />
                                     </div>
                                     <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}>
-                                        <FontAwesomeIcon icon={'caret-down'} />
+                                        <FontAwesomeIcon icon="caret-down" />
                                     </div>
                                 </div>
                             </div>
-                            <button
-                                style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }}
-                                onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
-                                onClick={e => e.stopPropagation()}
-                                className="propertiesButton">
-                                <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
-                            </button>
+                            {
+                                // eslint-disable-next-line jsx-a11y/control-has-associated-label
+                                <button
+                                    type="button"
+                                    style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }}
+                                    onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
+                                    onClick={e => e.stopPropagation()}
+                                    className="propertiesButton">
+                                    <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
+                                </button>
+                            }
                         </div>
                         {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)}
                         <div
-                            className={'slider-headers'}
+                            className="slider-headers"
                             style={{
                                 display: !targZoom ? 'none' : 'grid',
                                 justifyContent: 'space-between',
@@ -1594,7 +1676,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                             }}>
                             <div className="slider-text">0%</div>
                             <div className="slider-text">100%</div>
-                        </div>{' '}
+                        </div>
                     </div>
                 )}
             </>
@@ -1622,128 +1704,136 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                     </div>
                 </div>
             );
-        } else {
-            if (this.selectedDoc && !this.isPres) {
-                return (
-                    <div
-                        className="propertiesView"
-                        style={{
-                            background: SettingsManager.userBackgroundColor,
-                            color: SettingsManager.userColor,
-                            width: this._props.width,
-                            minWidth: this._props.width,
-                        }}>
-                        <div className="propertiesView-propAndInfoGrouping">
-                            <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
-                                Properties
-                                <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties')}>
-                                    <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} />
-                                </div>
+        }
+        if (this.selectedDoc && !this.isPres) {
+            return (
+                <div
+                    className="propertiesView"
+                    style={{
+                        background: SettingsManager.userBackgroundColor,
+                        color: SettingsManager.userColor,
+                        width: this._props.width,
+                        minWidth: this._props.width,
+                    }}>
+                    <div className="propertiesView-propAndInfoGrouping">
+                        <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
+                            Properties
+                            <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties')}>
+                                <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} />
                             </div>
                         </div>
+                    </div>
 
-                        <div className="propertiesView-name">{this.editableTitle}</div>
-                        <div className="propertiesView-type"> {this.currentType} </div>
-                        {this.fieldsSubMenu}
-                        {this.optionsSubMenu}
-                        {this.linksSubMenu}
-                        {!this.selectedLink || !this.openLinks ? null : this.linkProperties}
-                        {this.inkSubMenu}
-                        {this.contextsSubMenu}
-                        {isNovice ? null : this.sharingSubMenu}
-                        {this.filtersSubMenu}
-                        {isNovice ? null : this.layoutSubMenu}
+                    <div className="propertiesView-name">{this.editableTitle}</div>
+                    <div className="propertiesView-type"> {this.currentType} </div>
+                    {this.fieldsSubMenu}
+                    {this.optionsSubMenu}
+                    {this.linksSubMenu}
+                    {!this.selectedLink || !this.openLinks ? null : this.linkProperties}
+                    {this.inkSubMenu}
+                    {this.contextsSubMenu}
+                    {isNovice ? null : this.sharingSubMenu}
+                    {this.filtersSubMenu}
+                    {isNovice ? null : this.layoutSubMenu}
+                </div>
+            );
+        }
+        if (this.isPres && PresBox.Instance) {
+            const selectedItem: boolean = PresBox.Instance.selectedArray.size > 0;
+            const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
+                ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
+                : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
+            return (
+                <div className="propertiesView" style={{ width: this._props.width }}>
+                    <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
+                        Presentation
                     </div>
-                );
-            }
-            if (this.isPres && PresBox.Instance) {
-                const selectedItem: boolean = PresBox.Instance.selectedArray.size > 0;
-                const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
-                    ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
-                    : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
-                return (
-                    <div className="propertiesView" style={{ width: this._props.width }}>
-                        <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
-                            Presentation
-                        </div>
-                        <div className="propertiesView-name" style={{ borderBottom: 0 }}>
-                            {this.editableTitle}
-                            <div className="propertiesView-presSelected">
-                                <div className="propertiesView-selectedCount">{PresBox.Instance.selectedArray.size} selected</div>
-                                <div className="propertiesView-selectedList">{PresBox.Instance.listOfSelected}</div>
-                            </div>
+                    <div className="propertiesView-name" style={{ borderBottom: 0 }}>
+                        {this.editableTitle}
+                        <div className="propertiesView-presSelected">
+                            <div className="propertiesView-selectedCount">{PresBox.Instance.selectedArray.size} selected</div>
+                            <div className="propertiesView-selectedList">{PresBox.Instance.listOfSelected}</div>
                         </div>
-                        {!selectedItem ? null : (
-                            <div className="propertiesView-presentationTrails">
-                                <div
-                                    className="propertiesView-presentationTrails-title"
-                                    onPointerDown={action(() => (this.openPresTransitions = !this.openPresTransitions))}
-                                    style={{
-                                        color: SettingsManager.userColor,
-                                        backgroundColor: this.openPresTransitions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
-                                    }}>
-                                    &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Transitions
-                                    <div className="propertiesView-presentationTrails-title-icon">
-                                        <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" />
-                                    </div>
+                    </div>
+                    {!selectedItem ? null : (
+                        <div className="propertiesView-presentationTrails">
+                            <div
+                                className="propertiesView-presentationTrails-title"
+                                onPointerDown={action(() => {
+                                    this.openPresTransitions = !this.openPresTransitions;
+                                })}
+                                style={{
+                                    color: SettingsManager.userColor,
+                                    backgroundColor: this.openPresTransitions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+                                }}>
+                                &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Transitions
+                                <div className="propertiesView-presentationTrails-title-icon">
+                                    <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" />
                                 </div>
-                                {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
                             </div>
-                        )}
-                        {!selectedItem ? null : (
-                            <div className="propertiesView-presentationTrails">
-                                <div
-                                    className="propertiesView-presentationTrails-title"
-                                    onPointerDown={action(() => (this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration))}
-                                    style={{
-                                        color: SettingsManager.userColor,
-                                        backgroundColor: this.openPresVisibilityAndDuration ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
-                                    }}>
-                                    &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Visibility
-                                    <div className="propertiesView-presentationTrails-title-icon">
-                                        <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" />
-                                    </div>
+                            {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
+                        </div>
+                    )}
+                    {!selectedItem ? null : (
+                        <div className="propertiesView-presentationTrails">
+                            <div
+                                className="propertiesView-presentationTrails-title"
+                                onPointerDown={action(() => {
+                                    this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration;
+                                })}
+                                style={{
+                                    color: SettingsManager.userColor,
+                                    backgroundColor: this.openPresVisibilityAndDuration ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+                                }}>
+                                &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Visibility
+                                <div className="propertiesView-presentationTrails-title-icon">
+                                    <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" />
                                 </div>
-                                {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null}
                             </div>
-                        )}
-                        {!selectedItem ? null : (
-                            <div className="propertiesView-presentationTrails">
-                                <div
-                                    className="propertiesView-presentationTrails-title"
-                                    onPointerDown={action(() => (this.openPresProgressivize = !this.openPresProgressivize))}
-                                    style={{
-                                        color: SettingsManager.userColor,
-                                        backgroundColor: this.openPresProgressivize ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
-                                    }}>
-                                    &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Progressivize
-                                    <div className="propertiesView-presentationTrails-title-icon">
-                                        <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" />
-                                    </div>
+                            {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null}
+                        </div>
+                    )}
+                    {!selectedItem ? null : (
+                        <div className="propertiesView-presentationTrails">
+                            <div
+                                className="propertiesView-presentationTrails-title"
+                                onPointerDown={action(() => {
+                                    this.openPresProgressivize = !this.openPresProgressivize;
+                                })}
+                                style={{
+                                    color: SettingsManager.userColor,
+                                    backgroundColor: this.openPresProgressivize ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+                                }}>
+                                &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Progressivize
+                                <div className="propertiesView-presentationTrails-title-icon">
+                                    <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" />
                                 </div>
-                                {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null}
                             </div>
-                        )}
-                        {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
-                            <div className="propertiesView-presentationTrails">
-                                <div
-                                    className="propertiesView-presentationTrails-title"
-                                    onPointerDown={action(() => (this.openSlideOptions = !this.openSlideOptions))}
-                                    style={{
-                                        color: SettingsManager.userColor,
-                                        backgroundColor: this.openSlideOptions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
-                                    }}>
-                                    &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> &nbsp; {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'}
-                                    <div className="propertiesView-presentationTrails-title-icon">
-                                        <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" />
-                                    </div>
+                            {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null}
+                        </div>
+                    )}
+                    {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
+                        <div className="propertiesView-presentationTrails">
+                            <div
+                                className="propertiesView-presentationTrails-title"
+                                onPointerDown={action(() => {
+                                    this.openSlideOptions = !this.openSlideOptions;
+                                })}
+                                style={{
+                                    color: SettingsManager.userColor,
+                                    backgroundColor: this.openSlideOptions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+                                }}>
+                                &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> &nbsp; {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'}
+                                <div className="propertiesView-presentationTrails-title-icon">
+                                    <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" />
                                 </div>
-                                {this.openSlideOptions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null}
                             </div>
-                        )}
-                    </div>
-                );
-            }
+                            {this.openSlideOptions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null}
+                        </div>
+                    )}
+                </div>
+            );
         }
+        return null;
     }
 }
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 623201ed1..365980f33 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Doc, Opt } from '../../fields/Doc';
 import { ScriptField } from '../../fields/ScriptField';
 import { ScriptCast } from '../../fields/Types';
-import { emptyFunction } from '../../Utils';
+import { emptyFunction } from '../../ClientUtils';
 import { DragManager } from '../util/DragManager';
 import { CompileScript } from '../util/Scripting';
 import { EditableView } from './EditableView';
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 3ad3c92da..bcd82887c 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -1,8 +1,10 @@
 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 { ClientUtils, returnAll, returnFalse, returnOne, returnZero } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
+import { Doc, DocListCast, Field, FieldType, FieldResult, StrListCast } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
@@ -18,7 +20,6 @@ import { StyleProp } from './StyleProvider';
 import { CollectionStackingView } from './collections/CollectionStackingView';
 import { FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import { DocData } from '../../fields/DocSymbols';
 
 interface ExtraProps {
     fieldKey: string;
@@ -44,7 +45,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
 
     _stackRef = React.createRef<CollectionStackingView>();
     @computed get allMetadata() {
-        const keys = new Map<string, FieldResult<Field>>();
+        const keys = new Map<string, FieldResult<FieldType>>();
         DocListCast(this._props.Document[this.sidebarKey]).forEach(doc =>
             SearchUtil.documentKeys(doc)
                 .filter(key => key[0] && key[0] !== '_' && key[0] === key[0].toUpperCase())
@@ -95,7 +96,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                 return {
                     type: 'dashField',
                     attrs: { fieldKey: key, docId: '', hideKey: false, hideValue: false, editable: true },
-                    marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: Doc.CurrentUserEmail, modified: 0 } }],
+                    marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: ClientUtils.CurrentUserEmail, modified: 0 } }],
                 };
             });
 
@@ -196,7 +197,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                 </div>
             );
         };
-        const renderMeta = (tag: string, dflt: FieldResult<Field>) => {
+        const renderMeta = (tag: string, dflt: FieldResult<FieldType>) => {
             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)}>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index dcec2fe3d..15e309ca0 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -7,10 +7,10 @@ import { extname } from 'path';
 import * as React from 'react';
 import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
 import { FaFilter } from 'react-icons/fa';
+import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils';
 import { Doc, Opt, StrListCast } from '../../fields/Doc';
 import { DocViews } from '../../fields/DocSymbols';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
-import { DashColor, lightOrDark, Utils } from '../../Utils';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager';
 import { IsFollowLinkScript } from '../util/LinkFollower';
@@ -26,6 +26,7 @@ import { FieldViewProps } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PropertiesView } from './PropertiesView';
 import './StyleProvider.scss';
+import { ScriptField } from '../../fields/ScriptField';
 
 export enum StyleProp {
     TreeViewIcon = 'treeView_Icon',
@@ -70,6 +71,20 @@ function togglePaintView(e: React.MouseEvent, doc: Opt<Doc>, props: Opt<FieldVie
     ScriptCast(doc?.onPaint)?.script.run(scriptProps);
 }
 
+export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: number) {
+    const style: { [key: string]: any } = {};
+    const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
+    const replacer = (match: any, expr: string, offset: any, string: any) => {
+        // bcz: this executes a script to convert a property expression string:  { script }  into a value
+        return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? '';
+    };
+    divKeys.map((prop: string) => {
+        const p = (props as any)[prop];
+        typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
+    });
+    return style;
+}
+
 export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
     return `M ${pw * 0.5} ${ph * inset}   C ${pw * 0.6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset}   C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * 0.25} ${pw * (1 - inset)} ${ph * 0.3}   C ${
         pw * (1 - inset)
@@ -156,7 +171,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                         doc._layout_showTitle,
                         props?.layout_showTitle?.() ||
                             (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.FUNCPLOT, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any)
-                                ? doc.author === Doc.CurrentUserEmail
+                                ? doc.author === ClientUtils.CurrentUserEmail
                                     ? StrCast(Doc.UserDoc().layout_showTitle)
                                     : remoteDocHeader
                                 : '')
@@ -300,7 +315,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 const showFilterIcon =
                     StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
                         ? 'green' // #18c718bd' //'hasFilter'
-                        : props?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragDocsFilter).length || props?.childFiltersByRanges().length
+                        : props?.childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || props?.childFiltersByRanges().length
                         ? 'orange' //'inheritsFilter'
                         : undefined;
                 return !showFilterIcon ? null : (
@@ -308,7 +323,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                         <Dropdown
                             type={Type.TERT}
                             dropdownType={DropdownType.CLICK}
-                            fillWidth
+                        fillWidth
                             iconProvider={(active:boolean) => <div className='styleProvider-filterShift'><FaFilter/></div>}
                             closeOnSelect={true}
                             setSelectedVal={
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 5df0bea1a..1d02568dd 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -1,12 +1,13 @@
-import { computed, observable, ObservableSet, runInAction } from 'mobx';
+import { computed, ObservableSet, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../ClientUtils';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { DocData } from '../../fields/DocSymbols';
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, DocCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils';
+import { emptyFunction } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { Transform } from '../util/Transform';
@@ -15,21 +16,6 @@ import { DocumentView, returnEmptyDocViewList } from './nodes/DocumentView';
 import { DefaultStyleProvider } from './StyleProvider';
 import './TemplateMenu.scss';
 
-@observer
-class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> {
-    render() {
-        if (this.props.template) {
-            return (
-                <li className="templateToggle">
-                    <input type="checkbox" checked={this.props.checked} onChange={event => this.props.toggle(event, this.props.template)} />
-                    {this.props.template}
-                </li>
-            );
-        } else {
-            return null;
-        }
-    }
-}
 @observer
 class OtherToggle extends React.Component<{ checked: boolean; name: string; toggle: (event: React.ChangeEvent<HTMLInputElement>) => void }> {
     render() {
@@ -50,12 +36,25 @@ export interface TemplateMenuProps {
 export class TemplateMenu extends React.Component<TemplateMenuProps> {
     _addedKeys = new ObservableSet();
     _customRef = React.createRef<HTMLInputElement>();
-    @observable private _hidden: boolean = true;
+
+    componentDidMount() {
+        !this._addedKeys && (this._addedKeys = new ObservableSet());
+        [...Array.from(Object.keys(this.props.docViews[0].Document[DocData])), ...Array.from(Object.keys(this.props.docViews[0].Document))]
+            .filter(key => key.startsWith('layout_') && (
+                StrCast(this.props.docViews[0].Document[key]).startsWith("<") ||
+                DocCast(this.props.docViews[0].Document[key])?.isTemplateDoc
+            ))
+            .forEach(key => runInAction(() => this._addedKeys.add(key.replace('layout_', '')))); // prettier-ignore
+    }
+    @computed get scriptField() {
+        const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.Document) as any });
+        return script ? () => script : undefined;
+    }
 
     toggleLayout = (e: React.ChangeEvent<HTMLInputElement>, layout: string): void => {
         this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout, undefined, true));
     };
-    toggleDefault = (e: React.ChangeEvent<HTMLInputElement>): void => {
+    toggleDefault = (): void => {
         this.props.docViews.map(dv => dv.switchViews(false, 'layout'));
     };
 
@@ -65,21 +64,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             runInAction(() => this._addedKeys.add(this._customRef.current!.value));
         }
     };
-    componentDidMount() {
-        !this._addedKeys && (this._addedKeys = new ObservableSet());
-        [...Array.from(Object.keys(this.props.docViews[0].Document[DocData])), ...Array.from(Object.keys(this.props.docViews[0].Document))]
-            .filter(key => key.startsWith('layout_') && (
-                StrCast(this.props.docViews[0].Document[key]).startsWith("<") ||
-                DocCast(this.props.docViews[0].Document[key])?.isTemplateDoc
-            ))
-            .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', '')))); // prettier-ignore
-    }
 
     return100 = () => 300;
-    @computed get scriptField() {
-        const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.Document) as any });
-        return script ? () => script : undefined;
-    }
     templateIsUsed = (selDoc: Doc, templateDoc: Doc) => {
         const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
         return StrCast(selDoc.layout_fieldKey) === 'layout_' + template ? 'check' : 'unchecked';
@@ -88,10 +74,11 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         TraceMobx();
         const firstDoc = this.props.docViews[0].Document;
         const templateName = StrCast(firstDoc.layout_fieldKey, 'layout').replace('layout_', '');
-        const noteTypes = DocListCast(Cast(Doc.UserDoc()['template_notes'], Doc, null)?.data);
-        const addedTypes = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data);
+        const noteTypes = DocListCast(Cast(Doc.UserDoc().template_notes, Doc, null)?.data);
+        const addedTypes = DocListCast(Cast(Doc.UserDoc().template_clickFuncs, Doc, null)?.data);
         const templateMenu: Array<JSX.Element> = [];
         templateMenu.push(<OtherToggle key="default" name={firstDoc.layout instanceof Doc ? StrCast(firstDoc.layout.title) : 'Default'} checked={templateName === 'layout'} toggle={this.toggleDefault} />);
+        // eslint-disable-next-line no-return-assign
         addedTypes.concat(noteTypes).map(template => (template.treeView_Checked = this.templateIsUsed(firstDoc, template)));
         this._addedKeys &&
             Array.from(this._addedKeys)
@@ -122,10 +109,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                     addDocTab={returnFalse}
                     PanelWidth={this.return100}
                     PanelHeight={this.return100}
-                    treeViewHideHeaderFields={true}
-                    treeViewHideTitle={true}
-                    dontRegisterView={true}
-                    fieldKey={'data'}
+                    treeViewHideHeaderFields
+                    treeViewHideTitle
+                    dontRegisterView
+                    fieldKey="data"
                     moveDocument={returnFalse}
                     removeDocument={returnFalse}
                     addDocument={returnFalse}
@@ -135,10 +122,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     }
 }
 
-ScriptingGlobals.add(function switchView(doc: Doc, template: Doc | undefined) {
-    if (template?.dragFactory) {
-        template = Cast(template.dragFactory, Doc, null);
-    }
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function switchView(doc: Doc, templateIn: Doc | undefined) {
+    const template = templateIn?.dragFactory ? Cast(templateIn.dragFactory, Doc, null) : templateIn;
     const templateTitle = StrCast(template?.title);
     return templateTitle && DocUtils.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template);
 });
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
deleted file mode 100644
index 436cb688f..000000000
--- a/src/client/views/Touchable.tsx
+++ /dev/null
@@ -1,213 +0,0 @@
-import * as React from 'react';
-import { action } from 'mobx';
-import { InteractionUtils } from '../util/InteractionUtils';
-
-const HOLD_DURATION = 1000;
-
-export abstract class Touchable<T = {}> extends React.Component<React.PropsWithChildren<T>> {
-    //private holdTimer: NodeJS.Timeout | undefined;
-    private moveDisposer?: InteractionUtils.MultiTouchEventDisposer;
-    private endDisposer?: InteractionUtils.MultiTouchEventDisposer;
-    private holdMoveDisposer?: InteractionUtils.MultiTouchEventDisposer;
-    private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer;
-
-    protected abstract _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
-    protected _touchDrag: boolean = false;
-    protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
-
-    public FirstX: number = 0;
-    public FirstY: number = 0;
-    public SecondX: number = 0;
-    public SecondY: number = 0;
-
-    /**
-     * When a touch even starts, we keep track of each touch that is associated with that event
-     */
-    @action
-    protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
-        const actualPts: React.Touch[] = [];
-        const te = me.touchEvent;
-        // loop through all touches on screen
-        for (const pt of me.touches) {
-            actualPts.push(pt);
-            if (this.prevPoints.has(pt.identifier)) {
-                this.prevPoints.set(pt.identifier, pt);
-            }
-            // only add the ones that are targeted on "this" element, but with the identifier that the screen touch gives
-            for (const tPt of me.changedTouches) {
-                if (pt.clientX === tPt.clientX && pt.clientY === tPt.clientY) {
-                    // pen is also a touch, but with a radius of 0.5 (at least with the surface pens)
-                    // and this seems to be the only way of differentiating pen and touch on touch events
-                    if ((pt as any).radiusX > 1 && (pt as any).radiusY > 1) {
-                        this.prevPoints.set(pt.identifier, pt);
-                    }
-                }
-            }
-        }
-
-        const ptsToDelete: number[] = [];
-        this.prevPoints.forEach(pt => {
-            if (!actualPts.includes(pt)) {
-                ptsToDelete.push(pt.identifier);
-            }
-        });
-
-        ptsToDelete.forEach(pt => this.prevPoints.delete(pt));
-
-        if (this.prevPoints.size) {
-            switch (this.prevPoints.size) {
-                case 1:
-                    this.handle1PointerDown(te, me);
-                    te.persist();
-                    // -- code for radial menu --
-                    // if (this.holdTimer) {
-                    //     clearTimeout(this.holdTimer)
-                    //     this.holdTimer = undefined;
-                    // }
-                    break;
-                case 2:
-                    this.handle2PointersDown(te, me);
-                    break;
-            }
-        }
-    };
-
-    /**
-     * Handle touch move event
-     */
-    @action
-    protected onTouch = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
-        const te = me.touchEvent;
-        const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-
-        // if we're not actually moving a lot, don't consider it as dragging yet
-        if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
-        this._touchDrag = true;
-        switch (myTouches.length) {
-            case 1:
-                this.handle1PointerMove(te, me);
-                break;
-            case 2:
-                this.handle2PointersMove(te, me);
-                break;
-        }
-
-        for (const pt of me.touches) {
-            if (pt && this.prevPoints.has(pt.identifier)) {
-                this.prevPoints.set(pt.identifier, pt);
-            }
-        }
-    };
-
-    @action
-    protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
-        // remove all the touches associated with the event
-        const te = me.touchEvent;
-        for (const pt of me.changedTouches) {
-            if (pt) {
-                if (this.prevPoints.has(pt.identifier)) {
-                    this.prevPoints.delete(pt.identifier);
-                }
-            }
-        }
-        this._touchDrag = false;
-        te.stopPropagation();
-
-        // if (e.targetTouches.length === 0) {
-        //     this.prevPoints.clear();
-        // }
-
-        if (this.prevPoints.size === 0) {
-            this.cleanUpInteractions();
-        }
-        e.stopPropagation();
-    };
-
-    cleanUpInteractions = (): void => {
-        this.removeMoveListeners();
-        this.removeEndListeners();
-    };
-
-    handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => {
-        e.stopPropagation();
-        e.preventDefault();
-    };
-
-    handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>): any => {
-        e.stopPropagation();
-        e.preventDefault();
-    };
-
-    handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
-        this.removeMoveListeners();
-        this.addMoveListeners();
-        this.removeEndListeners();
-        this.addEndListeners();
-    };
-
-    handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
-        this.removeMoveListeners();
-        this.addMoveListeners();
-        this.removeEndListeners();
-        this.addEndListeners();
-    };
-
-    handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
-        e.stopPropagation();
-        me.touchEvent.stopPropagation();
-        this.removeMoveListeners();
-        this.removeEndListeners();
-        this.removeHoldMoveListeners();
-        this.removeHoldEndListeners();
-        this.addHoldMoveListeners();
-        this.addHoldEndListeners();
-    };
-
-    addMoveListeners = () => {
-        const handler = (e: Event) => this.onTouch(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
-        document.addEventListener('dashOnTouchMove', handler);
-        this.moveDisposer = () => document.removeEventListener('dashOnTouchMove', handler);
-    };
-    addEndListeners = () => {
-        const handler = (e: Event) => this.onTouchEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
-        document.addEventListener('dashOnTouchEnd', handler);
-        this.endDisposer = () => document.removeEventListener('dashOnTouchEnd', handler);
-    };
-
-    addHoldMoveListeners = () => {
-        const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
-        document.addEventListener('dashOnTouchHoldMove', handler);
-        this.holdMoveDisposer = () => document.removeEventListener('dashOnTouchHoldMove', handler);
-    };
-
-    addHoldEndListeners = () => {
-        const handler = (e: Event) => this.handle1PointerHoldEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
-        document.addEventListener('dashOnTouchHoldEnd', handler);
-        this.holdEndDisposer = () => document.removeEventListener('dashOnTouchHoldEnd', handler);
-    };
-
-    removeMoveListeners = () => this.moveDisposer?.();
-    removeEndListeners = () => this.endDisposer?.();
-    removeHoldMoveListeners = () => this.holdMoveDisposer?.();
-    removeHoldEndListeners = () => this.holdEndDisposer?.();
-
-    handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
-        // e.stopPropagation();
-        // me.touchEvent.stopPropagation();
-    };
-
-    handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
-        e.stopPropagation();
-        me.touchEvent.stopPropagation();
-        this.removeHoldMoveListeners();
-        this.removeHoldEndListeners();
-
-        me.touchEvent.stopPropagation();
-        me.touchEvent.preventDefault();
-    };
-
-    handleHandDown = (e: React.TouchEvent) => {
-        // e.stopPropagation();
-        // e.preventDefault();
-    };
-}
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index cc4da1694..c3e513154 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -4,7 +4,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { setupMoveUpEvents } from '../../../ClientUtils';
+import { Utils, emptyFunction } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { BoolCast, NumCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
diff --git a/src/client/views/animationtimeline/TimelineMenu.tsx b/src/client/views/animationtimeline/TimelineMenu.tsx
index 97a571dc4..20da21cb9 100644
--- a/src/client/views/animationtimeline/TimelineMenu.tsx
+++ b/src/client/views/animationtimeline/TimelineMenu.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { IconLookup } from '@fortawesome/fontawesome-svg-core';
 import { faChartLine, faClipboard } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -9,6 +10,7 @@ import './TimelineMenu.scss';
 
 @observer
 export class TimelineMenu extends React.Component {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: TimelineMenu;
 
     @observable private _opacity = 0;
@@ -67,16 +69,19 @@ export class TimelineMenu extends React.Component {
             this._currentMenu.push(
                 <div key={Utils.GenerateGuid()} className="timeline-menu-item">
                     <FontAwesomeIcon icon={faChartLine as IconLookup} size="lg" />
-                    <p
-                        className="timeline-menu-desc"
-                        onClick={e => {
-                            e.preventDefault();
-                            e.stopPropagation();
-                            event(e);
-                            this.closeMenu();
-                        }}>
-                        {title}
-                    </p>
+                    {
+                        // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
+                        <p
+                            className="timeline-menu-desc"
+                            onClick={e => {
+                                e.preventDefault();
+                                e.stopPropagation();
+                                event(e);
+                                this.closeMenu();
+                            }}>
+                            {title}
+                        </p>
+                    }
                 </div>
             );
         }
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index cbcc980a9..578ce77a5 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -1,7 +1,8 @@
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { dateRangeStrToDates, emptyFunction, returnTrue } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { dateRangeStrToDates, returnTrue } from '../../../ClientUtils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
 import { CollectionStackingView } from './CollectionStackingView';
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 4e4bd43bf..8d8f41126 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -1,9 +1,12 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Utils, emptyFunction, returnFalse, returnZero } from '../../../Utils';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { returnZero } from '../../../ClientUtils';
+import { Utils } from '../../../Utils';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -14,12 +17,13 @@ import { DocumentView } from '../nodes/DocumentView';
 import { FocusViewOptions } from '../nodes/FieldView';
 import './CollectionCarousel3DView.scss';
 import { CollectionSubView } from './CollectionSubView';
+
 const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss');
 
 @observer
 export class CollectionCarousel3DView extends CollectionSubView() {
     @computed get scrollSpeed() {
-        return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed
+        return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; // default scroll speed
     }
     constructor(props: any) {
         super(props);
@@ -48,16 +52,16 @@ export class CollectionCarousel3DView extends CollectionSubView() {
     panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE);
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
-    isChildContentActive = () => (this.isContentActive() ? true : false);
+    isChildContentActive = () => !!this.isContentActive();
     childScreenToLocal = () =>
         this._props // document's left is the panel shifted by the doc's index * panelWidth/#docs.  But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1)
             .ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor.
             .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
             .scale(1 / this.centerScale);
 
-    focus = (anchor: Doc, options: FocusViewOptions) => {
+    focus = (anchor: Doc, options: FocusViewOptions): Opt<number> => {
         const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
-        if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return;
+        if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return undefined;
         options.didMove = true;
         const target = DocCast(anchor.annotationOn) ?? anchor;
         const index = docs.indexOf(target);
@@ -66,37 +70,34 @@ export class CollectionCarousel3DView extends CollectionSubView() {
     };
     @computed get content() {
         const currentIndex = NumCast(this.layoutDoc._carousel_index);
-        const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
-            return (
-                <DocumentView
-                    {...this._props}
-                    Document={childPair.layout}
-                    TemplateDataDocument={childPair.data}
-                    //suppressSetHeight={true}
-                    NativeWidth={returnZero}
-                    NativeHeight={returnZero}
-                    layout_fitWidth={undefined}
-                    onDoubleClickScript={this.onChildDoubleClick}
-                    renderDepth={this._props.renderDepth + 1}
-                    LayoutTemplate={this._props.childLayoutTemplate}
-                    LayoutTemplateString={this._props.childLayoutString}
-                    focus={this.focus}
-                    ScreenToLocalTransform={this.childScreenToLocal}
-                    isContentActive={this.isChildContentActive}
-                    isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
-                    PanelWidth={this.panelWidth}
-                    PanelHeight={this.panelHeight}
-                />
-            );
-        };
-
-        return this.carouselItems.map((childPair, index) => {
-            return (
-                <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}>
-                    {displayDoc(childPair)}
-                </div>
-            );
-        });
+        const displayDoc = (childPair: { layout: Doc; data: Doc }) => (
+            <DocumentView
+                // eslint-disable-next-line react/jsx-props-no-spreading
+                {...this._props}
+                Document={childPair.layout}
+                TemplateDataDocument={childPair.data}
+                // suppressSetHeight={true}
+                NativeWidth={returnZero}
+                NativeHeight={returnZero}
+                layout_fitWidth={undefined}
+                onDoubleClickScript={this.onChildDoubleClick}
+                renderDepth={this._props.renderDepth + 1}
+                LayoutTemplate={this._props.childLayoutTemplate}
+                LayoutTemplateString={this._props.childLayoutString}
+                focus={this.focus}
+                ScreenToLocalTransform={this.childScreenToLocal}
+                isContentActive={this.isChildContentActive}
+                isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+                PanelWidth={this.panelWidth}
+                PanelHeight={this.panelHeight}
+            />
+        );
+
+        return this.carouselItems.map((childPair, index) => (
+            <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}>
+                {displayDoc(childPair)}
+            </div>
+        ));
     }
 
     changeSlide = (direction: number) => {
@@ -124,21 +125,21 @@ export class CollectionCarousel3DView extends CollectionSubView() {
     };
 
     toggleAutoScroll = (direction: number) => {
-        this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true;
+        this.layoutDoc.autoScrollOn = !this.layoutDoc.autoScrollOn;
         this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll();
     };
 
     fadeScrollButton = () => {
         window.setTimeout(() => {
-            !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); //fade away after 1.5s if it's not clicked.
+            !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); // fade away after 1.5s if it's not clicked.
         }, 1500);
     };
 
     @computed get buttons() {
         return (
             <div className="arrow-buttons">
-                <div title="click to go back" key="back" className="carousel3DView-back" onClick={e => this.onArrowClick(-1)} />
-                <div title="click to advance" key="fwd" className="carousel3DView-fwd" onClick={e => this.onArrowClick(1)} />
+                <div title="click to go back" key="back" className="carousel3DView-back" onClick={() => this.onArrowClick(-1)} />
+                <div title="click to advance" key="fwd" className="carousel3DView-fwd" onClick={() => this.onArrowClick(1)} />
                 {/* {this.autoScrollButton} */}
             </div>
         );
@@ -149,17 +150,25 @@ export class CollectionCarousel3DView extends CollectionSubView() {
         return (
             <>
                 <div className={`carousel3DView-back-scroll${whichButton === 'back' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(-1)}>
-                    {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-left'} size={'1x'} />}
+                    {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon="pause" size="1x" /> : <FontAwesomeIcon icon="angle-double-left" size="1x" />}
                 </div>
                 <div className={`carousel3DView-fwd-scroll${whichButton === 'fwd' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(1)}>
-                    {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-right'} size={'1x'} />}
+                    {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon="pause" size="1x" /> : <FontAwesomeIcon icon="angle-double-right" size="1x" />}
                 </div>
             </>
         );
     }
 
     @computed get dots() {
-        return this.carouselItems.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />);
+        return this.carouselItems.map((_child, index) => (
+            <div
+                key={Utils.GenerateGuid()}
+                className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`}
+                onClick={() => {
+                    this.layoutDoc._carousel_index = index;
+                }}
+            />
+        ));
     }
     @computed get translateX() {
         const index = NumCast(this.layoutDoc._carousel_index);
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 9c370bfbb..6dda6e545 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -2,9 +2,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { StopEvent, emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { StopEvent, returnFalse, returnOne, returnZero } from '../../../ClientUtils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { StyleProp } from '../StyleProvider';
 import { DocumentView } from '../nodes/DocumentView';
@@ -12,7 +14,6 @@ import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionCarouselView.scss';
 import { CollectionSubView } from './CollectionSubView';
-import { DocumentType } from '../../documents/DocumentTypes';
 
 @observer
 export class CollectionCarouselView extends CollectionSubView() {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index b2897a9b7..9a1781b93 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -2,25 +2,26 @@ import { action, IReactionDisposer, makeObservable, observable, reaction } from
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
-import * as GoldenLayout from '../../../client/goldenLayout';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, incrementTitleCopy } from '../../../ClientUtils';
 import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
 import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
-import { FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { GetEffectiveAcl, inheritParentAcls, SetPropSetterCb } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, emptyFunction, incrementTitleCopy } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { Docs } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import * as GoldenLayout from '../../goldenLayout';
 import { DocumentManager } from '../../util/DocumentManager';
 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 { SnappingManager } from '../../util/SnappingManager';
 import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
 import { DashboardView } from '../DashboardView';
 import { LightboxView } from '../LightboxView';
@@ -32,11 +33,12 @@ import './CollectionDockingView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionSubView } from './CollectionSubView';
 import { TabDocView } from './TabDocView';
-import { ComputedField } from '../../../fields/ScriptField';
+
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
 export class CollectionDockingView extends CollectionSubView() {
+    // eslint-disable-next-line no-use-before-define
     @observable public static Instance: CollectionDockingView | undefined = undefined;
     public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) {
         return {
@@ -68,7 +70,7 @@ export class CollectionDockingView extends CollectionSubView() {
         super(props);
         makeObservable(this);
         if (this._props.renderDepth < 0) CollectionDockingView.Instance = this;
-        //Why is this here?
+        // Why is this here?
         (window as any).React = React;
         (window as any).ReactDOM = ReactDOM;
         DragManager.StartWindowDrag = this.StartOtherDrag;
@@ -201,6 +203,7 @@ export class CollectionDockingView extends CollectionSubView() {
             } else if (instance._goldenLayout.root.contentItems[0].isRow) {
                 // if row
                 switch (pullSide) {
+                    // eslint-disable-next-line default-case-last
                     default:
                     case OpenWhereMod.none:
                     case OpenWhereMod.right:
@@ -210,7 +213,7 @@ export class CollectionDockingView extends CollectionSubView() {
                         glayRoot.contentItems[0].addChild(newContentItem(), 0);
                         break;
                     case OpenWhereMod.top:
-                    case OpenWhereMod.bottom:
+                    case OpenWhereMod.bottom: {
                         // if not going in a row layout, must add already existing content into column
                         const rowlayout = glayRoot.contentItems[0];
                         const newColumn = rowlayout.layoutManager.createContentItem({ type: 'column' }, instance._goldenLayout);
@@ -229,6 +232,7 @@ export class CollectionDockingView extends CollectionSubView() {
 
                         rowlayout.config.height = 50;
                         newItem.config.height = 50;
+                    }
                 }
             } else {
                 // if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
@@ -241,7 +245,7 @@ export class CollectionDockingView extends CollectionSubView() {
                         break;
                     case 'left':
                     case 'right':
-                    default:
+                    default: {
                         // if not going in a row layout, must add already existing content into column
                         const collayout = glayRoot.contentItems[0];
                         const newRow = collayout.layoutManager.createContentItem({ type: 'row' }, instance._goldenLayout);
@@ -260,6 +264,7 @@ export class CollectionDockingView extends CollectionSubView() {
 
                         collayout.config.width = 50;
                         newItem.config.width = 50;
+                    }
                 }
             }
             instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig());
@@ -278,10 +283,10 @@ export class CollectionDockingView extends CollectionSubView() {
     }
     setupGoldenLayout = async () => {
         if (this._unmounting) return;
-        //const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document)));
+        // const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document)));
         const config = StrCast(this.Document.dockingConfig);
         if (config) {
-            const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+            const matches = config.match(/"documentId":"[a-z0-9-]+"/g);
             const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? [];
 
             await Promise.all(docids.map(id => DocServer.GetRefField(id)));
@@ -289,12 +294,13 @@ export class CollectionDockingView extends CollectionSubView() {
             if (this._goldenLayout) {
                 if (config === JSON.stringify(this._goldenLayout.toConfig())) {
                     return;
-                } else {
-                    try {
-                        this._goldenLayout.unbind('tabCreated', this.tabCreated);
-                        this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
-                        this._goldenLayout.unbind('stackCreated', this.stackCreated);
-                    } catch (e) {}
+                }
+                try {
+                    this._goldenLayout.unbind('tabCreated', this.tabCreated);
+                    this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
+                    this._goldenLayout.unbind('stackCreated', this.stackCreated);
+                } catch (e) {
+                    /* empty */
                 }
                 this.tabMap.clear();
                 this._goldenLayout.destroy();
@@ -323,7 +329,7 @@ export class CollectionDockingView extends CollectionSubView() {
      */
     titleChanged = (target: any, value: any) => {
         const title = Field.toString(value);
-        if (title.startsWith('@') && !title.substring(1).match(/[\(\)\[\]@]/) && title.length > 1) {
+        if (title.startsWith('@') && !title.substring(1).match(/[()[\]@]/) && title.length > 1) {
             const embedding = DocListCast(target.proto_embeddings).lastElement();
             embedding && Doc.AddToMyPublished(embedding);
         } else if (!title.startsWith('@')) {
@@ -337,7 +343,7 @@ export class CollectionDockingView extends CollectionSubView() {
         if (this._containerRef.current) {
             this._lightboxReactionDisposer = reaction(
                 () => LightboxView.LightboxDoc,
-                doc => setTimeout(() => !doc && this.onResize(undefined))
+                doc => setTimeout(() => !doc && this.onResize())
             );
             new _global.ResizeObserver(this.onResize).observe(this._containerRef.current);
             this._reactionDisposer = reaction(
@@ -356,12 +362,12 @@ export class CollectionDockingView extends CollectionSubView() {
                 { fireImmediately: true }
             );
             reaction(
-                () => [SettingsManager.userBackgroundColor, SettingsManager.userBackgroundColor],
+                () => [SnappingManager.userBackgroundColor, SnappingManager.userBackgroundColor],
                 () => {
                     clearStyleSheetRules(CollectionDockingView._highlightStyleSheet);
-                    addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { background: `${SettingsManager.userBackgroundColor} !important` });
-                    addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { color: `${SettingsManager.userColor} !important` });
-                    addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${SettingsManager.userBackgroundColor} !important` });
+                    addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { background: `${SnappingManager.userBackgroundColor} !important` });
+                    addStyleSheetRule(CollectionDockingView._highlightStyleSheet, 'lm_controls', { color: `${SnappingManager.userColor} !important` });
+                    addStyleSheetRule(SnappingManager.SettingsStyle, 'lm_header', { background: `${SnappingManager.userBackgroundColor} !important` });
                 },
                 { fireImmediately: true }
             );
@@ -374,7 +380,9 @@ export class CollectionDockingView extends CollectionSubView() {
         try {
             this._goldenLayout.unbind('stackCreated', this.stackCreated);
             this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
-        } catch (e) {}
+        } catch (e) {
+            /* empty */
+        }
         this._goldenLayout?.destroy();
         window.removeEventListener('resize', this.onResize);
         window.removeEventListener('mouseup', this.onPointerUp);
@@ -384,7 +392,7 @@ export class CollectionDockingView extends CollectionSubView() {
     };
 
     @action
-    onResize = (event: any) => {
+    onResize = () => {
         const cur = this._containerRef.current;
         // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
         !LightboxView.LightboxDoc && cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height);
@@ -392,7 +400,7 @@ export class CollectionDockingView extends CollectionSubView() {
 
     endUndoBatch = () => {
         const json = JSON.stringify(this._goldenLayout.toConfig());
-        const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+        const matches = json.match(/"documentId":"[a-z0-9-]+"/g);
         const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', ''));
         const docs = !docids
             ? []
@@ -415,7 +423,7 @@ export class CollectionDockingView extends CollectionSubView() {
     };
 
     @action
-    onPointerUp = (e: MouseEvent): void => {
+    onPointerUp = (): void => {
         window.removeEventListener('pointerup', this.onPointerUp);
         DragManager.CompleteWindowDrag = undefined;
         setTimeout(this.endUndoBatch, 100);
@@ -453,24 +461,27 @@ export class CollectionDockingView extends CollectionSubView() {
         if (content) {
             const _width = DivWidth(content);
             const _height = DivHeight(content);
-            return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', (iconFile, _nativeWidth, _nativeHeight) => {
+            return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', iconFile => {
                 const proto = this.dataDoc; // Cast(img.proto, Doc, null)!;
-                proto['thumb_nativeWidth'] = _width;
-                proto['thumb_nativeHeight'] = _height;
+                proto.thumb_nativeWidth = _width;
+                proto.thumb_nativeHeight = _height;
                 proto.thumb = new ImageField(iconFile);
             });
         }
+        return undefined;
     }
     public static async TakeSnapshot(doc: Doc | undefined, clone = false) {
         if (!doc) return undefined;
         let json = StrCast(doc.dockingConfig);
         if (clone) {
             const cloned = await Doc.MakeClone(doc);
-            Array.from(cloned.map.entries()).map(entry => (json = json.replace(entry[0], entry[1][Id])));
+            Array.from(cloned.map.entries()).forEach(entry => {
+                json = json.replace(entry[0], entry[1][Id]);
+            });
             cloned.clone[DocData].dockingConfig = json;
             return DashboardView.openDashboard(cloned.clone);
         }
-        const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
+        const matches = json.match(/"documentId":"[a-z0-9-]+"/g);
         const origtabids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) || [];
         const origtabs = origtabids
             .map(id => DocServer.GetCachedRefField(id))
@@ -508,19 +519,20 @@ export class CollectionDockingView extends CollectionSubView() {
 
     tabDestroyed = (tab: any) => {
         this._flush = this._flush ?? UndoManager.StartBatch('tab movement');
-        if (tab.DashDoc && ![DocumentType.PRES].includes(tab.DashDoc?.type) && !tab.contentItem.config.props.keyValue) {
-            Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc, undefined, undefined, true);
+        const dashDoc = tab.DashDoc;
+        if (dashDoc && ![DocumentType.PRES].includes(dashDoc.type) && !tab.contentItem.config.props.keyValue) {
+            Doc.AddDocToList(Doc.MyHeaderBar, 'data', dashDoc, undefined, undefined, true);
             // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed
-            if (tab.DashDoc.embedContainer === this.Document) tab.DashDoc.embedContainer = undefined;
-            if (!tab.DashDoc.embedContainer) {
-                Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
-                Doc.RemoveEmbedding(tab.DashDoc, tab.DashDoc);
+            if (dashDoc.embedContainer === this.Document) dashDoc.embedContainer = undefined;
+            if (!dashDoc.embedContainer) {
+                Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true);
+                Doc.RemoveEmbedding(dashDoc, dashDoc);
             }
         }
         if (CollectionDockingView.Instance) {
             const dview = CollectionDockingView.Instance.Document;
-            const fieldKey = CollectionDockingView.Instance.props.fieldKey;
-            Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
+            const { fieldKey } = CollectionDockingView.Instance.props;
+            Doc.RemoveDocFromList(dview, fieldKey, dashDoc);
             this.tabMap.delete(tab);
             tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
             this.stateChanged();
@@ -531,8 +543,8 @@ export class CollectionDockingView extends CollectionSubView() {
         tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
     };
 
-    stackCreated = (stack: any) => {
-        stack = stack.header ? stack : stack.origin;
+    stackCreated = (stackIn: any) => {
+        const stack = stackIn.header ? stackIn : stackIn.origin;
         stack.header?.element.on('mousedown', (e: any) => {
             const dashboard = Doc.ActiveDashboard;
             if (dashboard && e.target === stack.header?.element[0] && e.button === 2) {
@@ -550,32 +562,29 @@ export class CollectionDockingView extends CollectionSubView() {
             }
         });
 
-        let addNewDoc = undoable(
-            action(() => {
-                const dashboard = Doc.ActiveDashboard;
-                if (dashboard) {
-                    dashboard.pane_count = NumCast(dashboard.pane_count) + 1;
-                    const docToAdd = Docs.Create.FreeformDocument([], {
-                        _width: this._props.PanelWidth(),
-                        _height: this._props.PanelHeight(),
-                        _layout_fitWidth: true,
-                        _freeform_backgroundGrid: true,
-                        title: `Untitled Tab ${NumCast(dashboard.pane_count)}`,
-                    });
-                    Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
-                    inheritParentAcls(this.dataDoc, docToAdd, false);
-                    CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
-                }
-            }),
-            'add new tab'
-        );
+        const addNewDoc = undoable(() => {
+            const dashboard = Doc.ActiveDashboard;
+            if (dashboard) {
+                dashboard.pane_count = NumCast(dashboard.pane_count) + 1;
+                const docToAdd = Docs.Create.FreeformDocument([], {
+                    _width: this._props.PanelWidth(),
+                    _height: this._props.PanelHeight(),
+                    _layout_fitWidth: true,
+                    _freeform_backgroundGrid: true,
+                    title: `Untitled Tab ${NumCast(dashboard.pane_count)}`,
+                });
+                Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
+                inheritParentAcls(this.dataDoc, docToAdd, false);
+                CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
+            }
+        }, 'add new tab');
 
         stack.header?.controlsContainer
-            .find('.lm_close') //get the close icon
-            .off('click') //unbind the current click handler
+            .find('.lm_close') // get the close icon
+            .off('click') // unbind the current click handler
             .click(
                 action(() => {
-                    //if (confirm('really close this?')) {
+                    // if (confirm('really close this?')) {
                     if ((!stack.parent.isRoot && !stack.parent.parent.isRoot) || stack.parent.contentItems.length > 1) {
                         const batch = UndoManager.StartBatch('close stack');
                         stack.remove();
@@ -595,11 +604,11 @@ export class CollectionDockingView extends CollectionSubView() {
             }
         });
         stack.header?.controlsContainer
-            .find('.lm_maximise') //get the close icon
+            .find('.lm_maximise') // get the close icon
             .click(() => setTimeout(this.stateChanged));
         stack.header?.controlsContainer
-            .find('.lm_popout') //get the popout icon
-            .off('click') //unbind the current click handler
+            .find('.lm_popout') // get the popout icon
+            .off('click') // unbind the current click handler
             .click(addNewDoc);
     };
 
@@ -609,13 +618,14 @@ export class CollectionDockingView extends CollectionSubView() {
             <div>
                 {href ? (
                     <img
+                        alt="thumbnail of nested dashboard"
                         style={{ background: 'white', top: 0, position: 'absolute' }}
                         src={href} // + '?d=' + (new Date()).getTime()}
                         width={this._props.PanelWidth()}
                         height={this._props.PanelHeight()}
                     />
                 ) : (
-                    <p>nested dashboards has no thumbnail</p>
+                    <p>nested dashboard has no thumbnail</p>
                 )}
             </div>
         ) : (
@@ -625,6 +635,7 @@ export class CollectionDockingView extends CollectionSubView() {
 }
 
 ScriptingGlobals.add(
+    // eslint-disable-next-line prefer-arrow-callback
     function openInLightbox(doc: any) {
         LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox);
     },
@@ -632,26 +643,37 @@ ScriptingGlobals.add(
     '(doc: any)'
 );
 ScriptingGlobals.add(
+    // eslint-disable-next-line prefer-arrow-callback
     function openDoc(doc: any, where: OpenWhere) {
         switch (where) {
             case OpenWhere.addRight:
                 return CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
             case OpenWhere.overlay:
+            default:
                 // prettier-ignore
                 switch (doc) {
                     case '<ScriptingRepl />': return OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
                     case "<UndoStack />": return OverlayView.Instance.addWindow(<UndoStack />, { x: 300, y: 100, width: 200, height: 200, title: 'Undo stack' });
+                    default: 
                 }
                 Doc.AddToMyOverlay(doc);
+                return true;
         }
     },
     'opens up document in location specified',
     '(doc: any)'
 );
 ScriptingGlobals.add(
+    // eslint-disable-next-line prefer-arrow-callback
     function openRepl() {
         return 'openRepl';
     },
     'opens up document in screen overlay layer',
     '(doc: any)'
 );
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(async function snapshotDashboard() {
+    const batch = UndoManager.StartBatch('snapshot');
+    await CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard);
+    batch.end();
+}, 'creates a snapshot copy of a dashboard');
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 7dcfd32bd..2d906dfe7 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -2,7 +2,8 @@ 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 { returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction, numberRange } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 4f25f69ef..a23725348 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -4,7 +4,8 @@ import { Toggle, ToggleType, Type } from 'browndash-components';
 import { Lambda, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { List } from '../../../fields/List';
@@ -12,7 +13,8 @@ import { ObjectField } from '../../../fields/ObjectField';
 import { RichTextField } from '../../../fields/RichTextField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { Transform } from '../../util/Transform';
@@ -79,7 +81,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
 
     buttonBarXf = () => {
         if (!this._docBtnRef.current) return Transform.Identity();
-        const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
+        const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._docBtnRef.current);
         return new Transform(-translateX, -translateY, 1 / scale);
     };
 
@@ -123,7 +125,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
         const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
 
         const hardCodedButtons = (
-            <div className={`hardCodedButtons`}>
+            <div className="hardCodedButtons">
                 <Toggle
                     toggleType={ToggleType.BUTTON}
                     type={Type.PRIM}
@@ -145,7 +147,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
             </div>
         );
 
-        //dash col linear view buttons
+        // dash col linear view buttons
         const contMenuButtons = (
             <div
                 className="collectionMenu-container"
@@ -172,7 +174,7 @@ const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
 
 @observer
 export class CollectionViewBaseChrome extends React.Component<CollectionViewMenuProps> {
-    //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
+    // (!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
 
     get document() {
         return this.props.docView?.Document;
@@ -206,14 +208,18 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         params: ['target', 'source'],
         title: 'child click view',
         script: 'this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])',
-        immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
+        immediate: undoBatch((source: Doc[]) => {
+            source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]));
+        }),
         initialize: emptyFunction,
     };
     _contentCommand = {
         params: ['target', 'source'],
         title: 'set content',
         script: 'getProto(this.target).data = copyField(this.source);',
-        immediate: undoBatch((source: Doc[]) => (this.target[DocData].data = new List<Doc>(source))),
+        immediate: undoBatch((source: Doc[]) => {
+            this.target[DocData].data = new List<Doc>(source);
+        }),
         initialize: emptyFunction,
     };
     _onClickCommand = {
@@ -224,14 +230,14 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
              getProto(this.proxy[0]).target = this.target.target;
              getProto(this.proxy[0]).source = copyField(this.target.source); 
             }}`,
-        immediate: undoBatch((source: Doc[]) => {}),
+        immediate: undoBatch(() => {}),
         initialize: emptyFunction,
     };
     _viewCommand = {
         params: ['target'],
         title: 'bookmark view',
         script: "this.target._freeform_panX = self['target-freeform_panX']; this.target._freeform_panY = this['target-freeform_panY']; this.target._freeform_scale = this['target_freeform_scale']; gotoFrame(this.target, this['target-currentFrame']);",
-        immediate: undoBatch((source: Doc[]) => {
+        immediate: undoBatch(() => {
             this.target._freeform_panX = 0;
             this.target._freeform_panY = 0;
             this.target._freeform_scale = 1;
@@ -248,14 +254,18 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         params: ['target'],
         title: 'fit content',
         script: 'this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox;',
-        immediate: undoBatch((source: Doc[]) => (this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox)),
+        immediate: undoBatch(() => {
+            this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox;
+        }),
         initialize: emptyFunction,
     };
     _fitContentCommand = {
         params: ['target'],
         title: 'toggle clusters',
         script: 'this.target._freeform_useClusters = !this.target._freeform_useClusters;',
-        immediate: undoBatch((source: Doc[]) => (this.target._freeform_useClusters = !this.target._freeform_useClusters)),
+        immediate: undoBatch(() => {
+            this.target._freeform_useClusters = !this.target._freeform_useClusters;
+        }),
         initialize: emptyFunction,
     };
     _saveFilterCommand = {
@@ -263,7 +273,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         title: 'save filter',
         script: `this.target._childFilters = compareLists(this['target-childFilters'],this.target._childFilters) ? undefined : copyField(this['target-childFilters']); 
         this.target._searchFilterDocs = compareLists(this['target-searchFilterDocs'],this.target._searchFilterDocs) ? undefined: copyField(this['target-searchFilterDocs']);`,
-        immediate: undoBatch((source: Doc[]) => {
+        immediate: undoBatch(() => {
             this.target._childFilters = undefined;
             this.target._searchFilterDocs = undefined;
         }),
@@ -332,12 +342,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     @undoBatch
     viewChanged = (e: React.ChangeEvent) => {
         const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : DocCast(this.document.proto);
-        //@ts-ignore
         target._type_collection = e.target.selectedOptions[0].value;
     };
 
     commandChanged = (e: React.ChangeEvent) => {
-        //@ts-ignore
         runInAction(() => (this._currentKey = e.target.selectedOptions[0].value));
     };
 
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index d8a0aebb1..4ceeb631d 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,7 +1,8 @@
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, Field, Opt } from '../../../fields/Doc';
+import { ClientUtils, DivHeight, lightOrDark, returnZero, smoothScroll } from '../../../ClientUtils';
+import { Doc, Field, FieldType, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Copy, Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
@@ -9,14 +10,16 @@ import { listSpec } from '../../../fields/Schema';
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { DivHeight, emptyFunction, lightOrDark, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoable, undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
+import { FieldsDropdown } from '../FieldsDropdown';
 import { Colors } from '../global/globalEnums';
 import { LightboxView } from '../LightboxView';
 import { DocumentView } from '../nodes/DocumentView';
@@ -27,7 +30,7 @@ import './CollectionNoteTakingView.scss';
 import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn';
 import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider';
 import { CollectionSubView } from './CollectionSubView';
-import { FieldsDropdown } from '../FieldsDropdown';
+
 const _global = (window /* browser */ || global) /* node */ as any;
 
 /**
@@ -300,7 +303,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     // getDocTransform is used to get the coordinates of a document when we go from a view like freeform to columns
     getDocTransform(doc: Doc, dref?: DocumentView) {
         const y = this._scroll; // required for document decorations to update when the text box container is scrolled
-        const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined);
+        const { translateX, translateY } = ClientUtils.GetScreenTransform(dref?.ContentDiv || undefined);
         // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
         return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.ScreenToLocalBoxXf().Scale);
     }
@@ -308,7 +311,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     // how to get the width of a document. Currently returns the width of the column (minus margins)
     // if a note doc. Otherwise, returns the normal width (for graphs, images, etc...)
     getDocWidth(d: Doc) {
-        const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as Field);
+        const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as FieldType);
         const existingHeader = this.colHeaderData.find(sh => sh.heading === heading);
         const existingWidth = this.layoutDoc._notetaking_columns_autoSize ? 1 / (this.colHeaderData.length ?? 1) : existingHeader?.width ? existingHeader.width : 0;
         const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth;
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 448b11b05..e00fdb50c 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { lightOrDark, returnEmptyString } from '../../../Utils';
+import { lightOrDark, returnEmptyString } from '../../../ClientUtils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { RichTextField } from '../../../fields/RichTextField';
 import { listSpec } from '../../../fields/Schema';
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index 50a97b978..67070b24d 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -1,7 +1,8 @@
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { setupMoveUpEvents } from '../../../ClientUtils';
 import { UndoManager } from '../../util/UndoManager';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 9d68c621b..e39f1c700 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,12 +1,13 @@
 import { action, computed, IReactionDisposer, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { ScriptField } from '../../../fields/ScriptField';
 import { NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
-import { dropActionType } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { SelectionManager } from '../../util/SelectionManager';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { OpenWhere } from '../nodes/DocumentView';
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 656f850b3..3f6638b25 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -2,6 +2,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../ClientUtils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -10,7 +11,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { Cast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
+import { emptyFunction, formatTime } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index bf0393883..b79d660c6 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,8 +1,10 @@
+/* eslint-disable react/jsx-props-no-spreading */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import * as CSS from 'csstype';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { ClientUtils, DivHeight, returnEmptyDoclist, returnNone, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -11,10 +13,11 @@ 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 { DivHeight, emptyFunction, returnEmptyDoclist, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { SettingsManager } from '../../util/SettingsManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
@@ -31,6 +34,7 @@ import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
 import './CollectionStackingView.scss';
 import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
 import { CollectionSubView } from './CollectionSubView';
+
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export type collectionStackingViewProps = {
@@ -125,11 +129,16 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     // TODO: plj - these are the children
     children = (docs: Doc[]) => {
-        //TODO: can somebody explain me to what exactly TraceMobX is?
+        // TODO: can somebody explain me to what exactly TraceMobX is?
         TraceMobx();
         // appears that we are going to reset the _docXfs. TODO: what is Xfs?
         this._docXfs.length = 0;
-        this._renderCount < docs.length && setTimeout(action(() => (this._renderCount = Math.min(docs.length, this._renderCount + 5))));
+        this._renderCount < docs.length &&
+            setTimeout(
+                action(() => {
+                    this._renderCount = Math.min(docs.length, this._renderCount + 5);
+                })
+            );
         return docs.map((d, i) => {
             const height = () => this.getDocHeight(d);
             const width = () => this.getDocWidth(d);
@@ -152,19 +161,21 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     };
 
     // is sections that all collections inherit? I think this is how we show the masonry/columns
-    //TODO: this seems important
+    // TODO: this seems important
     get Sections() {
         // appears that pivot field IS actually for sorting
         if (!this.pivotField || this.colHeaderData instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
 
         if (this.colHeaderData === undefined) {
-            setTimeout(() => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>()), 0);
+            setTimeout(() => {
+                this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>();
+            });
             return new Map<SchemaHeaderField, Doc[]>();
         }
         const colHeaderData = Array.from(this.colHeaderData);
         const fields = new Map<SchemaHeaderField, Doc[]>(colHeaderData.map(sh => [sh, []] as [SchemaHeaderField, []]));
         let changed = false;
-        this.filteredChildren.map(d => {
+        this.filteredChildren.forEach(d => {
             const sectionValue = (d[this.pivotField] ? d[this.pivotField] : `NO ${this.pivotField.toUpperCase()} VALUE`) as object;
             // the next five lines ensures that floating point rounding errors don't create more than one section -syip
             const parsed = parseInt(sectionValue.toString());
@@ -186,7 +197,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         if (this.layoutDoc._columnsHideIfEmpty) {
             Array.from(fields.keys())
                 .filter(key => !fields.get(key)!.length)
-                .map(header => {
+                .forEach(header => {
                     fields.delete(header);
                     colHeaderData.splice(colHeaderData.indexOf(header), 1);
                     changed = true;
@@ -207,11 +218,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         // reset section headers when a new filter is inputted
         this._disposers.pivotField = reaction(
             () => this.pivotField,
-            () => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List())
+            () => {
+                this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List();
+            }
         );
         this._disposers.autoHeight = reaction(
             () => this.layoutDoc._layout_autoHeight,
-            layout_autoHeight => layout_autoHeight && this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0)))
+            layoutAutoHeight => layoutAutoHeight && this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0)))
         );
         this._disposers.refList = reaction(
             () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }),
@@ -231,9 +244,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     isAnyChildContentActive = () => this._props.isAnyChildContentActive();
 
-    moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
-        return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
-    };
+    moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => !!(this._props.removeDocument?.(doc) && addDocument?.(doc));
 
     onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
     @computed get onChildDoubleClickHandler() {
@@ -250,10 +261,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
         if (found) {
-            const top = found.getBoundingClientRect().top;
+            const { top } = found.getBoundingClientRect();
             const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top);
             if (Math.floor(localTop[1]) !== 0) {
-                let focusSpeed = options.zoomTime ?? 500;
+                const focusSpeed = options.zoomTime ?? 500;
                 smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
                 return focusSpeed;
             }
@@ -276,18 +287,18 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
         if (['Enter'].includes(e.key) && e.ctrlKey) {
             e.stopPropagation?.();
-            const below = !e.altKey && e.key !== 'Tab';
-            const layout_fieldKey = StrCast(fieldProps.fieldKey);
+            const layoutFieldKey = StrCast(fieldProps.fieldKey);
             const newDoc = Doc.MakeCopy(fieldProps.Document, true);
             const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
             newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
-            if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
-                newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
+            if (layoutFieldKey !== 'layout' && fieldProps.Document[layoutFieldKey] instanceof Doc) {
+                newDoc[layoutFieldKey] = fieldProps.Document[layoutFieldKey];
             }
             newDoc[DocData].text = undefined;
             FormattedTextBox.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
+        return false;
     };
     isContentActive = () => (this._props.isContentActive() ? true : this._props.isSelected() === false || this._props.isContentActive() === false ? false : undefined);
 
@@ -363,7 +374,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     getDocTransform(doc: Doc) {
         const dref = this.docRefs.get(doc);
         this._scroll; // must be referenced for document decorations to update when the text box container is scrolled
-        const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv);
+        const { translateX, translateY } = ClientUtils.GetScreenTransform(dref?.ContentDiv);
         // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
         return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.ScreenToLocalBoxXf().Scale);
     }
@@ -399,7 +410,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     // This following three functions must be from the view Mehek showed
     columnDividerDown = (e: React.PointerEvent) => {
-        runInAction(() => (this._cursor = 'grabbing'));
+        runInAction(() => {
+            this._cursor = 'grabbing';
+        });
         const batch = UndoManager.StartBatch('stacking width');
         setupMoveUpEvents(
             this,
@@ -425,7 +438,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 onPointerDown={this.columnDividerDown}
                 ref={this._draggerRef}
                 style={{ cursor: this._cursor, color: SettingsManager.userColor, left: `${this.columnWidth + this.xMargin}px`, top: `${Math.max(0, this.yMargin - 9)}px` }}>
-                <FontAwesomeIcon icon={'arrows-alt-h'} />
+                <FontAwesomeIcon icon="arrows-alt-h" />
             </div>
         );
     }
@@ -439,7 +452,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         let dropAfter = 0;
         if (de.complete.docDragData) {
             // going to re-add the docs to the _docXFs based on position of where we just dropped
-            this._docXfs.map((cd, i) => {
+            this._docXfs.forEach((cd, i) => {
                 const pos = cd
                     .stackedDocTransform()
                     .inverse()
@@ -496,7 +509,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     onExternalDrop = async (e: React.DragEvent): Promise<void> => {
         const where = [e.clientX, e.clientY];
         let targInd = -1;
-        this._docXfs.map((cd, i) => {
+        this._docXfs.forEach((cd, i) => {
             const pos = cd
                 .stackedDocTransform()
                 .inverse()
@@ -521,10 +534,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     // what a section looks like if we're in stacking view
     sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
         const key = this.pivotField;
-        let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined;
+        let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
         if (this.pivotField) {
             const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
             if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
+                // eslint-disable-next-line prefer-destructuring
                 type = types[0];
             }
         }
@@ -557,9 +571,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     // what a section looks like if we're in masonry. Shouldn't actually need to use this.
     sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => {
         const key = this.pivotField;
-        let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined;
+        let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
         const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
         if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
+            // eslint-disable-next-line prefer-destructuring
             type = types[0];
         }
         const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
@@ -609,9 +624,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             const cm = ContextMenu.Instance;
             const options = cm.findByDescription('Options...');
             const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
-            optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' });
-            optionItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: 'plus' });
-            optionItems.push({ description: 'Clear All', event: () => (this.dataDoc[this.fieldKey ?? 'data'] = new List([])), icon: 'times' });
+            optionItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => { this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill; }, icon: 'plus' }); // prettier-ignore
+            optionItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => { this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight; }, icon: 'plus' }); // prettier-ignore
+            optionItems.push({ description: 'Clear All', event: () => { this.dataDoc[this.fieldKey ?? 'data'] = new List([]); } , icon: 'times' }); // prettier-ignore
             !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
         }
     };
@@ -700,7 +715,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         className={this.isStackingView ? 'collectionStackingView' : 'collectionMasonryView'}
                         ref={ele => {
                             this._masonryGridRef = ele;
-                            this.createDashEventsTarget(ele); //so the whole grid is the drop target?
+                            this.createDashEventsTarget(ele); // so the whole grid is the drop target?
                             this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
                             this._oldWheel = ele;
                             // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
@@ -711,7 +726,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                             background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor),
                             pointerEvents: (this._props.pointerEvents?.() as any) ?? this.backgroundEvents,
                         }}
-                        onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
+                        onScroll={action(e => {
+                            this._scroll = e.currentTarget.scrollTop;
+                        })}
                         onDrop={this.onExternalDrop.bind(this)}
                         onContextMenu={this.onContextMenu}
                         onWheel={e => this.isContentActive() && e.stopPropagation()}>
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index c5292f880..35f330b44 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -1,7 +1,11 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { DivHeight, DivWidth, returnEmptyString, setupMoveUpEvents } from '../../../ClientUtils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { RichTextField } from '../../../fields/RichTextField';
 import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
@@ -9,10 +13,11 @@ import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { DivHeight, DivWidth, emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
@@ -95,7 +100,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         this._ele && this.props.refList.push(this._ele);
         this._disposers.collapser = reaction(
             () => this._props.headingObject?.collapsed,
-            collapsed => (this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false),
+            collapsed => { this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false; }, // prettier-ignore
             { fireImmediately: true }
         );
     }
@@ -105,7 +110,6 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         this._ele = null;
     }
 
-    //TODO: what is scripting? I found it in SetInPlace def but don't know what that is
     @undoBatch
     columnDrop = action((e: Event, de: DragManager.DropEvent) => {
         const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
@@ -121,13 +125,13 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
     };
 
     @action
-    headingChanged = (value: string, shiftDown?: boolean) => {
+    headingChanged = (value: string /* , shiftDown?: boolean */) => {
         const castedValue = this.getValue(value);
         if (castedValue) {
             if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
                 return false;
             }
-            this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
+            this._props.pivotField && this._props.docList.forEach(d => { d[this._props.pivotField] = castedValue; }) // prettier-ignore
             if (this._props.headingObject) {
                 this._props.headingObject.setHeading(castedValue.toString());
                 this._heading = this._props.headingObject.heading;
@@ -143,9 +147,9 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         this._color = color;
     };
 
-    @action pointerEntered = () => SnappingManager.IsDragging && (this._background = '#b4b4b4');
-    @action pointerLeave = () => (this._background = 'inherit');
-    @undoBatch typedNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
+    @action pointerEntered = () => { SnappingManager.IsDragging && (this._background = '#b4b4b4'); } // prettier-ignore
+    @action pointerLeave = () => { this._background = 'inherit'}; // prettier-ignore
+    @undoBatch typedNote = () => this.addNewTextDoc('-typed text-', false, true);
 
     @action
     addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
@@ -163,7 +167,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
 
     @action
     deleteColumn = () => {
-        this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
+        this._props.pivotField &&
+            this._props.docList.forEach(d => {
+                d[this._props.pivotField] = undefined;
+            });
         if (this._props.colHeaderData && this._props.headingObject) {
             const index = this._props.colHeaderData.indexOf(this._props.headingObject);
             this._props.colHeaderData.splice(index, 1);
@@ -178,8 +185,8 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
 
     headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
 
-    //TODO: I think this is where I'm supposed to edit stuff
-    startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
+    // TODO: I think this is where I'm supposed to edit stuff
+    startDrag = (e: PointerEvent) => {
         // is MakeEmbedding a way to make a copy of a doc without rendering it?
         const embedding = Doc.MakeEmbedding(this._props.Document);
         embedding._width = this._props.columnWidth / (this._props.colHeaderData?.length || 1);
@@ -210,23 +217,23 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         );
     };
 
-    renderMenu = () => {
-        return (
-            <div className="collectionStackingView-optionPicker">
-                <div className="optionOptions">
-                    <div className={'optionPicker' + (true ? ' active' : '')} onClick={action(() => {})}>
-                        Add options here
-                    </div>
+    renderMenu = () => (
+        <div className="collectionStackingView-optionPicker">
+            <div className="optionOptions">
+                <div className={'optionPicker' + (true ? ' active' : '')} onClick={action(() => {})}>
+                    Add options here
                 </div>
             </div>
-        );
-    };
+        </div>
+    );
 
     @observable private collapsed: boolean = false;
 
-    private toggleVisibility = action(() => (this.collapsed = !this.collapsed));
+    private toggleVisibility = action(() => {
+        this.collapsed = !this.collapsed;
+    });
 
-    menuCallback = (x: number, y: number) => {
+    menuCallback = () => {
         ContextMenu.Instance.clearItems();
         const layoutItems: ContextMenuProps[] = [];
         const docItems: ContextMenuProps[] = [];
@@ -257,6 +264,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                             }
                             return this._props.addDocument?.(created);
                         }
+                        return false;
                     },
                     icon: 'compress-arrows-alt',
                 })
@@ -276,6 +284,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                             }
                             return this._props.addDocument?.(created) || false;
                         }
+                        return false;
                     },
                     icon: 'compress-arrows-alt',
                 })
@@ -307,7 +316,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin;
         const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
         const noValueHeader = `NO ${key.toUpperCase()} VALUE`;
-        const evContents = heading ? heading : this._props?.type === 'number' ? '0' : noValueHeader;
+        const evContents = heading || (this._props?.type === 'number' ? '0' : noValueHeader);
         const headingView = this._props.headingObject ? (
             <div
                 key={heading}
@@ -324,14 +333,19 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                     onPointerDown={this.headerDown}
                     title={evContents === noValueHeader ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
                     style={{ background: evContents !== noValueHeader ? this._color : 'inherit' }}>
-                    <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
+                    <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine />
                     <div className="collectionStackingView-sectionColor" style={{ display: evContents === noValueHeader ? 'none' : undefined }}>
-                        <button className="collectionStackingView-sectionColorButton" onClick={action(e => (this._paletteOn = !this._paletteOn))}>
+                        <button
+                            type="button"
+                            className="collectionStackingView-sectionColorButton"
+                            onClick={action(() => {
+                                this._paletteOn = !this._paletteOn;
+                            })}>
                             <FontAwesomeIcon icon="palette" size="lg" />
                         </button>
                         {this._paletteOn ? this.renderColorPicker() : null}
                     </div>
-                    <button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
+                    <button type="button" className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
                         <FontAwesomeIcon icon="trash" size="lg" />
                     </button>
                     {/* {evContents === noValueHeader ? null : (
@@ -352,7 +366,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
             </div>
         ) : null;
         const templatecols = `${this._props.columnWidth / this._props.numGroupColumns}px `;
-        const type = this._props.Document.type;
+        const { type } = this._props.Document;
         return (
             <>
                 {this._props.Document._columnsHideIfEmpty ? null : headingView}
@@ -364,7 +378,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                             style={{
                                 padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
                                 margin: 'auto',
-                                width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
+                                width: 'max-content', // singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
                                 height: 'max-content',
                                 position: 'relative',
                                 gridGap: this._props.gridGap,
@@ -376,10 +390,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                         {!this._props.chromeHidden && type !== DocumentType.PRES ? (
                             // TODO: this is the "new" button: see what you can work with here
                             // change cursor to pointer for this, and update dragging cursor
-                            //TODO: there is a bug that occurs when adding a freeform document and trying to move it around
-                            //TODO: would be great if there was additional space beyond the frame, so that you can actually see your
+                            //  TODO: there is a bug that occurs when adding a freeform document and trying to move it around
+                            //  TODO: would be great if there was additional space beyond the frame, so that you can actually see your
                             // bottom note
-                            //TODO: ok, so we are using a single column, and this is it!
+                            // TODO: ok, so we are using a single column, and this is it!
                             <div
                                 key={`${heading}-add-document`}
                                 onKeyDown={e => e.stopPropagation()}
@@ -390,7 +404,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                                     SetValue={this.addNewTextDoc}
                                     textCallback={this.typedNote}
                                     placeholder={"Type ':' for commands"}
-                                    contents={<FontAwesomeIcon icon={'plus'} />}
+                                    contents={<FontAwesomeIcon icon="plus" />}
                                     menuCallback={this.menuCallback}
                                 />
                             </div>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 32198e3a2..cd401058f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,10 +1,10 @@
 import { action, computed, makeObservable, observable } from 'mobx';
 import * as React from 'react';
 import * as rp from 'request-promise';
-import { Utils, returnFalse } from '../../../Utils';
+import { ClientUtils, returnFalse } from '../../../ClientUtils';
 import CursorField from '../../../fields/CursorField';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, DocData } from '../../../fields/DocSymbols';
+import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
+import { AclPrivate } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
@@ -15,16 +15,47 @@ import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { DocServer } from '../../DocServer';
 import { Networking } from '../../Network';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { ViewBoxBaseComponent } from '../DocComponent';
 import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { UndoManager, undoBatch } from '../../util/UndoManager';
+import { ViewBoxBaseComponent } from '../DocComponent';
+import { FieldViewProps } from '../nodes/FieldView';
 import { LoadingBox } from '../nodes/LoadingBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { CollectionView, CollectionViewProps } from './CollectionView';
+
+export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> {
+    isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
+    isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
+    layoutEngine?: () => string;
+    setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => void;
+    ignoreUnrendered?: boolean;
+
+    // property overrides for child documents
+    childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
+    childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
+    childContentsActive?: () => boolean | undefined;
+    childLayoutFitWidth?: (child: Doc) => boolean;
+    childlayout_showTitle?: () => string;
+    childOpacity?: () => number;
+    childContextMenuItems?: () => { script: ScriptField; label: string }[];
+    childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
+    childHideDecorationTitle?: boolean;
+    childHideResizeHandles?: boolean;
+    childDragAction?: dropActionType;
+    childXPadding?: number;
+    childYPadding?: number;
+    childLayoutString?: string;
+    childIgnoreNativeSize?: boolean;
+    childClickScript?: ScriptField;
+    childDoubleClickScript?: ScriptField;
+    AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+    RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
+    hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
+}
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     isAnyChildContentActive: () => boolean;
@@ -53,7 +84,7 @@ export function CollectionSubView<X>(moreProps?: X) {
             }
         };
         protected CreateDropTarget(ele: HTMLDivElement) {
-            //used in schema view
+            // used in schema view
             this.createDashEventsTarget(ele);
         }
 
@@ -83,10 +114,11 @@ export function CollectionSubView<X>(moreProps?: X) {
             const { Document, TemplateDataDocument } = this._props;
             const validPairs = this.childDocs
                 .map(doc => Doc.GetLayoutDataDocPair(Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
-                .filter(pair => {
-                    // filter out any documents that have a proto that we don't have permissions to
-                    return !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));
-                });
+                .filter(
+                    pair =>
+                        // filter out any documents that have a proto that we don't have permissions to
+                        !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate))
+                );
             return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
         }
         @computed get childDocList() {
@@ -95,9 +127,9 @@ export function CollectionSubView<X>(moreProps?: X) {
         collectionFilters = () => this._focusFilters ?? StrListCast(this.Document._childFilters);
         collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.Document._childFiltersByRanges, listSpec('string'), []);
         // child filters apply to the descendants of the documents in this collection
-        childDocFilters = () => [...(this._props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
+        childDocFilters = () => [...(this._props.childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
         // unrecursive filters apply to the documents in the collection, but no their children.  See Utils.noRecursionHack
-        unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
+        unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !ClientUtils.IsRecursiveFilter(f)) || [])];
         childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
         searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this.Document._searchFilterDocs);
         @computed.struct get childDocs() {
@@ -129,13 +161,13 @@ export function CollectionSubView<X>(moreProps?: X) {
             const docsforFilter: Doc[] = [];
             childDocs.forEach(d => {
                 // dragging facets
-                const dragged = this._props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter));
-                if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return false;
+                const dragged = this._props.childFilters?.().some(f => f.includes(ClientUtils.noDragDocsFilter));
+                if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return;
                 let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0;
                 if (notFiltered) {
                     notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0;
                     const fieldKey = Doc.LayoutFieldKey(d);
-                    const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name);
+                    const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes(CollectionView.name);
                     const data = d[annos ? fieldKey + '_annotations' : fieldKey];
                     const side = annos && d[fieldKey + '_sidebar'];
                     if (data !== undefined || side !== undefined) {
@@ -147,7 +179,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                                 newarray = [];
                                 subDocs.forEach(t => {
                                     const fieldKey = Doc.LayoutFieldKey(t);
-                                    const annos = !Field.toString(Doc.LayoutField(t) as Field).includes(CollectionView.name);
+                                    const annos = !Field.toString(Doc.LayoutField(t) as FieldType).includes(CollectionView.name);
                                     notFiltered =
                                         notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length));
                                     DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
@@ -168,7 +200,7 @@ export function CollectionSubView<X>(moreProps?: X) {
             let ind;
             const doc = this.Document;
             const id = Doc.UserDoc()[Id];
-            const email = Doc.CurrentUserEmail;
+            const email = ClientUtils.CurrentUserEmail;
             const pos = { x: position[0], y: position[1] };
             if (id && email) {
                 const proto = Doc.GetProto(doc);
@@ -184,6 +216,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                 if (!cursors) {
                     proto.cursors = cursors = new List<CursorField>();
                 }
+                // eslint-disable-next-line no-cond-assign
                 if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.data.metadata.id === id)) > -1) {
                     cursors[ind].setPosition(pos);
                 } else {
@@ -215,9 +248,9 @@ export function CollectionSubView<X>(moreProps?: X) {
         moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this._props.moveDocument?.(doc, targetCollection, addDocument) || false;
 
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
-            const docDragData = de.complete.docDragData;
+            const { docDragData } = de.complete;
             if (docDragData) {
-                let added = undefined;
+                let added;
                 const dropAction = docDragData.dropAction || docDragData.userDropAction;
                 const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]);
                 const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
@@ -245,8 +278,9 @@ export function CollectionSubView<X>(moreProps?: X) {
                 }
                 added === false && !this._props.isAnnotationOverlay && e.preventDefault();
                 added === true && e.stopPropagation();
-                return added ? true : false;
-            } else if (de.complete.annoDragData) {
+                return !!added;
+            }
+            if (de.complete.annoDragData) {
                 const dropCreator = de.complete.annoDragData.dropDocCreator;
                 de.complete.annoDragData.dropDocCreator = () => {
                     const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined);
@@ -316,7 +350,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                             const result = (await Networking.PostToServer('/uploadRemoteImage', { sources: [imgSrc] })).lastElement();
                             const newImgSrc =
                                 result.accessPaths.agnostic.client.indexOf('dashblobstore') === -1 //
-                                    ? Utils.prepend(result.accessPaths.agnostic.client)
+                                    ? ClientUtils.prepend(result.accessPaths.agnostic.client)
                                     : result.accessPaths.agnostic.client;
 
                             addDocument(ImageUtils.AssignImgInfo(Docs.Create.ImageDocument(newImgSrc, imgOpts), result));
@@ -325,39 +359,39 @@ export function CollectionSubView<X>(moreProps?: X) {
                             addDocument(ImageUtils.AssignImgInfo(doc, await ImageUtils.ExtractImgInfo(doc)));
                         }
                         return;
+                    }
+                    const path = window.location.origin + '/doc/';
+                    if (text.startsWith(path)) {
+                        const docId = text.replace(Doc.globalServerPath(), '').split('?')[0];
+                        DocServer.GetRefField(docId).then(f => {
+                            const fDoc = f;
+                            if (fDoc instanceof Doc) {
+                                if (options.x || options.y) {
+                                    fDoc.x = options.x as number;
+                                    fDoc.y = options.y as number;
+                                } // should be in CollectionFreeFormView
+                                addDocument(fDoc);
+                            }
+                        });
                     } else {
-                        const path = window.location.origin + '/doc/';
-                        if (text.startsWith(path)) {
-                            const docId = text.replace(Doc.globalServerPath(), '').split('?')[0];
-                            DocServer.GetRefField(docId).then(f => {
-                                if (f instanceof Doc) {
-                                    if (options.x || options.y) {
-                                        f.x = options.x as number;
-                                        f.y = options.y as number;
-                                    } // should be in CollectionFreeFormView
-                                    f instanceof Doc && addDocument(f);
-                                }
-                            });
-                        } else {
-                            const srcWeb = SelectionManager.Views.lastElement();
-                            const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0];
-                            const reg = new RegExp(Utils.prepend(''), 'g');
-                            const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
-                            const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, '$1')).filter(t => t)?.[0];
-                            const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? 'from:' + srcUrl : '-web clip-', _width: 300, _height: 300, backgroundColor });
-                            Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text;
-                            addDocument(htmlDoc);
-                            if (srcWeb) {
-                                const iframe = SelectionManager.Views[0].ContentDiv?.getElementsByTagName('iframe')?.[0];
-                                const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any;
-                                if (focusNode) {
-                                    const anchor = srcWeb?.ComponentView?.getAnchor?.(true);
-                                    anchor && DocUtils.MakeLink(htmlDoc, anchor, {});
-                                }
+                        const srcWeb = SelectionManager.Views.lastElement();
+                        const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0];
+                        const reg = new RegExp(ClientUtils.prepend(''), 'g');
+                        const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
+                        const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, '$1')).filter(t => t)?.[0];
+                        const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? 'from:' + srcUrl : '-web clip-', _width: 300, _height: 300, backgroundColor });
+                        Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text;
+                        addDocument(htmlDoc);
+                        if (srcWeb) {
+                            const iframe = SelectionManager.Views[0].ContentDiv?.getElementsByTagName('iframe')?.[0];
+                            const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any;
+                            if (focusNode) {
+                                const anchor = srcWeb?.ComponentView?.getAnchor?.(true);
+                                anchor && DocUtils.MakeLink(htmlDoc, anchor, {});
                             }
                         }
-                        return;
                     }
+                    return;
                 }
             }
 
@@ -414,10 +448,15 @@ export function CollectionSubView<X>(moreProps?: X) {
             for (let i = 0; i < length; i++) {
                 const item = e.dataTransfer.items[i];
                 if (item.kind === 'string' && item.type.includes('uri')) {
-                    const stringContents = await new Promise<string>(resolve => item.getAsString(resolve));
-                    const type = (await rp.head(Utils.CorsProxy(stringContents)))['content-type'];
+                    // eslint-disable-next-line no-await-in-loop
+                    const stringContents = await new Promise<string>(resolve => {
+                        item.getAsString(resolve);
+                    });
+                    // eslint-disable-next-line no-await-in-loop
+                    const type = (await rp.head(ClientUtils.CorsProxy(stringContents)))['content-type'];
                     if (type) {
-                        const doc = await DocUtils.DocumentFromType(type, Utils.CorsProxy(stringContents), options);
+                        // eslint-disable-next-line no-await-in-loop
+                        const doc = await DocUtils.DocumentFromType(type, ClientUtils.CorsProxy(stringContents), options);
                         doc && generatedDocuments.push(doc);
                     }
                 }
@@ -426,7 +465,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                     file?.type && files.push(file);
 
                     file?.type === 'application/json' &&
-                        Utils.readUploadedFileAsText(file).then(result => {
+                        ClientUtils.readUploadedFileAsText(file).then(result => {
                             const json = JSON.parse(result as string);
                             addDocument(
                                 Docs.Create.TreeDocument(
@@ -450,7 +489,7 @@ export function CollectionSubView<X>(moreProps?: X) {
             // create placeholder docs
             // inside placeholder docs have some func that
 
-            let pileUpDoc = undefined;
+            let pileUpDoc;
             if (typeof files === 'string') {
                 const loading = Docs.Create.LoadingDocument(files, options);
                 generatedDocuments.push(loading);
@@ -478,20 +517,16 @@ export function CollectionSubView<X>(moreProps?: X) {
                         })
                       : [];
                 if (completed) completed(set);
-                else {
-                    if (isFreeformView && generatedDocuments.length > 1) {
-                        pileUpDoc = DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!;
-                        addDocument(pileUpDoc);
-                    } else {
-                        generatedDocuments.forEach(addDocument);
-                    }
-                }
-            } else {
-                if (text && !text.includes('https://')) {
-                    addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 }));
+                else if (isFreeformView && generatedDocuments.length > 1) {
+                    pileUpDoc = DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!;
+                    addDocument(pileUpDoc);
                 } else {
-                    alert('Document upload failed - possibly an unsupported file type.');
+                    generatedDocuments.forEach(addDocument);
                 }
+            } else if (text && !text.includes('https://')) {
+                addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 }));
+            } else {
+                alert('Document upload failed - possibly an unsupported file type.');
             }
         };
     }
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 37452ddfb..7f234ffe9 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,7 +1,8 @@
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils';
 import { Doc, Opt, StrListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -195,7 +196,7 @@ export class CollectionTimeView extends CollectionSubView() {
         let nonNumbers = 0;
         this.childDocs.map(doc => {
             const num = NumCast(doc[this.pivotField], Number(StrCast(doc[this.pivotField])));
-            if (Number.isNaN(num)) {
+            if (isNaN(num)) {
                 nonNumbers++;
             }
         });
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 5741fc29b..32473f20b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,6 +1,7 @@
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { DivHeight, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../ClientUtils';
 import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -8,10 +9,11 @@ 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 { DivHeight, emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero, Utils } from '../../../Utils';
+import { emptyFunction, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 18eb4dd1f..a0d84ab28 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,21 +1,20 @@
+/* eslint-disable react/jsx-props-no-spreading */
 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 { returnEmptyString } from '../../../ClientUtils';
+import { Doc, DocListCast } 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 { 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';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { OpenWhere } from '../nodes/DocumentView';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { FieldView } from '../nodes/FieldView';
 import { CollectionCalendarView } from './CollectionCalendarView';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
 import { CollectionCarouselView } from './CollectionCarouselView';
@@ -23,7 +22,7 @@ import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionNoteTakingView } from './CollectionNoteTakingView';
 import { CollectionPileView } from './CollectionPileView';
 import { CollectionStackingView } from './CollectionStackingView';
-import { SubCollectionViewProps } from './CollectionSubView';
+import { CollectionViewProps, SubCollectionViewProps } from './CollectionSubView';
 import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from './CollectionTreeView';
 import './CollectionView.scss';
@@ -33,36 +32,7 @@ import { CollectionLinearView } from './collectionLinear';
 import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
 import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
 import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
-export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> {
-    isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
-    isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
-    layoutEngine?: () => string;
-    setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => void;
-    ignoreUnrendered?: boolean;
 
-    // property overrides for child documents
-    childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
-    childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
-    childContentsActive?: () => boolean | undefined;
-    childLayoutFitWidth?: (child: Doc) => boolean;
-    childlayout_showTitle?: () => string;
-    childOpacity?: () => number;
-    childContextMenuItems?: () => { script: ScriptField; label: string }[];
-    childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
-    childHideDecorationTitle?: boolean;
-    childHideResizeHandles?: boolean;
-    childDragAction?: dropActionType;
-    childXPadding?: number;
-    childYPadding?: number;
-    childLayoutString?: string;
-    childIgnoreNativeSize?: boolean;
-    childClickScript?: ScriptField;
-    childDoubleClickScript?: ScriptField;
-    //TODO: [AL] add these fields
-    AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
-    RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
-    hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
-}
 @observer
 export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldStr: string) {
@@ -89,7 +59,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
         // this a reaction, downstream invalidations only occur when the reaction value actually changes.
         this.reactionDisposer = reaction(
             () => (this.isAnyChildContentActive() ? true : this._props.isContentActive()),
-            active => (this._isContentActive = active),
+            active => {
+                this._isContentActive = active;
+            },
             { fireImmediately: true }
         );
     }
@@ -100,13 +72,12 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
     get collectionViewType(): CollectionViewType | undefined {
         const viewField = StrCast(this.layoutDoc._type_collection);
         if (CollectionView._safeMode) {
-            switch (viewField) {
+            switch (viewField) { 
                 case CollectionViewType.Freeform:
-                case CollectionViewType.Schema:
-                    return CollectionViewType.Tree;
-                case CollectionViewType.Invalid:
-                    return CollectionViewType.Freeform;
-            }
+                case CollectionViewType.Schema:  return CollectionViewType.Tree;
+                case CollectionViewType.Invalid:  return CollectionViewType.Freeform;
+                default:
+            } // prettier-ignore
         }
         return viewField as any as CollectionViewType;
     }
@@ -117,8 +88,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
         TraceMobx();
         if (type === undefined) return null;
         switch (type) {
-            default:
-            case CollectionViewType.Freeform:     return <CollectionFreeFormView    key="collview" {...props} />;
             case CollectionViewType.Schema:       return <CollectionSchemaView      key="collview" {...props} />;
             case CollectionViewType.Calendar:     return <CollectionCalendarView    key="collview" {...props} />;
             case CollectionViewType.Docking:      return <CollectionDockingView     key="collview" {...props} />;
@@ -134,6 +103,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
             case CollectionViewType.Masonry:      return <CollectionStackingView    key="collview" {...props} />;
             case CollectionViewType.Time:         return <CollectionTimeView        key="collview" {...props} />;
             case CollectionViewType.Grid:         return <CollectionGridView        key="collview" {...props} />;
+            case CollectionViewType.Freeform:     
+            default:                              return <CollectionFreeFormView    key="collview" {...props} />;
         }
     };
 
@@ -144,9 +115,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
                 { description: 'Freeform',    event: () => func(CollectionViewType.Freeform), icon: 'signature' },
                 { description: 'Schema',      event: () => func(CollectionViewType.Schema), icon: 'th-list' },
                 { description: 'Tree',        event: () => func(CollectionViewType.Tree), icon: 'tree' },
-                { description: 'Stacking',    event: () => (func(CollectionViewType.Stacking)._layout_autoHeight = true), icon: 'ellipsis-v' },
+                { description: 'Stacking',    event: () => {func(CollectionViewType.Stacking)._layout_autoHeight = true}, icon: 'ellipsis-v' },
                 { description: 'Calendar',    event: () => func(CollectionViewType.Calendar), icon: 'columns'},
-                { description: 'Notetaking',  event: () => (func(CollectionViewType.NoteTaking)._layout_autoHeight = true), icon: 'ellipsis-v' },
+                { description: 'Notetaking',  event: () => {func(CollectionViewType.NoteTaking)._layout_autoHeight = true}, icon: 'ellipsis-v' },
                 { description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' },
                 { description: 'Multirow',    event: () => func(CollectionViewType.Multirow), icon: 'columns' },
                 { description: 'Masonry',     event: () => func(CollectionViewType.Masonry), icon: 'columns' },
@@ -178,14 +149,14 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
 
             const options = cm.findByDescription('Options...');
             const optionItems = options && 'subitems' in options ? options.subitems : [];
-            !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.Document.forceActive = !this.Document.forceActive), icon: 'project-diagram' }) : null;
+            !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => {this.Document.forceActive = !this.Document.forceActive}, icon: 'project-diagram' }) : null; // prettier-ignore
             if (this.Document.childLayout instanceof Doc) {
                 optionItems.push({ description: 'View Child Layout', event: () => this._props.addDocTab(this.Document.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' });
             }
             if (this.Document.childClickedOpenTemplateView instanceof Doc) {
                 optionItems.push({ description: 'View Child Detailed Layout', event: () => this._props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' });
             }
-            !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox), icon: 'project-diagram' });
+            !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => { this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox; }, icon: 'project-diagram' }); // prettier-ignore
 
             !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' });
 
@@ -200,7 +171,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
                     onClicks.push({
                         description: `Edit ${func.name} script`,
                         icon: 'edit',
-                        event: (obj: any) => {
+                        event: () => {
                             const embedding = Doc.MakeEmbedding(this.Document);
                             DocUtils.makeCustomViewClicked(embedding, undefined, func.key);
                             this._props.addDocTab(embedding, OpenWhere.addRight);
@@ -211,7 +182,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
                     onClicks.push({
                         description: `Set child ${childClick.title}`,
                         icon: 'edit',
-                        event: () => (this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))),
+                        event: () => {
+                            this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data));
+                        },
                     })
                 );
                 !Doc.IsSystem(this.Document) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
@@ -229,7 +202,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
     bodyPanelWidth = () => this._props.PanelWidth();
 
     childLayoutTemplate = () => this._props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null);
-    isContentActive = (outsideReaction?: boolean) => this._isContentActive;
+    isContentActive = () => this._isContentActive;
 
     pointerEvents = () =>
         this.layoutDoc._lockedPosition && //
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 5cb7b149b..8a2e83ed9 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -6,7 +6,8 @@ import { IReactionDisposer, ObservableSet, action, computed, makeObservable, obs
 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 { ClientUtils, DashColor, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -18,13 +19,14 @@ 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 { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { SelectionManager } from '../../util/SelectionManager';
-import { SettingsManager } from '../../util/SettingsManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { UndoManager, undoable } from '../../util/UndoManager';
 import { DashboardView } from '../DashboardView';
+import { PinProps } from '../DocComponent';
 import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
@@ -32,12 +34,12 @@ import { Colors } from '../global/globalEnums';
 import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView';
 import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
-import { DashFieldView } from '../nodes/formattedText/DashFieldView';
-import { PinProps, PresBox, PresMovement } from '../nodes/trails';
+import { PresBox, PresMovement } from '../nodes/trails';
 import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionView } from './CollectionView';
 import './TabDocView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface TabDocViewProps {
@@ -105,7 +107,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                 while (child?.children.length) {
                     const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
                     if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break;
-                    if (next?.className?.toString().includes(DashFieldView.name)) break;
                     if (next) child = next;
                     else break;
                 }
@@ -149,17 +150,17 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                 };
 
                 const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />;
-                const closeIcon = <FontAwesomeIcon icon={'eye'} />;
+                const closeIcon = <FontAwesomeIcon icon="eye" />;
                 ReactDOM.createRoot(iconWrap).render(docIcon);
                 ReactDOM.createRoot(closeWrap).render(closeIcon);
                 tab.reactComponents = [iconWrap, closeWrap];
                 tab.element[0].prepend(iconWrap);
                 tab._disposers.color = reaction(
-                    () => ({ variant: SettingsManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }),
+                    () => ({ variant: SnappingManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }),
                     ({ variant, degree, highlight }) => {
                         const color = highlight?.highlightIndex === Doc.DocBrushStatus.highlighted ? highlight.highlightColor : degree ? ['transparent', variant, variant, 'orange'][degree] : variant;
 
-                        const textColor = color === variant ? SettingsManager.userColor : lightOrDark(color);
+                        const textColor = color === variant ? SnappingManager.userColor : lightOrDark(color);
                         titleEle.style.color = textColor;
                         iconWrap.style.color = textColor;
                         closeWrap.style.color = textColor;
@@ -407,9 +408,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         return false;
     };
 
-    getCurrentFrame = () => {
-        return NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
-    };
+    getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
     static Activate = (tabDoc: Doc) => {
         const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(tab => tab.DashDoc === tabDoc && !tab.contentItem.config.props.keyValue);
         tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
@@ -426,7 +425,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     @observable _forceInvalidateScreenToLocal = 0;
     ScreenToLocalTransform = () => {
         this._forceInvalidateScreenToLocal;
-        const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
+        const { translateX, translateY } = ClientUtils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
         return CollectionDockingView.Instance?.ScreenToLocalBoxXf().translate(-translateX, -translateY) ?? Transform.Identity();
     };
     PanelWidth = () => this._panelWidth;
@@ -434,7 +433,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     miniMapColor = () => Colors.MEDIUM_GRAY;
     tabView = () => this._view;
     disableMinimap = () => !this._document;
-    whenChildContentActiveChanges = (isActive: boolean) => (this._isAnyChildContentActive = isActive);
+    whenChildContentActiveChanges = (isActive: boolean) => {
+        this._isAnyChildContentActive = isActive;
+    };
     isContentActive = () => this._isContentActive;
     waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined);
     @computed get docView() {
@@ -465,9 +466,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                     addDocument={undefined}
                     removeDocument={this.remDocTab}
                     addDocTab={this.addDocTab}
-                    suppressSetHeight={this._document._layout_fitWidth ? true : false}
+                    suppressSetHeight={!!this._document._layout_fitWidth}
                     ScreenToLocalTransform={this.ScreenToLocalTransform}
-                    dontCenter={'y'}
+                    dontCenter="y"
                     whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
                     focus={this.focusFunc}
                     containerViewPath={returnEmptyDoclist}
@@ -485,12 +486,13 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                 style={{
                     fontFamily: Doc.UserDoc().renderStyle === 'comic' ? 'Comic Sans MS' : undefined,
                 }}
-                onPointerOver={action(() => (this._hovering = true))}
-                onPointerLeave={action(() => (this._hovering = false))}
-                onDragOver={action(() => (this._hovering = true))}
-                onDragLeave={action(() => (this._hovering = false))}
+                onPointerOver={action(() =>  { this._hovering = true; })} // prettier-ignore
+                onPointerLeave={action(() => { this._hovering = false; })} // prettier-ignore
+                onDragOver={action(() =>     { this._hovering = true; })} // prettier-ignore
+                onDragLeave={action(() =>    { this._hovering = false; })} // prettier-ignore
                 ref={ref => {
-                    if ((this._mainCont = ref)) {
+                    this._mainCont = ref;
+                    if (this._mainCont) {
                         if (this._lastTab) {
                             this._view && DocumentManager.Instance.RemoveView(this._view);
                         }
@@ -524,7 +526,8 @@ interface TabMiniThumbProps {
 @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()}% ` }} />;
+        const { miniWidth, miniHeight, miniLeft, miniTop } = this.props;
+        return <div className="miniThumb" style={{ width: `${miniWidth()}%`, height: `${miniHeight()}%`, left: `${miniLeft()}%`, top: `${miniTop()}%` }} />;
     }
 }
 @observer
@@ -532,14 +535,10 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
     static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
         if (doc) {
             switch (property.split(':')[0]) {
-                default:
-                    return DefaultStyleProvider(doc, props, property);
-                case StyleProp.PointerEvents:
-                    return 'none';
-                case StyleProp.DocContents:
-                    const background = ((type: DocumentType) => {
-                        // prettier-ignore
-                        switch (type) {
+                case StyleProp.PointerEvents: return 'none';
+                case StyleProp.DocContents: {
+                    const background = (() => {
+                        switch (doc.type as DocumentType) {
                             case DocumentType.PDF:    return 'pink';
                             case DocumentType.AUDIO:  return 'lightgreen';
                             case DocumentType.WEB:    return 'brown';
@@ -549,10 +548,12 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
                             case DocumentType.RTF:    return 'yellow';
                             case DocumentType.COL:    return undefined;
                             default:                  return 'gray';
-                        }
-                    })(doc.type as DocumentType);
+                        } // prettier-ignore
+                    })();
                     return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />;
-            }
+                }
+                default: return DefaultStyleProvider(doc, props, property);
+            } // prettier-ignore
         }
     };
 
@@ -639,7 +640,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
     render() {
         return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : (
             <div className="miniMap-hidden">
-                <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
+                <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
             </div>
         );
     }
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 4fd49f8fe..e266ccd14 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,11 +1,15 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { IconButton, Size } from 'browndash-components';
 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, setupMoveUpEvents, simulateMouseClick } from '../../../Utils';
-import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
+import { ClientUtils, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
+import { Doc, DocListCast, Field, FieldType, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
@@ -17,7 +21,8 @@ import { TraceMobx } from '../../../fields/util';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocUtils, Docs } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { LinkManager } from '../../util/LinkManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -35,10 +40,12 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
 import { CollectionView } from './CollectionView';
 import { TreeSort } from './TreeSort';
 import './TreeView.scss';
+
 const { TREE_BULLET_WIDTH } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
 
 export interface TreeViewProps {
     treeView: CollectionTreeView;
+    // eslint-disable-next-line no-use-before-define
     parentTreeView: TreeView | CollectionTreeView | undefined;
     observeHeight: (ref: any) => void;
     unobserveHeight: (ref: any) => void;
@@ -87,6 +94,7 @@ const treeBulletWidth = function () {
  */
 @observer
 export class TreeView extends ObservableReactComponent<TreeViewProps> {
+    // eslint-disable-next-line no-use-before-define
     static _editTitleOnLoad: Opt<{ id: string; parent: TreeView | CollectionTreeView | undefined }>;
     static _openTitleScript: Opt<ScriptField | undefined>;
     static _openLevelScript: Opt<ScriptField | undefined>;
@@ -101,6 +109,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     get treeViewOpenIsTransient() {
         return this.treeView.Document.treeView_OpenIsTransient || Doc.IsDataProto(this.Document);
     }
+    @computed get treeViewOpen() {
+        return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
+    }
     set treeViewOpen(c: boolean) {
         if (this.treeViewOpenIsTransient) this._transientOpenState = c;
         else {
@@ -137,9 +148,6 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     @computed get Document() {
         return this._props.Document;
     }
-    @computed get treeViewOpen() {
-        return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
-    }
     @computed get treeViewExpandedView() {
         return this.validExpandViewTypes.includes(StrCast(this.Document.treeView_ExpandedView)) ? StrCast(this.Document.treeView_ExpandedView) : this.defaultExpandedView;
     }
@@ -221,7 +229,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             this.treeViewOpen = !this.treeViewOpen;
         } else {
             // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns,  (2) has no context field ... otherwise make a new embedding
-            const bestEmbedding = docView.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document);
+            const bestEmbedding = docView.Document.author === ClientUtils.CurrentUserEmail && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document);
             this._props.addDocTab(bestEmbedding, OpenWhere.lightbox);
         }
     };
@@ -230,7 +238,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     recurToggle = (childList: Doc[]) => {
         if (childList.length > 0) {
             childList.forEach(child => {
-                child.runProcess = !!!child.runProcess;
+                child.runProcess = !child.runProcess;
                 TreeView.ToggleChildrenRun.get(child)?.();
             });
         }
@@ -273,9 +281,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             this.recurToggle(this.childDocs);
         });
 
-        TreeView.GetRunningChildren.set(this.Document, () => {
-            return this.getRunningChildren(this.childDocs);
-        });
+        TreeView.GetRunningChildren.set(this.Document, () => this.getRunningChildren(this.childDocs));
     }
 
     _treeEle: any;
@@ -301,7 +307,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         super.componentDidUpdate(prevProps);
         this._disposers.opening = reaction(
             () => this.treeViewOpen,
-            open => !open && (this._renderCount = 20)
+            open => {
+                !open && (this._renderCount = 20);
+            }
         );
         this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex);
     }
@@ -324,7 +332,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             document.addEventListener('pointerup', this.onDragUp, true);
         }
     };
-    onPointerLeave = (e: React.PointerEvent): void => {
+    onPointerLeave = (): void => {
         Doc.UnBrushDoc(this.dataDoc);
         if (this._header.current?.className !== 'treeView-header-editing') {
             this._header.current!.className = 'treeView-header';
@@ -385,7 +393,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         return this.localAdd(folder);
     };
 
-    preTreeDrop = (e: Event, de: DragManager.DropEvent, docDropAction: dropActionType) => {
+    preTreeDrop = () => {
         // fall through and let the CollectionTreeView handle this since treeView items have no special properties of their own
     };
 
@@ -403,7 +411,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             e.stopPropagation();
             return true;
         }
-        const docDragData = de.complete.docDragData;
+        const { docDragData } = de.complete;
         if (docDragData && pt[0] < rect.left + rect.width) {
             if (docDragData.draggedDocuments[0] === this.Document) return true;
             const added = this.dropDocuments(
@@ -462,8 +470,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
 
     refTransform = (ref: HTMLDivElement | undefined | null) => {
         if (!ref) return this.ScreenToLocalTransform();
-        const { scale, translateX, translateY } = Utils.GetScreenTransform(ref);
-        const outerXf = Utils.GetScreenTransform(this.treeView.MainEle());
+        const { translateX, translateY } = ClientUtils.GetScreenTransform(ref);
+        const outerXf = ClientUtils.GetScreenTransform(this.treeView.MainEle());
         const offset = this.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
         return this.ScreenToLocalTransform().translate(offset[0], offset[1]);
     };
@@ -490,14 +498,19 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         const ids: { [key: string]: string } = {};
         const rows: JSX.Element[] = [];
         const doc = this.Document;
-        doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
+        doc &&
+            Object.keys(doc).forEach(key => {
+                !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key);
+            });
 
+        // eslint-disable-next-line no-restricted-syntax
         for (const key of Object.keys(ids).slice().sort()) {
+            // eslint-disable-next-line no-continue
             if (this._props.skipFields?.includes(key) || key === 'title' || key === 'treeView_Open') continue;
             const contents = doc[key];
             let contentElement: (JSX.Element | null)[] | JSX.Element = [];
 
-            let leftOffset = observable({ width: 0 });
+            const leftOffset = observable({ width: 0 });
             const expandedWidth = () => this._props.panelWidth() - leftOffset.width;
             if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) {
                 const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
@@ -549,7 +562,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 contentElement = (
                     <EditableView
                         key="editableView"
-                        contents={contents !== undefined ? Field.toString(contents as Field) : 'null'}
+                        contents={contents !== undefined ? Field.toString(contents as FieldType) : 'null'}
                         height={13}
                         fontSize={12}
                         GetValue={() => Field.toKeyValueString(doc, key)}
@@ -572,15 +585,15 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             );
         }
         rows.push(
-            <div style={{ display: 'flex', overflow: 'auto' }} key={'newKeyValue'}>
+            <div style={{ display: 'flex', overflow: 'auto' }} key="newKeyValue">
                 <EditableView
                     key="editableView"
-                    contents={'+key=value'}
+                    contents="+key=value"
                     height={13}
                     fontSize={12}
                     GetValue={returnEmptyString}
                     SetValue={input => {
-                        const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]+)/);
+                        const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@?+\-*/ 0-9()]+)/);
                         if (match) {
                             const key = match[1];
                             const assign = match[2];
@@ -620,7 +633,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering);
                     doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000;
                     docs.push(doc);
-                    docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => (d.zIndex = i));
+                    docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => {
+                        d.zIndex = i;
+                    });
                 }
                 const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
                 const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false);
@@ -630,8 +645,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             };
             const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
             const docs = expandKey === 'embeddings' ? this.childEmbeddings : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs;
-            let downX = 0,
-                downY = 0;
+            let downX = 0;
+            let downY = 0;
             if (docs?.length && this._renderCount < docs?.length) {
                 this._renderTimer && clearTimeout(this._renderTimer);
                 this._renderTimer = setTimeout(
@@ -667,7 +682,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                         style={{ cursor: 'inherit' }}
                         key={expandKey + 'more'}
                         title={`Sorted by : ${this.Document.treeView_SortCriterion}. click to cycle`}
-                        className="" //this.doc.treeView_HideTitle ? 'no-indent' : ''}
+                        className="" // this.doc.treeView_HideTitle ? 'no-indent' : ''}
                         onPointerDown={e => {
                             downX = e.clientX;
                             downY = e.clientY;
@@ -719,7 +734,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     </ul>
                 </div>
             );
-        } else if (this.treeViewExpandedView === 'fields') {
+        }
+        if (this.treeViewExpandedView === 'fields') {
             return (
                 <ul key={this.Document[Id] + this.Document.title} style={{ cursor: 'inherit' }}>
                     <div>{this.expandedField}</div>
@@ -891,14 +907,15 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     titleStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
 
-        const treeView = this.treeView;
+        const { treeView } = this;
         // prettier-ignore
         switch (property.split(':')[0]) {
             case StyleProp.Opacity:         return this.treeView.outlineMode ? undefined : 1;
             case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : undefined;//StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
             case StyleProp.Highlighting:    if (this.treeView.outlineMode) return undefined;
+                                            break;
             case StyleProp.BoxShadow:       return undefined;
-            case StyleProp.DocContents:
+            case StyleProp.DocContents: {
                 const highlightIndex = this.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                 return treeView.outlineMode ? null : (
@@ -917,6 +934,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                         {StrCast(doc?.title)}
                     </div>
                 );
+            }
+            default:
         }
         return treeView._props.styleProvider?.(doc, props, property);
     };
@@ -924,7 +943,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         if (property.startsWith(StyleProp.Decorations)) return null;
         return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
     };
-    onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+    onKeyDown = (e: React.KeyboardEvent) => {
         if (this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode) {
             switch (e.key) {
                 case 'Tab':
@@ -944,6 +963,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     e.stopPropagation?.();
                     e.preventDefault?.();
                     return UndoManager.RunInBatch(this.makeTextCollection, 'bullet');
+                default:
             }
         }
         return false;
@@ -959,22 +979,24 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         const view = this._editTitle ? (
             <EditableView
                 key="_editTitle"
-                oneLine={true}
-                display={'inline-block'}
+                oneLine
+                display="inline-block"
                 editing={this._editTitle}
-                background={'#7089bb'}
+                background="#7089bb"
                 contents={StrCast(this.Document.title)}
                 height={12}
-                sizeToContent={true}
+                sizeToContent
                 fontSize={12}
-                isEditingCallback={action(e => (this._editTitle = e))}
+                isEditingCallback={action(e => {
+                    this._editTitle = e;
+                })}
                 GetValue={() => StrCast(this.Document.title)}
                 OnTab={undoBatch((shift?: boolean) => {
                     if (!shift) this._props.indentDocument?.(true);
                     else this._props.outdentDocument?.(true);
                 })}
                 OnEmpty={undoBatch(() => this.treeView.outlineMode && this._props.removeDoc?.(this.Document))}
-                OnFillDown={val => this.treeView.fileSysMode && this.makeFolder()}
+                OnFillDown={() => this.treeView.fileSysMode && this.makeFolder()}
                 SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => {
                     Doc.SetInPlace(this.Document, 'title', value, false);
                     this.treeView.outlineMode && enterKey && this.makeTextCollection();
@@ -984,7 +1006,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             <DocumentView
                 key="title"
                 ref={action((r: any) => {
-                    this._docRef = r ? r : undefined;
+                    this._docRef = r || undefined;
                     if (this._docRef && TreeView._editTitleOnLoad?.id === this.Document[Id] && TreeView._editTitleOnLoad.parent === this._props.parentTreeView) {
                         this._docRef.select(false);
                         this.setEditTitle(this._docRef);
@@ -994,8 +1016,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 Document={this.Document}
                 layout_fitWidth={returnTrue}
                 scriptContext={this}
-                hideDecorations={true}
-                hideClickBehaviors={true}
+                hideDecorations
+                hideClickBehaviors
                 styleProvider={this.titleStyleProvider}
                 onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
                 containerViewPath={this.treeView.childContainerViewPath}
@@ -1015,7 +1037,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 PanelHeight={return18}
                 contextMenuItems={this.contextMenuItems}
                 renderDepth={1}
-                isContentActive={emptyFunction} //this._props.isContentActive}
+                isContentActive={emptyFunction} // this._props.isContentActive}
                 isDocumentActive={this._props.isContentActive}
                 focus={this.refocus}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
@@ -1045,99 +1067,101 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     }}>
                     {view}
                 </div>
-                <div className="treeView-rightButtons" ref={action((r: any) => r && (this.headerEleWidth = r.getBoundingClientRect().width))}>
+                <div
+                    className="treeView-rightButtons"
+                    ref={action((r: any) => {
+                        r && (this.headerEleWidth = r.getBoundingClientRect().width);
+                    })}>
                     {this.titleButtons}
                 </div>
             </>
         );
     }
 
-    renderBulletHeader = (contents: JSX.Element, editing: boolean) => {
-        return (
-            <>
+    renderBulletHeader = (contents: JSX.Element, editing: boolean) => (
+        <>
+            <div
+                className={`treeView-header` + (editing ? '-editing' : '')}
+                key="titleheader"
+                ref={this._header}
+                onClick={this.ignoreEvent}
+                onPointerDown={e => {
+                    this.treeView.isContentActive() &&
+                        setupMoveUpEvents(
+                            this,
+                            e,
+                            () => {
+                                (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, '' as any);
+                                return true;
+                            },
+                            returnFalse,
+                            emptyFunction
+                        );
+                }}
+                onPointerEnter={this.onPointerEnter}
+                onPointerLeave={this.onPointerLeave}>
                 <div
-                    className={`treeView-header` + (editing ? '-editing' : '')}
-                    key="titleheader"
-                    ref={this._header}
-                    onClick={this.ignoreEvent}
-                    onPointerDown={e => {
-                        this.treeView.isContentActive() &&
-                            setupMoveUpEvents(
-                                this,
-                                e,
-                                () => {
-                                    (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, '' as any);
-                                    return true;
-                                },
-                                returnFalse,
-                                emptyFunction
-                            );
+                    className="treeView-background"
+                    style={{
+                        background: SettingsManager.userColor,
                     }}
-                    onPointerEnter={this.onPointerEnter}
-                    onPointerLeave={this.onPointerLeave}>
-                    <div
-                        className="treeView-background"
-                        style={{
-                            background: SettingsManager.userColor,
-                        }}
-                    />
-                    {contents}
-                </div>
-                {this.renderBorder}
-            </>
-        );
-    };
-
-    fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined);
-    renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => {
-        return (
-            <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
-                <DocumentView
-                    key={this.Document[Id]}
-                    ref={action((r: DocumentView | null) => (this._dref = r))}
-                    Document={this.Document}
-                    layout_fitWidth={this.fitWidthFilter}
-                    PanelWidth={this.embeddedPanelWidth}
-                    PanelHeight={this.embeddedPanelHeight}
-                    LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
-                    LayoutTemplate={this.treeView._props.childLayoutTemplate}
-                    isContentActive={isActive}
-                    isDocumentActive={isActive}
-                    styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
-                    fitContentsToBox={returnTrue}
-                    hideTitle={asText}
-                    hideDecorations={true}
-                    hideClickBehaviors={true}
-                    hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
-                    dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
-                    ScreenToLocalTransform={this.docTransform}
-                    renderDepth={this._props.renderDepth + 1}
-                    onClickScript={this.onChildClick}
-                    onKey={this.onKeyDown}
-                    containerViewPath={this.treeView.childContainerViewPath}
-                    childFilters={returnEmptyFilter}
-                    childFiltersByRanges={returnEmptyFilter}
-                    searchFilterDocs={returnEmptyDoclist}
-                    focus={this.refocus}
-                    addDocument={this._props.addDocument}
-                    moveDocument={this.move}
-                    removeDocument={this._props.removeDoc}
-                    whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                    xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
-                    yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
-                    addDocTab={this._props.addDocTab}
-                    pinToPres={this.treeView._props.pinToPres}
-                    disableBrushing={this.treeView._props.disableBrushing}
-                    scriptContext={this}
                 />
+                {contents}
             </div>
-        );
-    };
+            {this.renderBorder}
+        </>
+    );
+
+    fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined);
+    renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => (
+        <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
+            <DocumentView
+                key={this.Document[Id]}
+                ref={action((r: DocumentView | null) => {
+                    this._dref = r;
+                })}
+                Document={this.Document}
+                layout_fitWidth={this.fitWidthFilter}
+                PanelWidth={this.embeddedPanelWidth}
+                PanelHeight={this.embeddedPanelHeight}
+                LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
+                LayoutTemplate={this.treeView._props.childLayoutTemplate}
+                isContentActive={isActive}
+                isDocumentActive={isActive}
+                styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
+                fitContentsToBox={returnTrue}
+                hideTitle={asText}
+                hideDecorations
+                hideClickBehaviors
+                hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
+                dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
+                ScreenToLocalTransform={this.docTransform}
+                renderDepth={this._props.renderDepth + 1}
+                onClickScript={this.onChildClick}
+                onKey={this.onKeyDown}
+                containerViewPath={this.treeView.childContainerViewPath}
+                childFilters={returnEmptyFilter}
+                childFiltersByRanges={returnEmptyFilter}
+                searchFilterDocs={returnEmptyDoclist}
+                focus={this.refocus}
+                addDocument={this._props.addDocument}
+                moveDocument={this.move}
+                removeDocument={this._props.removeDoc}
+                whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+                xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
+                yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
+                addDocTab={this._props.addDocTab}
+                pinToPres={this.treeView._props.pinToPres}
+                disableBrushing={this.treeView._props.disableBrushing}
+                scriptContext={this}
+            />
+        </div>
+    );
 
     // renders the text version of a document as the header.  This is used in the file system mode and in other vanilla tree views.
     @computed get renderTitleAsHeader() {
         return this.treeView.Document.treeView_HideUnrendered && this.Document.layout_unrendered && !this.Document.treeView_FieldKey ? (
-            <div></div>
+            <div />
         ) : (
             <>
                 {this.renderBullet}
@@ -1147,14 +1171,12 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     }
 
     // renders the document in the header field instead of a text proxy.
-    renderDocumentAsHeader = (asText: boolean) => {
-        return (
-            <>
-                {this.renderBullet}
-                {this.renderEmbeddedDocument(asText, this._props.isContentActive)}
-            </>
-        );
-    };
+    renderDocumentAsHeader = (asText: boolean) => (
+        <>
+            {this.renderBullet}
+            {this.renderEmbeddedDocument(asText, this._props.isContentActive)}
+        </>
+    );
 
     @computed get renderBorder() {
         const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded);
@@ -1185,7 +1207,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 className={`treeView-container${this._props.isContentActive() ? '-active' : ''}`}
                 ref={this.createTreeDropTarget}
                 onDrop={this.onTreeDrop}
-                //onPointerDown={e => this._props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
+                // onPointerDown={e => this._props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
                 // onKeyDown={this.onKeyDown}
             >
                 <li className="collection-child">
@@ -1209,11 +1231,10 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     const aN = parseInt(a.match(reN)![0], 10);
                     const bN = parseInt(b.match(reN)![0], 10);
                     return aN === bN ? 0 : aN > bN ? 1 : -1;
-                } else {
-                    return aA > bA ? 1 : -1;
                 }
+                return aA > bA ? 1 : -1;
             };
-            docs.sort(function (d1, d2): 0 | 1 | -1 {
+            docs.sort((d1, d2): 0 | 1 | -1 => {
                 const a = criterion === TreeSort.AlphaUp ? d2 : d1;
                 const b = criterion === TreeSort.AlphaUp ? d1 : d2;
                 const first = a[criterion === TreeSort.Zindex ? 'zIndex' : 'title'];
@@ -1230,7 +1251,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         childDocs: Doc[],
         treeView: CollectionTreeView,
         parentTreeView: CollectionTreeView | TreeView | undefined,
-        treeView_Parent: Doc,
+        treeViewParent: Doc,
         dataDoc: Doc | undefined,
         parentCollectionDoc: Doc | undefined,
         containerPrevSibling: Doc | undefined,
@@ -1244,7 +1265,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         isContentActive: (outsideReaction?: boolean) => boolean,
         panelWidth: () => number,
         renderDepth: number,
-        treeView_HideHeaderFields: () => boolean,
+        treeViewHideHeaderFields: () => boolean,
         renderedIds: string[],
         onCheckedClick: undefined | (() => ScriptField),
         onChildClick: undefined | (() => ScriptField),
@@ -1261,19 +1282,19 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         hierarchyIndex?: number[],
         renderCount?: number
     ) {
-        const viewSpecScript = Cast(treeView_Parent.viewSpecScript, ScriptField);
+        const viewSpecScript = Cast(treeViewParent.viewSpecScript, ScriptField);
         if (viewSpecScript) {
             childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
         }
 
-        const docs = TreeView.sortDocs(childDocs, StrCast(treeView_Parent.treeView_SortCriterion, TreeSort.WhenAdded));
+        const docs = TreeView.sortDocs(childDocs, StrCast(treeViewParent.treeView_SortCriterion, TreeSort.WhenAdded));
         const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView._props.NativeDimScaling?.() || 1);
-        const treeView_Refs = new Map<Doc, TreeView | undefined>();
+        const treeViewRefs = new Map<Doc, TreeView | undefined>();
         return docs
             .filter(child => child instanceof Doc)
             .map((child, i) => {
                 if (renderCount && i > renderCount) return null;
-                const pair = Doc.GetLayoutDataDocPair(treeView_Parent, dataDoc, child);
+                const pair = Doc.GetLayoutDataDocPair(treeViewParent, dataDoc, child);
                 if (!pair.layout || pair.data instanceof Promise) {
                     return null;
                 }
@@ -1290,7 +1311,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                         Doc.SetContainer(child, treeView.Document);
                     }
                 };
-                const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeView_Refs.get(docs[i - 1]));
+                const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
                 const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView._props.parentTreeView : undefined);
                 const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
                 const childLayout = Doc.Layout(pair.layout);
@@ -1301,10 +1322,10 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 return (
                     <TreeView
                         key={child[Id]}
-                        ref={r => treeView_Refs.set(child, r ? r : undefined)}
+                        ref={r => treeViewRefs.set(child, r || undefined)}
                         Document={pair.layout}
                         dataDoc={pair.data}
-                        treeViewParent={treeView_Parent}
+                        treeViewParent={treeViewParent}
                         prevSibling={docs[i]}
                         // TODO: [AL] add these
                         hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined}
@@ -1316,7 +1337,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                         onCheckedClick={onCheckedClick}
                         onChildClick={onChildClick}
                         renderDepth={renderDepth}
-                        removeDoc={StrCast(treeView_Parent.treeView_FreezeChildren).includes('remove') ? undefined : remove}
+                        removeDoc={StrCast(treeViewParent.treeView_FreezeChildren).includes('remove') ? undefined : remove}
                         addDocument={addDocument}
                         styleProvider={styleProvider}
                         panelWidth={rowWidth}
@@ -1327,7 +1348,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                         addDocTab={addDocTab}
                         ScreenToLocalTransform={screenToLocalXf}
                         isContentActive={isContentActive}
-                        treeViewHideHeaderFields={treeView_HideHeaderFields}
+                        treeViewHideHeaderFields={treeViewHideHeaderFields}
                         renderedIds={renderedIds}
                         skipFields={skipFields}
                         firstLevel={firstLevel}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index cc729decc..29d835b28 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -1,7 +1,7 @@
 import { makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, Field, FieldResult } from '../../../../fields/Doc';
+import { Doc, DocListCast, FieldType, FieldResult } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
 import { StrCast } from '../../../../fields/Types';
 import { DocumentManager } from '../../../util/DocumentManager';
@@ -10,13 +10,14 @@ import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
 import { TopBar } from '../../topbar/TopBar';
 import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState';
-import { CollectionFreeFormView } from './CollectionFreeFormView';
 import './CollectionFreeFormView.scss';
+import { DocData } from '../../../../fields/DocSymbols';
 
 export interface CollectionFreeFormInfoUIProps {
     Document: Doc;
-    Freeform: CollectionFreeFormView;
-    close: () => boolean;
+    LayoutDoc: Doc;
+    childDocs: () => Doc[];
+    close: () => void;
 }
 
 @observer
@@ -32,10 +33,10 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
 
     @observable _currState: infoState | undefined = undefined;
     get currState() { return this._currState; } // prettier-ignore
-    set currState(val) { runInAction(() => (this._currState = val)); } // prettier-ignore
+    set currState(val) { runInAction(() => {this._currState = val;}); } // prettier-ignore
 
     componentWillUnmount(): void {
-        this._props.Freeform.dataDoc.backgroundColor = this._originalbackground;
+        this._props.Document[DocData].backgroundColor = this._originalbackground;
     }
 
     setCurrState = (state: infoState) => {
@@ -46,16 +47,16 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
     };
 
     setupStates = () => {
-        this._originalbackground = StrCast(this._props.Freeform.dataDoc.backgroundColor);
+        this._originalbackground = StrCast(this._props.Document[DocData].backgroundColor);
         //  state entry functions
-        const setBackground = (colour: string) => () => (this._props.Freeform.dataDoc.backgroundColor = colour);
-        const setOpacity = (opacity: number) => () => (this._props.Freeform.layoutDoc.opacity = opacity);
+        const setBackground = (colour: string) => () => {this._props.Document[DocData].backgroundColor = colour;} // prettier-ignore
+        const setOpacity = (opacity: number) => () => {this._props.LayoutDoc.opacity = opacity;} // prettier-ignore
         //  arc transition trigger conditions
-        const firstDoc = () => (this._props.Freeform.childDocs.length ? this._props.Freeform.childDocs[0] : undefined);
-        const numDocs = () => this._props.Freeform.childDocs.length;
+        const firstDoc = () => (this._props.childDocs().length ? this._props.childDocs()[0] : undefined);
+        const numDocs = () => this._props.childDocs().length;
 
-        let docX: FieldResult<Field>;
-        let docY: FieldResult<Field>;
+        let docX: FieldResult<FieldType>;
+        let docY: FieldResult<FieldType>;
 
         const docNewX = () => firstDoc()?.x;
         const docNewY = () => firstDoc()?.y;
@@ -244,12 +245,12 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
             // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
             manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
             docRemoved: [() => numDocs() < 3, () => viewedLink],
-            docCreated: [() => numDocs() == 4, () => completed],
+            docCreated: [() => numDocs() === 4, () => completed],
         });
 
         const completed = InfoState(
             'Eager to learn more? Click the ? icon in the top right corner to read our full documentation.',
-            { docRemoved: [() => numDocs() == 1, () => oneDoc] },
+            { docRemoved: [() => numDocs() === 1, () => oneDoc] },
             'documentation.png',
             () => TopBar.Instance.FlipDocumentationIcon()
         ); // prettier-ignore
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index c83c26509..a0b96c75a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,4 +1,4 @@
-import { Doc, Field, FieldResult } from '../../../../fields/Doc';
+import { Doc, Field, FieldType, FieldResult } from '../../../../fields/Doc';
 import { Id, ToString } from '../../../../fields/FieldSymbols';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { RefField } from '../../../../fields/RefField';
@@ -50,7 +50,7 @@ export interface ViewDefResult {
     bounds?: ViewDefBounds;
     inkMask?: number; //sort elements into either the mask layer (which has a mixedBlendMode appropriate for transparent masks), or the regular documents layer;   -1 = no mask, 0 = mask layer but stroke is transparent (hidden, as in during a presentation when you want to smoothly animate it into being a mask), >0 = mask layer and not hidden
 }
-function toLabel(target: FieldResult<Field>) {
+function toLabel(target: FieldResult<FieldType>) {
     if (typeof target === 'number' || Number(target)) {
         const truncated = Number(Number(target).toFixed(0));
         const precise = Number(Number(target).toFixed(2));
@@ -128,7 +128,7 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
 export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
     const docMap = new Map<string, PoolData>();
     const fieldKey = 'data';
-    const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
+    const pivotColumnGroups = new Map<FieldResult<FieldType>, PivotColumn>();
 
     let nonNumbers = 0;
     const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author';
@@ -136,10 +136,10 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
         const listValue = Cast(pair.layout[pivotFieldKey], listSpec('string'), null);
 
         const num = toNumber(pair.layout[pivotFieldKey]);
-        if (num === undefined || Number.isNaN(num)) {
+        if (num === undefined || isNaN(num)) {
             nonNumbers++;
         }
-        const val = Field.toString(pair.layout[pivotFieldKey] as Field);
+        const val = Field.toString(pair.layout[pivotFieldKey] as FieldType);
         if (listValue) {
             listValue.forEach((val, i) => {
                 !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
@@ -265,7 +265,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
     return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
 }
 
-function toNumber(val: FieldResult<Field>) {
+function toNumber(val: FieldResult<FieldType>) {
     return val === undefined ? undefined : NumCast(val, Number(StrCast(val)));
 }
 
@@ -274,7 +274,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc:
     const pivotDateGroups = new Map<number, Doc[]>();
     const docMap = new Map<string, PoolData>();
     const groupNames: ViewDefBounds[] = [];
-    const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field);
+    const timelineFieldKey = Field.toString(pivotDoc._pivotField as FieldType);
     const curTime = toNumber(pivotDoc[fieldKey + '-timelineCur']);
     const curTimeSpan = Cast(pivotDoc[fieldKey + '-timelineSpan'], 'number', null);
     const minTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + '-timelineMinReq'], 'number', null) : curTime && curTime - curTimeSpan;
@@ -290,7 +290,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc:
     let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq;
     childPairs.forEach(pair => {
         const num = NumCast(pair.layout[timelineFieldKey], Number(StrCast(pair.layout[timelineFieldKey])));
-        if (!Number.isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) {
+        if (!isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) {
             !pivotDateGroups.get(num) && pivotDateGroups.set(num, []);
             pivotDateGroups.get(num)!.push(pair.layout);
             minTime = Math.min(num, minTime);
@@ -400,7 +400,7 @@ function normalizeResults(
     const height = aggBounds.b - aggBounds.y === 0 ? 1 : aggBounds.b - aggBounds.y;
     const wscale = panelDim[0] / width;
     let scale = wscale * height > panelDim[1] ? panelDim[1] / height : wscale;
-    if (Number.isNaN(scale)) scale = 1;
+    if (isNaN(scale)) scale = 1;
 
     Array.from(docMap.entries())
         .filter(ele => ele[1].pair)
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index 69cbae86f..707f6c198 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -4,26 +4,28 @@ import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { ScriptField } from '../../../../fields/ScriptField';
 import { PresBox } from '../../nodes/trails/PresBox';
-import { CollectionFreeFormView } from './CollectionFreeFormView';
 import './CollectionFreeFormView.scss';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+
 export interface CollectionFreeFormPannableContentsProps {
     Document: Doc;
     viewDefDivClick?: ScriptField;
     children?: React.ReactNode | undefined;
     transition?: string;
     isAnnotationOverlay: boolean | undefined;
+    showPresPaths: () => boolean;
     transform: () => string;
     brushedView: () => { panX: number; panY: number; width: number; height: number } | undefined;
 }
 
 @observer
-export class CollectionFreeFormPannableContents extends React.Component<CollectionFreeFormPannableContentsProps> {
+export class CollectionFreeFormPannableContents extends ObservableReactComponent<CollectionFreeFormPannableContentsProps> {
     constructor(props: CollectionFreeFormPannableContentsProps) {
         super(props);
         makeObservable(this);
     }
     @computed get presPaths() {
-        return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.Document) : null;
+        return this._props.showPresPaths() ? PresBox.Instance.pathLines(this._props.Document) : null;
     }
     // rectangle highlight used when following trail/link to a region of a collection that isn't a document
     showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) =>
@@ -42,7 +44,7 @@ export class CollectionFreeFormPannableContents extends React.Component<Collecti
     render() {
         return (
             <div
-                className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')}
+                className={'collectionfreeformview' + (this._props.viewDefDivClick ? '-viewDef' : '-none')}
                 onScroll={e => {
                     const target = e.target as any;
                     if (getComputedStyle(target)?.overflow === 'visible') {
@@ -50,13 +52,13 @@ export class CollectionFreeFormPannableContents extends React.Component<Collecti
                     }
                 }}
                 style={{
-                    transform: this.props.transform(),
-                    transition: this.props.transition,
-                    width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable.  This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
+                    transform: this._props.transform(),
+                    transition: this._props.transition,
+                    width: this._props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable.  This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
                 }}>
                 {this.props.children}
                 {this.presPaths}
-                {this.showViewport(this.props.brushedView())}
+                {this.showViewport(this._props.brushedView())}
             </div>
         );
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index fa8218bdd..35394016d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -9,17 +9,17 @@ import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { Cast } from '../../../../fields/Types';
-import { CollectionViewProps } from '../CollectionView';
+import { CollectionViewProps } from '../CollectionSubView';
 import './CollectionFreeFormView.scss';
 
 @observer
 export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
     @computed protected get cursors(): CursorField[] {
-        const doc = this.props.Document;
+        const { Document } = this.props;
 
         let cursors: FieldResult<List<CursorField>>;
         const id = Doc.UserDoc()[Id];
-        if (!id || !(cursors = Cast(doc.cursors, listSpec(CursorField)))) {
+        if (!id || !(cursors = Cast(Document.cursors, listSpec(CursorField)))) {
             return [];
         }
         const now = mobxUtils.now();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3fab00968..a70713429 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,14 +1,17 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { Bezier } from 'bezier-js';
 import { Colors } from 'browndash-components';
 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';
+import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
 import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, Opt } from '../../../../fields/Doc';
 import { DocData, Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
-import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
+import { InkData, InkField, InkTool, Segment } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { listSpec } from '../../../../fields/Schema';
@@ -16,13 +19,15 @@ import { ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
+import { Gestures, PointData } from '../../../../pen-gestures/GestureTypes';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, emptyFunction, intersectRect, Utils } from '../../../../Utils';
 import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
 import { ReplayMovements } from '../../../util/ReplayMovements';
 import { CompileScript } from '../../../util/Scripting';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
@@ -33,8 +38,8 @@ import { Transform } from '../../../util/Transform';
 import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
 import { Timeline } from '../../animationtimeline/Timeline';
 import { ContextMenu } from '../../ContextMenu';
+import { PinProps } from '../../DocComponent';
 import { GestureOverlay } from '../../GestureOverlay';
-import { CtrlKey } from '../../GlobalKeyHandler';
 import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
@@ -42,7 +47,7 @@ import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
 import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
 import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
-import { PinProps, PresBox } from '../../nodes/trails/PresBox';
+import { PresBox } from '../../nodes/trails/PresBox';
 import { CreateImage } from '../../nodes/WebBoxRenderer';
 import { StyleProp } from '../../StyleProvider';
 import { CollectionSubView } from '../CollectionSubView';
@@ -77,7 +82,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
     @computed get paintFunc() {
         const field = this.dataDoc[this.fieldKey];
-        const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim();
+        const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as FieldType)).trim();
         return !paintFunc
             ? ''
             : paintFunc.includes('dashDiv')
@@ -635,11 +640,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
         switch (ge.gesture) {
             default:
-            case GestureUtils.Gestures.Line:
-            case GestureUtils.Gestures.Circle:
-            case GestureUtils.Gestures.Rectangle:
-            case GestureUtils.Gestures.Triangle:
-            case GestureUtils.Gestures.Stroke:
+            case Gestures.Line:
+            case Gestures.Circle:
+            case Gestures.Rectangle:
+            case Gestures.Triangle:
+            case Gestures.Stroke:
                 const points = ge.points;
                 const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
                 const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale;
@@ -660,7 +665,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 this.addDocument(inkDoc);
                 e.stopPropagation();
                 break;
-            case GestureUtils.Gestures.Rectangle:
+            case Gestures.Rectangle:
                 const strokes = this.getActiveDocuments()
                     .filter(doc => doc.type === DocumentType.INK)
                     .map(i => {
@@ -672,7 +677,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
                 CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {});
                 break;
-            case GestureUtils.Gestures.Text:
+            case Gestures.Text:
                 if (ge.text) {
                     const B = this.screenToFreeformContentsXf.transformPoint(ge.points[0].X, ge.points[0].Y);
                     this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
@@ -690,7 +695,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @action
     onClick = (e: React.MouseEvent) => {
         if (this._lightboxDoc) this._lightboxDoc = undefined;
-        if (Utils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) {
+        if (ClientUtils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) {
             if (this.onBrowseClickHandler()) {
                 this.onBrowseClickHandler().script.run({ documentView: this.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
                 e.stopPropagation();
@@ -746,7 +751,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     segments.forEach(segment =>
                         this.forceStrokeGesture(
                             e,
-                            GestureUtils.Gestures.Stroke,
+                            Gestures.Stroke,
                             segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
                         )
                     );
@@ -759,7 +764,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         });
         return false;
     };
-    forceStrokeGesture = (e: PointerEvent, gesture: GestureUtils.Gestures, points: InkData, text?: any) => {
+    forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: any) => {
         this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text));
     };
 
@@ -956,7 +961,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         switch (
             !e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?//
                 Doc.UserDoc().freeformScrollMode : // no modifiers, do assigned mode
-                    e.ctrlKey && !CtrlKey? // otherwise, if ctrl key (pinch gesture) try to zoom else pan
+                    e.ctrlKey && !SnappingManager.CtrlKey? // otherwise, if ctrl key (pinch gesture) try to zoom else pan
                         freeformScrollMode.Zoom : freeformScrollMode.Pan // prettier-ignore
         ) {
             case freeformScrollMode.Pan:
@@ -1030,9 +1035,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 (!this._props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
             let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
             if (false && NumCast(this.layoutDoc.layout_scrollTop) && NumCast(this.layoutDoc._freeform_scale, minScale) !== minScale) {
-                const relTop = NumCast(this.layoutDoc.layout_scrollTop) / maxScrollTop;
-                this.layoutDoc.layout_scrollTop = undefined;
-                newPanY = minPanY + relTop * (maxPanY - minPanY);
             } else if (fitYscroll > 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) {
                 const maxPanY = minPanY + fitYscroll;
                 const relTop = (panY - minPanY) / (maxPanY - minPanY);
@@ -1289,8 +1291,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 : NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) );
         const childProps = { ...this._props, fieldKey: '', styleProvider: this.clusterStyleProvider };
         return {
-            x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
-            y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
+            x: isNaN(NumCast(x)) ? 0 : NumCast(x),
+            y: isNaN(NumCast(y)) ? 0 : NumCast(y),
             z: Cast(z, 'number'),
             autoDim,
             rotation,
@@ -1424,8 +1426,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return anchor;
     };
 
-    @action closeInfo = () => (Doc.IsInfoUIDisabled = true);
-    infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
+    childDocsFunc = () => this.childDocs;
+    @action closeInfo = () => { Doc.IsInfoUIDisabled = true }; // prettier-ignore
+    infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} LayoutDoc={this.layoutDoc} childDocs={this.childDocsFunc} close={this.closeInfo} />);
 
     componentDidMount() {
         this._props.setContentViewBox?.(this);
@@ -1483,6 +1486,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 if (!code.includes('dashDiv')) {
                     const script = CompileScript(code, { params: { docView: 'any' }, typecheck: false, editable: true });
                     if (script.compiled) script.run({ this: this.DocumentView?.() });
+                    // eslint-disable-next-line no-eval
                 } else code && !first && eval?.(code);
             },
             { fireImmediately: true }
@@ -1491,7 +1495,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this._disposers.layoutElements = reaction(
             // layoutElements can't be a computed value because doLayoutComputation() is an action that has side effect of updating clusters
             () => this.doInternalLayoutComputation,
-            computation => (this._layoutElements = this.doLayoutComputation(computation.newPool, computation.computedElementData)),
+            computation => {
+                this._layoutElements = this.doLayoutComputation(computation.newPool, computation.computedElementData);
+            },
             { fireImmediately: true }
         );
     }
@@ -1512,7 +1518,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const canvas = oldDiv;
                 const img = document.createElement('img'); // create a Image Element
                 try {
-                    img.src = canvas.toDataURL(); //image source
+                    img.src = canvas.toDataURL(); // image source
                 } catch (e) {
                     console.log(e);
                 }
@@ -1567,14 +1573,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const htmlString = new XMLSerializer().serializeToString(newDiv);
         const nativeWidth = width;
         const nativeHeight = height;
-        return CreateImage(Utils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight)
-            .then(async (data_url: any) => {
-                const returnedFilename = await Utils.convertDataUri(data_url, filename, noSuffix, replaceRootFilename);
+        return CreateImage(ClientUtils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight)
+            .then(async (dataUrl: any) => {
+                const returnedFilename = await ClientUtils.convertDataUri(dataUrl, filename, noSuffix, replaceRootFilename);
                 cb(returnedFilename as string, nativeWidth, nativeHeight);
             })
-            .catch(function (error: any) {
-                console.error('oops, something went wrong!', error);
-            });
+            .catch((error: any) => console.error('oops, something went wrong!', error));
     }
 
     componentWillUnmount() {
@@ -1583,14 +1587,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     @action
-    onCursorMove = (e: React.PointerEvent) => {
+    onCursorMove = () => {
         //  super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
     };
 
     @undoBatch
     promoteCollection = () => {
         const childDocs = this.childDocs.slice();
-        childDocs.forEach(doc => {
+        childDocs.forEach(docIn => {
+            const doc = docIn;
             const scr = this.screenToFreeformContentsXf.inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
             doc.x = scr?.[0];
             doc.y = scr?.[1];
@@ -1604,7 +1609,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const width = Math.max(...docs.map(doc => NumCast(doc._width))) + 20;
         const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20;
         const dim = Math.ceil(Math.sqrt(docs.length));
-        docs.forEach((doc, i) => {
+        docs.forEach((docIn, i) => {
+            const doc = docIn;
             doc.x = NumCast(this.Document[this.panXFieldKey]) + (i % dim) * width - (width * dim) / 2;
             doc.y = NumCast(this.Document[this.panYFieldKey]) + Math.floor(i / dim) * height - (height * dim) / 2;
         });
@@ -1704,14 +1710,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         const snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
         activeDocs
-            .filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc))
+            .filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc))
             .forEach(doc => DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited));
 
         const horizLines: number[] = [];
         const vertLines: number[] = [];
         const invXf = this.screenToFreeformContentsXf.inverse();
         snappableDocs
-            .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc))))
+            .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc))))
             .forEach(doc => {
                 const { left, top, width, height } = docDims(doc);
                 const topLeftInScreen = invXf.transformPoint(left, top);
@@ -1727,10 +1733,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     incrementalRender = action(() => {
         if (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.())) {
-            const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
+            const layoutUnrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
             const loadIncrement = this.Document.isTemplateDoc ? Number.MAX_VALUE : 5;
-            for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
-                this._renderCutoffData.set(layout_unrendered[i][Id] + '', true);
+            for (let i = 0; i < Math.min(layoutUnrendered.length, loadIncrement); i++) {
+                this._renderCutoffData.set(layoutUnrendered[i][Id] + '', true);
             }
         }
         this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
@@ -1744,6 +1750,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         );
     }
 
+    showPresPaths = () => CollectionFreeFormView.ShowPresPaths;
     brushedView = () => this._brushedView;
     gridColor = () =>
         DashColor(lightOrDark(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor)))
@@ -1776,6 +1783,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 brushedView={this.brushedView}
                 isAnnotationOverlay={this.isAnnotationOverlay}
                 transform={this.PanZoomCenterXf}
+                showPresPaths={this.showPresPaths}
                 transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))}
                 viewDefDivClick={this._props.viewDefDivClick}>
                 {this.props.children ?? null} {/* most likely case of children is document content that's being annoated: eg., an image */}
@@ -1828,7 +1836,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this._brushtimer1 = setTimeout(
             action(() => {
                 this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2 };
-                this._brushtimer = setTimeout(action(() => (this._brushedView = undefined)), holdTime); // prettier-ignore
+                this._brushtimer = setTimeout(action(() => { this._brushedView = undefined; }), holdTime); // prettier-ignore
             }),
             transTime + 1
         );
@@ -1912,6 +1920,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 @observer
 class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> {
     render() {
+        // eslint-disable-next-line react/destructuring-assignment
         return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele); // prettier-ignore
     }
 }
@@ -1923,11 +1932,12 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
         DocumentManager.Instance.showDocument(dv.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
             if (!focused) {
                 const selfFfview = !dv.Document.isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
-                let containers = dv.containerViewPath?.() ?? [];
+                const containers = dv.containerViewPath?.() ?? [];
                 let parFfview = dv.CollectionFreeFormView;
-                for (var cont of containers) {
+                containers.forEach(cont => {
                     parFfview = parFfview ?? cont.CollectionFreeFormView;
-                }
+                });
+
                 while (parFfview?.Document.isGroup) parFfview = parFfview.DocumentView?.().CollectionFreeFormView;
                 const ffview = selfFfview && selfFfview.layoutDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
                 ffview?.zoomSmoothlyAboutPt(ffview.screenToFreeformContentsXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
@@ -1936,17 +1946,21 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
         });
 }
 ScriptingGlobals.add(CollectionBrowseClick);
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
     !readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame();
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
     !readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
     const selView = SelectionManager.Views;
     if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent';
     runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.()));
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
     SelectionManager.Views.forEach(view =>
         view._props.pinToPres(view.Document, {
@@ -1959,15 +1973,19 @@ ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
         })
     );
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function bringToFront() {
     SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document));
 });
-ScriptingGlobals.add(function sendToBack(doc: Doc) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function sendToBack() {
     SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document, true));
 });
-ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function datavizFromSchema() {
     // creating a dataviz doc to represent the schema table
-    SelectionManager.Views.forEach(view => {
+    SelectionManager.Views.forEach(viewIn => {
+        const view = viewIn;
         if (!view.layoutDoc.schema_columnKeys) {
             view.layoutDoc.schema_columnKeys = new List<string>(['title', 'type', 'author', 'author_date']);
         }
@@ -1975,13 +1993,13 @@ ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
         if (!keys) return;
 
         const children = DocListCast(view.Document[Doc.LayoutFieldKey(view.Document)]);
-        let csvRows = [];
+        const csvRows = [];
         csvRows.push(keys.join(','));
         for (let i = 0; i < children.length; i++) {
-            let eachRow = [];
+            const eachRow = [];
             for (let j = 0; j < keys.length; j++) {
-                var cell = children[i][keys[j]]?.toString();
-                if (cell) cell = cell.toString().replace(/\,/g, '');
+                let cell = children[i][keys[j]]?.toString();
+                if (cell) cell = cell.toString().replace(/,/g, '');
                 eachRow.push(cell);
             }
             csvRows.push(eachRow);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 6eca91e9d..b03e435ce 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,7 +1,9 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 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 { ClientUtils, lightOrDark, returnFalse } from '../../../../ClientUtils';
+import { intersectRect } from '../../../../Utils';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
@@ -20,6 +22,7 @@ import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { UndoManager, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
+import { MarqueeViewBounds } from '../../DocComponent';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { PreviewCursor } from '../../PreviewCursor';
 import { OpenWhere } from '../../nodes/DocumentView';
@@ -28,6 +31,7 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { SubCollectionViewProps } from '../CollectionSubView';
 import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
 import './MarqueeView.scss';
+
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
     getTransform: () => Transform;
@@ -44,13 +48,6 @@ interface MarqueeViewProps {
     slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
 }
 
-export interface MarqueeViewBounds {
-    left: number;
-    top: number;
-    width: number;
-    height: number;
-}
-
 @observer
 export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps & MarqueeViewProps> {
     public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) {
@@ -102,7 +99,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
 
     @action
     onKeyDown = (e: KeyboardEvent) => {
-        //make textbox and add it to this collection
+        // make textbox and add it to this collection
         // tslint:disable-next-line:prefer-const
         const cm = ContextMenu.Instance;
         const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
@@ -141,7 +138,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
                     }
                 }
                 let ypos = y;
-                ns.map(line => {
+                ns.forEach(line => {
                     const indent = line.search(/\S|$/);
                     const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + (indent / 3) * 10, y: ypos, title: line });
                     this._props.addDocument?.(newBox);
@@ -155,7 +152,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
                 pasteImageBitmap((data: any, error: any) => {
                     error && console.log(error);
                     data &&
-                        Utils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
+                        ClientUtils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
                             this._props.Document['thumb-frozen'] = new ImageField(returnedfilename);
                         });
                 })
@@ -169,7 +166,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined };
             this._props.addDocument?.(slide);
             e.stopPropagation();
-        }*/ else if (e.key === 'p' && e.ctrlKey) {
+        } */ else if (e.key === 'p' && e.ctrlKey) {
             e.preventDefault();
             (async () => {
                 const text: string = await navigator.clipboard.readText();
@@ -184,7 +181,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             e.stopPropagation();
         }
     };
-    //heuristically converts pasted text into a table.
+    // heuristically converts pasted text into a table.
     // assumes each entry is separated by a tab
     // skips all rows until it gets to a row with more than one entry
     // assumes that 1st row has header entry for each column
@@ -192,7 +189,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     //         any row that has only one column is a section header-- this header is then added as a column to subsequent rows until the next header
     // assumes each cell is a string or a number
     pasteTable(ns: string[], x: number, y: number) {
-        let csvRows = [];
+        const csvRows = [];
         const headers = ns[0].split('\t');
         csvRows.push(headers.join(','));
         ns[0] = '';
@@ -200,7 +197,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         let eachRow = [];
         for (let i = 1; i < eachCell.length; i++) {
             eachRow.push(eachCell[i].replace(/\,/g, ''));
-            if (i % headers.length == 0) {
+            if (i % headers.length === 0) {
                 csvRows.push(eachRow);
                 eachRow = [];
             }
@@ -233,7 +230,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         this._lastY = e.pageY;
         this._lassoPts.push([e.clientX, e.clientY]);
         if (!e.cancelBubble) {
-            if (!Utils.isClick(this._lastX, this._lastY, this._downX, this._downY, Date.now())) {
+            if (!ClientUtils.isClick(this._lastX, this._lastY, this._downX, this._downY, Date.now())) {
                 if (!this._commandExecuted) {
                     this.showMarquee();
                 }
@@ -320,7 +317,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     @action
     onClick = (e: React.MouseEvent): void => {
         if (this._props.pointerEvents?.() === 'none') return;
-        if (Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
+        if (ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
             if (Doc.ActiveTool === InkTool.None) {
                 if (!this._props.trySelectCluster(e.shiftKey)) {
                     !SnappingManager.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
@@ -335,15 +332,21 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     };
 
     @action
-    showMarquee = () => (this._visible = true);
+    showMarquee = () => {
+        this._visible = true;
+    };
     @action
-    hideMarquee = () => (this._visible = false);
+    hideMarquee = () => {
+        this._visible = false;
+    };
 
     @undoBatch
     delete = action((e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
         const selected = this.marqueeSelect(false);
         SelectionManager.DeselectAll();
-        selected.forEach(doc => (hide ? (doc.hidden = true) : this._props.removeDocument?.(doc)));
+        selected.forEach(doc => {
+            hide ? (doc.hidden = true) : this._props.removeDocument?.(doc);
+        });
 
         this.cleanupInteractions(false);
         MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -540,6 +543,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     };
 
     touchesLine(r1: { left: number; top: number; width: number; height: number }) {
+        // eslint-disable-next-line no-restricted-syntax
         for (const lassoPt of this._lassoPts) {
             const topLeft = this.Transform.transformPoint(lassoPt[0], lassoPt[1]);
             if (r1.left < topLeft[0] && topLeft[0] < r1.left + r1.width && r1.top < topLeft[1] && topLeft[1] < r1.top + r1.height) {
@@ -560,6 +564,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             let hasLeft = false;
             let hasBottom = false;
             let hasRight = false;
+            // eslint-disable-next-line no-restricted-syntax
             for (const lassoPt of this._lassoPts) {
                 const truePoint = this.Transform.transformPoint(lassoPt[0], lassoPt[1]);
                 hasLeft = hasLeft || (truePoint[0] > tl[0] && truePoint[0] < r1.left && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height);
@@ -662,6 +667,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     };
     render() {
         return (
+            // eslint-disable-next-line jsx-a11y/click-events-have-key-events
             <div
                 className="marqueeView"
                 ref={r => {
@@ -673,7 +679,9 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
                     cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer',
                 }}
                 onDragOver={e => e.preventDefault()}
-                onScroll={e => (e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0)}
+                onScroll={e => {
+                    e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0;
+                }}
                 onClick={this.onClick}
                 onPointerDown={this.onPointerDown}>
                 {this._visible ? this.marqueeDiv : null}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index f25872c2b..ab6788e6f 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -4,7 +4,8 @@ import * as React from 'react';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
+import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 228af78aa..6635ab222 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,10 +1,13 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { Toggle, ToggleType, Type } from 'browndash-components';
 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 { ClientUtils, returnTrue } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
@@ -46,13 +49,15 @@ export class CollectionLinearView extends CollectionSubView() {
         this._dropDisposer?.();
         this._widthDisposer?.();
         this._selectedDisposer?.();
-        this.childLayoutPairs.map((pair, ind) => ScriptCast(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
+        this.childLayoutPairs.map(pair => ScriptCast(DocCast(pair.layout.proto)?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
     }
 
     componentDidMount() {
         this._widthDisposer = reaction(
             () => 5 + NumCast(this.dataDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0),
-            width => this.childDocs.length && (this.layoutDoc._width = width),
+            width => {
+                this.childDocs.length && (this.layoutDoc._width = width);
+            },
             { fireImmediately: true }
         );
     }
@@ -64,14 +69,14 @@ export class CollectionLinearView extends CollectionSubView() {
     dimension = () => NumCast(this.layoutDoc._height);
     getTransform = (ele: Opt<HTMLDivElement>) => {
         if (!ele) return Transform.Identity();
-        const { scale, translateX, translateY } = Utils.GetScreenTransform(ele);
+        const { translateX, translateY } = ClientUtils.GetScreenTransform(ele);
         return new Transform(-translateX, -translateY, 1);
     };
 
     @action
     exitLongLinks = () => {
         if (DocumentLinksButton.StartLink?.Document) {
-            action((e: React.PointerEvent<HTMLDivElement>) => Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc));
+            action(() => Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc));
         }
         DocumentLinksButton.StartLink = undefined;
         DocumentLinksButton.StartLinkView = undefined;
@@ -97,8 +102,8 @@ export class CollectionLinearView extends CollectionSubView() {
         e.preventDefault();
     };
 
-    getLinkUI = () => {
-        return !DocumentLinksButton.StartLink ? null : (
+    getLinkUI = () =>
+        !DocumentLinksButton.StartLink ? null : (
             <span className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}>
                 <span className="bottomPopup-text">
                     Creating link from:{' '}
@@ -108,7 +113,7 @@ export class CollectionLinearView extends CollectionSubView() {
                     </b>
                 </span>
 
-                <Tooltip title={<div className="dash-tooltip">{'Toggle description pop-up'} </div>} placement="top">
+                <Tooltip title={<div className="dash-tooltip">Toggle description pop-up </div>} placement="top">
                     <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
                         Labels: {LinkDescriptionPopup.Instance.showDescriptions ? LinkDescriptionPopup.Instance.showDescriptions : 'ON'}
                     </span>
@@ -121,9 +126,8 @@ export class CollectionLinearView extends CollectionSubView() {
                 </Tooltip>
             </span>
         );
-    };
-    getCurrentlyPlayingUI = () => {
-        return !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : (
+    getCurrentlyPlayingUI = () =>
+        !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : (
             <span className="bottomPopup-background">
                 <span className="bottomPopup-text">
                     Currently playing:
@@ -139,7 +143,6 @@ export class CollectionLinearView extends CollectionSubView() {
                 </span>
             </span>
         );
-    };
 
     getDisplayDoc = (doc: Doc, preview: boolean = false) => {
         // hack to avoid overhead of making UndoStack,etc into DocumentView style Boxes. If the UndoStack is ever intended to become part of the persisten state of the dashboard, then this would have to change.
@@ -149,6 +152,7 @@ export class CollectionLinearView extends CollectionSubView() {
             case '<CurrentlyPlayingUI>': return this.getCurrentlyPlayingUI();
             case '<UndoStack>':          return <UndoStack key={doc[Id]}/>;
             case '<Branching>':          return Doc.UserDoc().isBranchingMode  ? <BranchingTrailManager key={doc[Id]} /> : null;
+            default: 
         }
 
         const nested = doc._type_collection === CollectionViewType.Linear;
@@ -161,7 +165,9 @@ export class CollectionLinearView extends CollectionSubView() {
             <div
                 className={preview ? 'preview' : `collectionLinearView-docBtn`}
                 key={doc[Id]}
-                ref={r => (dref = r || undefined)}
+                ref={r => {
+                    dref = r || undefined;
+                }}
                 style={{
                     pointerEvents: 'all',
                     width: NumCast(doc._width),
@@ -194,7 +200,7 @@ export class CollectionLinearView extends CollectionSubView() {
                     childFilters={this._props.childFilters}
                     childFiltersByRanges={this._props.childFiltersByRanges}
                     searchFilterDocs={this._props.searchFilterDocs}
-                    hideResizeHandles={true}
+                    hideResizeHandles
                 />
             </div>
         );
@@ -220,30 +226,26 @@ export class CollectionLinearView extends CollectionSubView() {
                     ScriptCast(this.Document.onClick)?.script.run({ this: this.Document }, console.log);
                 }}
                 tooltip={isExpanded ? 'Close' : 'Open'}
-                fillWidth={true}
-                align={'center'}
+                fillWidth
+                align="center"
             />
         );
 
         return (
             <div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsOpen ? undefined : 'transparent' }}>
                 <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}>
-                    {
-                        <>
-                            {!this.layoutDoc.linearView_Expandable ? null : menuOpener}
-                            {!this.layoutDoc.linearView_IsOpen ? null : (
-                                <div
-                                    className="collectionLinearView-content"
-                                    style={{
-                                        height: this.dimension(),
-                                        flexDirection: flexDir as any,
-                                        gap: flexGap,
-                                    }}>
-                                    {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
-                                </div>
-                            )}
-                        </>
-                    }
+                    {!this.layoutDoc.linearView_Expandable ? null : menuOpener}
+                    {!this.layoutDoc.linearView_IsOpen ? null : (
+                        <div
+                            className="collectionLinearView-content"
+                            style={{
+                                height: this.dimension(),
+                                flexDirection: flexDir as any,
+                                gap: flexGap,
+                            }}>
+                            {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
+                        </div>
+                    )}
                 </div>
             </div>
         );
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 6a956f2ac..46bf56dc8 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -3,16 +3,19 @@ import { Popup, PopupTrigger, Type } from 'browndash-components';
 import { ObservableMap, action, computed, makeObservable, observable, observe } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
-import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
+import { returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
+import { Doc, DocListCast, Field, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
 import { DocData } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
+import { ColumnType } from '../../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { DocUtils, Docs, DocumentOptions, FInfo } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { undoBatch, undoable } from '../../../util/UndoManager';
@@ -30,17 +33,6 @@ import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
 const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
 
-export enum ColumnType {
-    Number,
-    String,
-    Boolean,
-    Date,
-    Image,
-    RTF,
-    Enumeration,
-    Any,
-}
-
 export const FInfotoColType: { [key: string]: ColumnType } = {
     string: ColumnType.String,
     number: ColumnType.Number,
@@ -823,8 +815,8 @@ export class CollectionSchemaView extends CollectionSubView() {
         const docs = !field
             ? this.childDocs
             : [...this.childDocs].sort((docA, docB) => {
-                  const aStr = Field.toString(docA[field] as Field);
-                  const bStr = Field.toString(docB[field] as Field);
+                  const aStr = Field.toString(docA[field] as FieldType);
+                  const bStr = Field.toString(docB[field] as FieldType);
                   var out = 0;
                   if (aStr < bStr) out = -1;
                   if (aStr > bStr) out = 1;
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 5f8b412be..2823e1936 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -2,7 +2,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import { setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
 import { Colors } from '../../global/globalEnums';
 import './CollectionSchemaView.scss';
 
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 39fea2d2e..27a4493cb 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -5,7 +5,8 @@ import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import { CgClose, CgLock, CgLockUnlock } from 'react-icons/cg';
 import { FaExternalLinkAlt } from 'react-icons/fa';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
+import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Doc } from '../../../../fields/Doc';
 import { BoolCast } from '../../../../fields/Types';
 import { DragManager } from '../../../util/DragManager';
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index bf36b2668..08eb35586 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -7,15 +7,17 @@ import * as React from 'react';
 import DatePicker from 'react-datepicker';
 import 'react-datepicker/dist/react-datepicker.css';
 import Select from 'react-select';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field } from '../../../../fields/Doc';
 import { RichTextField } from '../../../../fields/RichTextField';
+import { ColumnType } from '../../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { FInfo, FInfoFieldType } from '../../../documents/Documents';
 import { DocFocusOrOpen } from '../../../util/DocumentManager';
-import { dropActionType } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
@@ -28,7 +30,7 @@ import { OpenWhere, returnEmptyDocViewList } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
-import { ColumnType, FInfotoColType } from './CollectionSchemaView';
+import { FInfotoColType } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
 
 export interface SchemaTableCellProps {
@@ -204,7 +206,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
 
     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
+        if (url.href.indexOf(window.location.origin) === -1) return ClientUtils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver
         if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question
 
         const ext = extname(url.href);
@@ -220,7 +222,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
             .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
         const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
         // If there is a path, follow it; otherwise, follow a link to a default image icon
-        const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
+        const url = paths.length ? paths : [ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
         return url[0];
     }
 
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 2a5732708..50cf26cdb 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -2,10 +2,11 @@ import { Colors } from 'browndash-components';
 import { action, runInAction } from 'mobx';
 import { aggregateBounds } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
-import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { Gestures } from '../../../pen-gestures/GestureTypes';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -14,26 +15,28 @@ import { UndoManager, undoable } from '../../util/UndoManager';
 import { GestureOverlay } from '../GestureOverlay';
 import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm';
-// import { InkTranscription } from '../InkTranscription';
-import { DocData } from '../../../fields/DocSymbols';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
 import { DocumentView } from '../nodes/DocumentView';
+import { ImageBox } from '../nodes/ImageBox';
 import { VideoBox } from '../nodes/VideoBox';
 import { WebBox } from '../nodes/WebBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { ImageBox } from '../nodes/ImageBox';
+// import { InkTranscription } from '../InkTranscription';
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function IsNoneSelected() {
     return SelectionManager.Views.length <= 0;
 }, 'are no document selected');
 
 // toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setView(view: string) {
     const selected = SelectionManager.Docs.lastElement();
     selected ? (selected._type_collection = view) : console.log('[FontIconBox.tsx] changeView failed');
 });
 
 // toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
     const selectedViews = SelectionManager.Views;
     if (Doc.ActiveTool !== InkTool.None) {
@@ -70,15 +73,18 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
             return selected.lastElement()?._backgroundColor ?? 'transparent';
         }
         SetActiveFillColor(color ?? 'transparent');
-        selected.forEach(doc => (doc[DocData].backgroundColor = color));
+        selected.forEach(doc => { doc[DocData].backgroundColor = color; }); // prettier-ignore
     }
+    return '';
 });
 
 // toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) {
     return DocumentView.setDefaultTemplate(checkResult);
 });
 // toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
     if (checkResult) {
         return SelectionManager.Views.length ? StrCast(SelectionManager.Docs.lastElement().layout_headingColor) : Doc.SharingDoc().headingColor;
@@ -93,9 +99,11 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
         Doc.GetProto(Doc.SharingDoc()).headingColor = color === 'transparent' ? undefined : color;
         Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'title');
     }
+    return undefined;
 });
 
 // toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
     const selected = SelectionManager.Views.length ? SelectionManager.Views[0] : undefined;
     if (checkResult) {
@@ -103,19 +111,21 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
         return false;
     }
     selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
+    return undefined;
 });
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) {
     const selected = SelectionManager.Docs.lastElement();
     // prettier-ignore
     const map: Map<'center' |'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
         ['grid', {
             checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
-            setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
+            setDoc: (doc:Doc) => { doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid; },
         }],
         ['snaplines', {
             checkResult: (doc:Doc) => BoolCast(doc?._freeform_snapLines, false),
-            setDoc: (doc:Doc, dv:DocumentView) => doc._freeform_snapLines = !doc._freeform_snapLines,
+            setDoc: (doc:Doc) => { doc._freeform_snapLines = !doc._freeform_snapLines; },
         }],
         ['viewAll', {
             checkResult: (doc:Doc) => BoolCast(doc?._freeform_fitContentsToBox, false),
@@ -127,12 +137,12 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
         }],
         ['center', {
             checkResult: (doc:Doc) => BoolCast(doc?._stacking_alignCenter, false),
-            setDoc: (doc:Doc,dv:DocumentView) => doc._stacking_alignCenter = !doc._stacking_alignCenter,
+            setDoc: (doc:Doc) => { doc._stacking_alignCenter = !doc._stacking_alignCenter; },
         }],
         ['clusters', {
             waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
             checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false),
-            setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters,
+            setDoc: (doc:Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; },
         }],
    ]);
 
@@ -142,11 +152,12 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
     SelectionManager.Views.map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv));
     setTimeout(() => batch.end(), 100);
+    return undefined;
 });
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize' | 'alignment', value: any, checkResult?: boolean) {
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-    const selected = SelectionManager.Docs.lastElement();
     // prettier-ignore
     const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => any; setDoc: () => void;}> = new Map([
         ['font', {
@@ -163,14 +174,15 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
         }],
         ['alignment', {
             checkResult: () => RichTextMenu.Instance?.textAlign,
-            setDoc: () => value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
+            setDoc: () => { value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value); },
         }],
         ['fontSize', {
             checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''),
             setDoc: () => {
-                if (typeof value === 'number') value = value.toString();
-                if (value && Number(value).toString() === value) value += 'px';
-                RichTextMenu.Instance?.setFontSize(value);
+                let fsize = value;
+                if (typeof fsize === 'number') fsize = fsize.toString();
+                if (fsize && Number(fsize).toString() === fsize) fsize += 'px';
+                RichTextMenu.Instance?.setFontSize(fsize);
             },
         }],
     ]);
@@ -179,63 +191,55 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
         return map.get(attr)?.checkResult();
     }
     map.get(attr)?.setDoc?.();
+    return undefined;
 });
 
 type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
 type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => any }];
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) {
     const textView = RichTextMenu.Instance?.TextView;
     const editorView = textView?.EditorView;
     // prettier-ignore
     const alignments:attrfuncs[] = (['left','right','center','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) =>
-        [ where, {      checkResult: () =>(editorView ? (where === 'vcent' ?  RichTextMenu.Instance?.textVcenter ?? false:
-                                                                              (RichTextMenu.Instance?.textAlign === where)): 
-                                                         where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered):
-                                                                              (Doc.UserDoc().textAlign ===where) ? true:false),
-                        toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(editorView, editorView.dispatch):
-                                                                              RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)):
+        [ where, {      checkResult: () => editorView ? (where === 'vcent' ?  RichTextMenu.Instance?.textVcenter ?? false:
+                                                                             (RichTextMenu.Instance?.textAlign === where)): 
+                                                         where === 'vcent' ?  BoolCast(Doc.UserDoc()._layout_centered):
+                                                                            (Doc.UserDoc().textAlign === where),
+                        toggle: () => { editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(editorView, editorView.dispatch):
+                                                                                 RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)):
                                                             where === 'vcent' ?  Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered:
-                                                                              (Doc.UserDoc().textAlign = where))}]); // prettier-ignore
+                                                                                (Doc.UserDoc().textAlign = where); }
+        }]); // prettier-ignore
     // prettier-ignore
     const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
         [ list, {       checkResult: () => (editorView ? RichTextMenu.Instance?.listStyle === list:false),
                         toggle: () => editorView?.state && RichTextMenu.Instance?.changeListType(list) }]);
     // prettier-ignore
     const attrs:attrfuncs[] = [
-        ['dictation', { checkResult: () => textView?._recordingDictation ? true:false,
-                        toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }],
+        ['dictation', { checkResult: () => !!textView?._recordingDictation,
+                        toggle: () => textView && runInAction(() => { textView._recordingDictation = !textView._recordingDictation;} ) }],
         ['elide',      { checkResult: () => false,
                         toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}],
         ['noAutoLink',{ checkResult: () => ((editorView && RichTextMenu.Instance?.noAutoLink) ?? false),
                         toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
-        ['bold',      { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
-                        toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
-        ['italics',   { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
-                        toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
-        ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline') ? true:false),
-                        toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
+        ['bold',      { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold')),
+                        toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => { Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold'; }}],
+        ['italics',   { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics')),
+                        toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => { Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics'; }}],
+        ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline')),
+                        toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => { Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline'; } }]]
 
     const map = new Map(attrs.concat(alignments).concat(listings));
     if (checkResult) {
         return map.get(charStyle)?.checkResult();
     }
     undoable(() => map.get(charStyle)?.toggle?.(), 'toggle ' + charStyle)();
+    return undefined;
 });
 
-export function checkInksToGroup() {
-    if (Doc.ActiveTool === InkTool.Write) {
-        CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
-            // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
-            // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other
-            const inksToGroup = ffView.unprocessedDocs.filter(inkDoc => {
-                // console.log(inkDoc.x, inkDoc.y);
-            });
-        });
-    }
-}
-
-export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
+export function createInkGroup(/* inksToGroup?: Doc[], isSubGroup?: boolean */) {
     // TODO nda - if document being added to is a inkGrouping then we can just add to that group
     if (Doc.ActiveTool === InkTool.Write) {
         CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
@@ -301,26 +305,24 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
     CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
 }
 
-function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) {
+function setActiveTool(tool: InkTool | Gestures, keepPrim: boolean, checkResult?: boolean) {
     // InkTranscription.Instance?.createInkGroup();
     if (checkResult) {
         return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool
-            ? GestureOverlay.Instance?.KeepPrimitiveMode || ![GestureUtils.Gestures.Circle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Rectangle].includes(tool as GestureUtils.Gestures)
-                ? true
-                : true
+            ? GestureOverlay.Instance?.KeepPrimitiveMode || ![Gestures.Circle, Gestures.Line, Gestures.Rectangle].includes(tool as Gestures)
             : false;
     }
     runInAction(() => {
         if (GestureOverlay.Instance) {
             GestureOverlay.Instance.KeepPrimitiveMode = keepPrim;
         }
-        if (Object.values(GestureUtils.Gestures).includes(tool as any)) {
+        if (Object.values(Gestures).includes(tool as any)) {
             if (GestureOverlay.Instance.InkShape === tool && !keepPrim) {
                 Doc.ActiveTool = InkTool.None;
                 GestureOverlay.Instance.InkShape = undefined;
             } else {
                 Doc.ActiveTool = InkTool.Pen;
-                GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures;
+                GestureOverlay.Instance.InkShape = tool as Gestures;
             }
         } else if (tool) {
             // pen or eraser
@@ -334,38 +336,40 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean,
             Doc.ActiveTool = InkTool.None;
         }
     });
+    return undefined;
 }
 
 ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');
 
 // toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
     const selected = SelectionManager.Docs.lastElement() ?? Doc.UserDoc();
     // prettier-ignore
     const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
         ['inkMask', {
             checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())),
-            setInk: (doc: Doc) => (doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask),
+            setInk: (doc: Doc) => { doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask; },
             setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
         }],
         ['labels', {
             checkResult: () => ((selected?._stroke_showLabel ? BoolCast(selected[DocData].stroke_showLabel) : ActiveInkHideTextLabels())),
-            setInk: (doc: Doc) => (doc[DocData].stroke_showLabel = !doc.stroke_showLabel),
+            setInk: (doc: Doc) => { doc[DocData].stroke_showLabel = !doc.stroke_showLabel; },
             setMode: () => selected?.type !== DocumentType.INK && SetActiveInkHideTextLabels(!ActiveInkHideTextLabels()),
         }],
         ['fillColor', {
             checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"),
-            setInk: (doc: Doc) => (doc[DocData].fillColor = StrCast(value)),
+            setInk: (doc: Doc) => { doc[DocData].fillColor = StrCast(value); },
             setMode: () => SetActiveFillColor(StrCast(value)),
         }],
         [ 'strokeWidth', {
             checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()),
-            setInk: (doc: Doc) => (doc[DocData].stroke_width = NumCast(value)),
+            setInk: (doc: Doc) => { doc[DocData].stroke_width = NumCast(value); },
             setMode: () => { SetActiveInkWidth(value.toString()); selected?.type === DocumentType.INK && setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
         }],
         ['strokeColor', {
             checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()),
-            setInk: (doc: Doc) => (doc[DocData].color = String(value)),
+            setInk: (doc: Doc) => { doc[DocData].color = String(value); },
             setMode: () => { SetActiveInkColor(StrCast(value)); selected?.type === DocumentType.INK &&  setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
         }],
     ]);
@@ -375,11 +379,13 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fil
     }
     map.get(option)?.setMode();
     SelectionManager.Docs.filter(doc => doc._layout_isSvg).map(doc => map.get(option)?.setInk(doc));
+    return undefined;
 });
 
 /** WEB
  *      webSetURL
- **/
+ * */
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
     const selected = SelectionManager.Views.lastElement();
     if (selected?.Document.type === DocumentType.WEB) {
@@ -387,30 +393,36 @@ ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
             return StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href);
         }
         selected.ComponentView?.setData?.(url);
-        //selected.Document.data = new WebField(url);
     }
+    return '';
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function webForward(checkResult?: boolean) {
     const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox;
     if (checkResult) {
         return selected?.forward(checkResult) ? undefined : 'lightGray';
     }
     selected?.forward();
+    return undefined;
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function webBack() {
     const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox;
     selected?.back();
 });
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function videoSnapshot() {
     const selected = SelectionManager.Views.lastElement()?.ComponentView as VideoBox;
     selected?.Snapshot();
 });
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function imageSetPixelSize() {
     const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox;
     selected?.setNativeSize();
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function imageRotate90() {
     const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox;
     selected?.rotate();
@@ -418,21 +430,25 @@ ScriptingGlobals.add(function imageRotate90() {
 
 /** Schema
  *      toggleSchemaPreview
- **/
+ * */
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) {
     const selected = SelectionManager.Docs.lastElement();
     if (checkResult && selected) {
         const result: boolean = NumCast(selected.schema_previewWidth) > 0;
         if (result) return Colors.MEDIUM_BLUE;
-        else return 'transparent';
-    } else if (selected) {
+        return 'transparent';
+    }
+    if (selected) {
         if (NumCast(selected.schema_previewWidth) > 0) {
             selected.schema_previewWidth = 0;
         } else {
             selected.schema_previewWidth = 200;
         }
     }
+    return '';
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
     const selected = SelectionManager.Docs.lastElement();
     if (checkResult && selected) {
@@ -441,17 +457,20 @@ ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
     if (selected) {
         selected._schema_singleLine = !selected._schema_singleLine;
     }
+    return undefined;
 });
 
 /** STACK
  *      groupBy
  */
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
-    SelectionManager.Docs.map(doc => (doc._text_fontFamily = key));
+    SelectionManager.Docs.forEach(doc => { doc._text_fontFamily = key; }); // prettier-ignore
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     if (checkResult) {
         return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily);
     }
     if (editorView) RichTextMenu.Instance?.setFontFamily(key);
     else Doc.UserDoc().fontFamily = key;
+    return undefined;
 });
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index a2c9d10b6..38ef08ef9 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -4,13 +4,15 @@ import { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { Cast, DocCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { SelectionManager } from '../../util/SelectionManager';
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index c9e3c203d..f2681bf2e 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -2,7 +2,8 @@ import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { EditorView } from 'prosemirror-view';
 import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
 import { Doc } from '../../../fields/Doc';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index 12b9870ca..dcbc9fc50 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -2,7 +2,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index c685ec66f..c37466914 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,15 +1,18 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { action, computed, IReactionDisposer, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
 import { DateField } from '../../../fields/DateField';
 import { Doc } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DateCast, NumCast } from '../../../fields/Types';
 import { AudioField, nullAudio } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { formatTime } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { Networking } from '../../Network';
 import { DragManager } from '../../util/DragManager';
@@ -18,11 +21,11 @@ import { undoBatch } from '../../util/UndoManager';
 import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent } from '../DocComponent';
 import './AudioBox.scss';
-import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
-import { PinProps, PresBox } from './trails';
 import { OpenWhere } from './DocumentView';
+import { FieldView, FieldViewProps } from './FieldView';
+import { PresBox } from './trails';
 
 /**
  * AudioBox
@@ -42,7 +45,7 @@ declare class MediaRecorder {
     constructor(e: any); // whatever MediaRecorder has
 }
 
-export enum media_state {
+export enum mediaState {
     PendingRecording = 'pendingRecording',
     Recording = 'recording',
     Paused = 'paused',
@@ -97,16 +100,16 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return LinkManager.Links(this.dataDoc);
     }
     @computed get mediaState() {
-        return this.dataDoc.mediaState as media_state;
+        return this.dataDoc.mediaState as mediaState;
+    }
+    set mediaState(value) {
+        this.dataDoc.mediaState = value;
     }
     @computed get path() {
         // returns the path of the audio file
         const path = Cast(this.Document[this.fieldKey], AudioField, null)?.url.href || '';
         return path === nullAudio ? '' : path;
     }
-    set mediaState(value) {
-        this.dataDoc.mediaState = value;
-    }
 
     @computed get timeline() {
         return this._stackedTimeline;
@@ -117,17 +120,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._dropDisposer?.();
         Object.values(this._disposers).forEach(disposer => disposer?.());
 
-        this.mediaState === media_state.Recording && this.stopRecording();
+        this.mediaState === mediaState.Recording && this.stopRecording();
     }
 
     @action
     componentDidMount() {
         this._props.setContentViewBox?.(this);
         if (this.path) {
-            this.mediaState = media_state.Paused;
+            this.mediaState = mediaState.Paused;
             this.setPlayheadTime(NumCast(this.layoutDoc.clipStart));
         } else {
-            this.mediaState = undefined as any as media_state;
+            this.mediaState = undefined as any as mediaState;
         }
     }
 
@@ -149,7 +152,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                   this.Document,
                   this.dataDoc,
                   this.annotationKey,
-                  this._ele?.currentTime || Cast(this.Document._layout_currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
+                  this._ele?.currentTime || Cast(this.Document._layout_currentTimecode, 'number', null) || (this.mediaState === mediaState.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
                   undefined,
                   undefined,
                   addAsAnnotation
@@ -163,10 +166,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // updates timecode and shows it in timeline, follows links at time
     @action
     timecodeChanged = () => {
-        if (this.mediaState !== media_state.Recording && this._ele) {
+        if (this.mediaState !== mediaState.Recording && this._ele) {
             this.links
                 .map(l => this.getLinkData(l))
-                .forEach(({ la1, la2, linkTime }) => {
+                .forEach(({ la1, linkTime }) => {
                     if (linkTime > NumCast(this.layoutDoc._layout_currentTimecode) && linkTime < this._ele!.currentTime) {
                         Doc.linkFollowHighlight(la1);
                     }
@@ -180,7 +183,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @action
     playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => {
         clearTimeout(this._play); // abort any previous clip ending
-        if (Number.isNaN(this._ele?.duration)) {
+        if (isNaN(this._ele?.duration ?? Number.NaN)) {
             // audio element isn't loaded yet... wait 1/2 second and try again
             setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
         } else if (this.timeline && this._ele && AudioBox.Enabled) {
@@ -191,7 +194,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             if (seekTimeInSeconds >= 0 && this.timeline.trimStart <= end && seekTimeInSeconds <= this.timeline.trimEnd) {
                 this._ele.currentTime = start;
                 this._ele.play();
-                this.mediaState = media_state.Playing;
+                this.mediaState = mediaState.Playing;
                 this.addCurrentlyPlaying();
                 this._play = setTimeout(
                     () => {
@@ -233,7 +236,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     // update the recording time
     updateRecordTime = () => {
-        if (this.mediaState === media_state.Recording) {
+        if (this.mediaState === mediaState.Recording) {
             setTimeout(this.updateRecordTime, 30);
             if (!this._paused) {
                 this.layoutDoc._layout_currentTimecode = (new Date().getTime() - this._recordStart - this._pausedTime) / 1000;
@@ -254,7 +257,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             }
         };
         this._recordStart = new Date().getTime();
-        runInAction(() => (this.mediaState = media_state.Recording));
+        runInAction(() => {
+            this.mediaState = mediaState.Recording;
+        });
         setTimeout(this.updateRecordTime);
         this._recorder.start();
         setTimeout(this.stopRecording, 60 * 60 * 1000); // stop after an hour
@@ -269,7 +274,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             const now = new Date().getTime();
             this._paused && (this._pausedTime += now - this._pauseStart);
             this.dataDoc[this.fieldKey + '_duration'] = (now - this._recordStart - this._pausedTime) / 1000;
-            this.mediaState = media_state.Paused;
+            this.mediaState = mediaState.Paused;
             this._stream?.getAudioTracks()[0].stop();
             const ind = DocUtils.ActiveRecordings.indexOf(this);
             ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
@@ -277,26 +282,26 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     // context menu
-    specificContextMenu = (e: React.MouseEvent): void => {
+    specificContextMenu = (): void => {
         const funcs: ContextMenuProps[] = [];
         funcs.push({
             description: (this.layoutDoc.hideAnchors ? "Don't hide" : 'Hide') + ' anchors',
-            event: e => (this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors),
+            event: () => { this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors; }, // prettier-ignore
             icon: 'expand-arrows-alt',
         });
         funcs.push({
             description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered',
-            event: e => (this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks),
+            event: () => { this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks}, // prettier-ignore
             icon: 'expand-arrows-alt',
         });
         funcs.push({
             description: (this.layoutDoc.dontAutoPlayFollowedLinks ? '' : "Don't") + ' play when link is selected',
-            event: e => (this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks),
+            event: () => { this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks; }, // prettier-ignore
             icon: 'expand-arrows-alt',
         });
         funcs.push({
             description: (this.layoutDoc.autoPlayAnchors ? "Don't auto" : 'Auto') + ' play anchors onClick',
-            event: e => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors),
+            event: () => { this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors; }, // prettier-ignore
             icon: 'expand-arrows-alt',
         });
         ContextMenu.Instance?.addItem({
@@ -342,9 +347,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
     };
 
-    IsPlaying = () => this.mediaState === media_state.Playing;
+    IsPlaying = () => this.mediaState === mediaState.Playing;
     TogglePause = () => {
-        if (this.mediaState === media_state.Paused) this.Play();
+        if (this.mediaState === mediaState.Paused) this.Play();
         else this.pause();
     };
     // pause playback without removing from the playback list to allow user to play it again.
@@ -352,7 +357,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     pause = () => {
         if (this._ele) {
             this._ele.pause();
-            this.mediaState = media_state.Paused;
+            this.mediaState = mediaState.Paused;
 
             // if paused in the middle of playback, prevents restart on next play
             if (!this._finished) clearTimeout(this._play);
@@ -434,7 +439,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     // plays link
-    playLink = (link: Doc, options: FocusViewOptions) => {
+    playLink = (link: Doc /* , options: FocusViewOptions */) => {
         if (link.annotationOn === this.Document) {
             if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
                 this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link));
@@ -460,13 +465,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     @action
-    timelineWhenChildContentsActiveChanged = (isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
+    timelineWhenChildContentsActiveChanged = (isActive: boolean) => {
+        this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
+    };
 
     timelineScreenToLocal = () => this.ScreenToLocalBoxXf().translate(0, -AudioBox.topControlsHeight);
 
-    setPlayheadTime = (time: number) => (this._ele!.currentTime /*= this.layoutDoc._layout_currentTimecode*/ = time);
+    setPlayheadTime = (time: number) => {
+        this._ele!.currentTime /* = this.layoutDoc._layout_currentTimecode */ = time;
+    };
 
-    playing = () => this.mediaState === media_state.Playing;
+    playing = () => this.mediaState === mediaState.Playing;
 
     isActiveChild = () => this._isAnyChildContentActive;
 
@@ -497,7 +506,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 e,
                 returnFalse,
                 returnFalse,
-                action(e => {
+                action(() => {
                     if (this.timeline?.IsTrimming !== TrimScope.None) {
                         this.timeline?.CancelTrimming();
                     } else {
@@ -566,7 +575,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             this._dropDisposer = DragManager.MakeDropTarget(
                 r,
                 (e, de) => {
-                    const [xp, yp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
+                    const [xp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
                     de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
                 },
                 this.layoutDoc
@@ -581,7 +590,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 <div className="audiobox-dictation" onPointerDown={this.onFile}>
                     <FontAwesomeIcon size="2x" icon="file-alt" />
                 </div>
-                {[media_state.Recording, media_state.Playing].includes(this.mediaState) ? (
+                {[mediaState.Recording, mediaState.Playing].includes(this.mediaState) ? (
                     <div className="recording-controls" onClick={e => e.stopPropagation()}>
                         <div className="record-button" onPointerDown={this.Record}>
                             <FontAwesomeIcon size="2x" icon="stop" />
@@ -614,31 +623,29 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     <div className="controls-left">
                         <div
                             className="audiobox-button"
-                            title={this.mediaState === media_state.Paused ? 'play' : 'pause'}
+                            title={this.mediaState === mediaState.Paused ? 'play' : 'pause'}
                             onPointerDown={
-                                this.mediaState === media_state.Paused
+                                this.mediaState === mediaState.Paused
                                     ? this.Play
                                     : e => {
                                           e.stopPropagation();
                                           this.Pause();
                                       }
                             }>
-                            <FontAwesomeIcon icon={this.mediaState === media_state.Paused ? 'play' : 'pause'} size={'1x'} />
+                            <FontAwesomeIcon icon={this.mediaState === mediaState.Paused ? 'play' : 'pause'} size="1x" />
                         </div>
 
                         {!this.miniPlayer && (
                             <>
                                 <Tooltip title={<>trim audio</>}>
                                     <div className="audiobox-button" onPointerDown={this.onClipPointerDown}>
-                                        <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size={'1x'} />
+                                        <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size="1x" />
                                     </div>
                                 </Tooltip>
-                                {this.timeline?.IsTrimming == TrimScope.None && !NumCast(this.layoutDoc.clipStart) && NumCast(this.layoutDoc.clipEnd) === this.rawDuration ? (
-                                    <></>
-                                ) : (
-                                    <Tooltip title={<>{this.timeline?.IsTrimming !== TrimScope.None ? 'Cancel trimming' : 'Edit original timeline'}</>}>
+                                {this.timeline?.IsTrimming === TrimScope.None && !NumCast(this.layoutDoc.clipStart) && NumCast(this.layoutDoc.clipEnd) === this.rawDuration ? null : (
+                                    <Tooltip title={this.timeline?.IsTrimming !== TrimScope.None ? 'Cancel trimming' : 'Edit original timeline'}>
                                         <div className="audiobox-button" onPointerDown={this.onResetPointerDown}>
-                                            <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'cancel' : 'arrows-left-right'} size={'1x'} />
+                                            <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'cancel' : 'arrows-left-right'} size="1x" />
                                         </div>
                                     </Tooltip>
                                 )}
@@ -705,9 +712,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @computed get renderTimeline() {
         return (
             <CollectionStackedTimeline
-                ref={action((r: CollectionStackedTimeline | null) => (this._stackedTimeline = r))}
+                ref={action((r: CollectionStackedTimeline | null) => {
+                    this._stackedTimeline = r;
+                })}
+                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props}
-                CollectionFreeFormDocumentView={undefined}
                 dataFieldKey={this.fieldKey}
                 fieldKey={this.annotationKey}
                 dictationKey={this.fieldKey + '_dictation'}
@@ -738,10 +747,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // returns the html audio element
     @computed get audio() {
         return (
+            // eslint-disable-next-line jsx-a11y/media-has-caption
             <audio
                 ref={this.setRef}
                 className={`audiobox-control${this._props.isContentActive() ? '-interactive' : ''}`}
-                onLoadedData={action(e => this._ele?.duration && this._ele?.duration !== Infinity && (this.dataDoc[this.fieldKey + '_duration'] = this._ele.duration))}>
+                onLoadedData={action(() => {
+                    this._ele?.duration && this._ele?.duration !== Infinity && (this.dataDoc[this.fieldKey + '_duration'] = this._ele.duration);
+                })}>
                 <source src={this.path} type="audio/mpeg" />
                 Not supported.
             </audio>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 0d0a7c623..958d63267 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,8 +1,10 @@
-import { action, makeObservable, observable, trace } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { OmitKeys, numberRange } from '../../../Utils';
+import { OmitKeys } from '../../../ClientUtils';
+import { numberRange } from '../../../Utils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { TransitionTimer } from '../../../fields/DocSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ComputedField } from '../../../fields/ScriptField';
@@ -17,7 +19,6 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import './CollectionFreeFormDocumentView.scss';
 import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
 import { FieldViewProps } from './FieldView';
-import { TransitionTimer } from '../../../fields/DocSymbols';
 
 /// Ugh, typescript has no run-time way of iterating through the keys of an interface.  so we need
 /// manaully keep this list of keys in synch wih the fields of the freeFormProps interface
@@ -239,6 +240,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         const isGroup = this.dataDoc.isGroup && (!backColor || backColor === 'transparent');
         return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
     };
+    localRotation = () => this._props.rotation;
     render() {
         TraceMobx();
 
@@ -259,6 +261,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     <DocumentView
                         {...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore
                         DataTransition={this.DataTransition}
+                        LocalRotation={this.localRotation}
                         CollectionFreeFormDocumentView={this.returnThis}
                         styleProvider={this.styleProvider}
                         ScreenToLocalTransform={this.screenToLocalTransform}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 9ffdc350d..99f9c03bf 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,22 +1,24 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, makeObservable, observable } from 'mobx';
+import { action, computed, makeObservable, observable, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
+import { returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { RichTextField } from '../../../fields/RichTextField';
 import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
 import { DocUtils, Docs } from '../../documents/Documents';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { undoBatch } from '../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import './ComparisonBox.scss';
 import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { KeyValueBox } from './KeyValueBox';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
-import { PinProps, PresBox } from './trails';
+import { PresBox } from './trails';
 
 @observer
 export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -164,7 +166,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
      * @returns a JSX layout string if a text field is found, othwerise undefined
      */
     testForTextFields = (whichSlot: string) => {
-        const slotHasText = Doc.Get(this.dataDoc, whichSlot, true) instanceof RichTextField || typeof Doc.Get(this.dataDoc, whichSlot, true) === 'string';
+        const slotData = Doc.Get(this.dataDoc, whichSlot, true);
+        const slotHasText = slotData instanceof RichTextField || typeof slotData === 'string';
         const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim();
         const altText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text.trim();
         const layoutTemplateString =
@@ -180,8 +183,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         //     where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field
         // The GPT call will put the "answer"  in the second slot of the comparison (eg., text_2)
         if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) {
-            var queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
-            if (queryText && queryText.match(/\(\(.*\)\)/)) {
+            const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ...
+            if (queryText?.match(/\(\(.*\)\)/)) {
                 KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt
             }
         }
@@ -190,17 +193,16 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
     _closeRef = React.createRef<HTMLDivElement>();
     render() {
-        const clearButton = (which: string) => {
-            return (
-                <div
-                    ref={this._closeRef}
-                    className={`clear-button ${which}`}
-                    onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding
-                >
-                    <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
-                </div>
-            );
-        };
+        trace();
+        const clearButton = (which: string) => (
+            <div
+                ref={this._closeRef}
+                className={`clear-button ${which}`}
+                onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding
+            >
+                <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
+            </div>
+        );
 
         /**
          * Display the Docs in the before/after fields of the comparison.  This also supports a GPT flash card use case
@@ -229,7 +231,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         isDocumentActive={returnFalse}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                         styleProvider={this._isAnyChildContentActive ? this._props.styleProvider : this.docStyleProvider}
-                        hideLinkButton={true}
+                        hideLinkButton
                         pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
                     />
                     {layoutTemplateString ? null : clearButton(whichSlot)}
@@ -240,13 +242,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 </div>
             );
         };
-        const displayBox = (which: string, index: number, cover: number) => {
-            return (
-                <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}>
-                    {displayDoc(which)}
-                </div>
-            );
-        };
+        const displayBox = (which: string, index: number, cover: number) => (
+            <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}>
+                {displayDoc(which)}
+            </div>
+        );
 
         return (
             <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 22f1f7b79..2a0bc42ee 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,9 +1,11 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Checkbox } from '@mui/material';
 import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
 import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
+import { returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -14,20 +16,18 @@ import { TraceMobx } from '../../../../fields/util';
 import { DocUtils, Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
 import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
 import { DocumentView } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
-import { PinProps } from '../trails';
+import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
 import './DataVizBox.scss';
 import { Histogram } from './components/Histogram';
 import { LineChart } from './components/LineChart';
 import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
-import { Checkbox } from '@mui/material';
 
 export enum DataVizView {
     TABLE = 'table',
diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
index 24023077f..0084d7394 100644
--- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
+++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
@@ -3,7 +3,8 @@ import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { CgClose } from 'react-icons/cg';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
 import { Doc } from '../../../../fields/Doc';
 import { StrCast } from '../../../../fields/Types';
 import { DragManager } from '../../../util/DragManager';
@@ -84,7 +85,7 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
                     const embedding = Doc.MakeEmbedding(this.dataVizDoc!);
                     return embedding;
                 };
-                if (this.view && sourceAnchorCreator && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
+                if (this.view && sourceAnchorCreator && !ClientUtils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
                     DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
                         dragComplete: e => {
                             this.setVisible(false);
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 6672603f3..58cacef76 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -11,8 +11,9 @@ import { listSpec } from '../../../../../fields/Schema';
 import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
 import { undoable } from '../../../../util/UndoManager';
+import { PinProps } from '../../../DocComponent';
 import { ObservableReactComponent } from '../../../ObservableReactComponent';
-import { PinProps, PresBox } from '../../trails';
+import { PresBox } from '../../trails';
 import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
 import './Chart.scss';
 
@@ -64,7 +65,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
         if (this._props.axes.length < 1) return [];
         if (this._props.axes.length < 2) {
             var ax0 = this._props.axes[0];
-            if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])){
+            if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) {
                 this.numericalXData = true;
             }
             return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]] }));
@@ -132,7 +133,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
 
     // cleans data by converting numerical data to numbers and taking out empty cells
     data = (dataSet: any) => {
-        var validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+        var validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
         const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
         return !field
             ? []
@@ -191,7 +192,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
         const endingPoint = this.numericalXData ? this.rangeVals.xMax! : numBins;
 
         // converts data into Objects
-        var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+        var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
         if (!this.numericalXData) {
             var histStringDataSet: { [x: string]: unknown }[] = [];
             if (this.numericalYData) {
@@ -452,17 +453,16 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
                     : ''
             );
             selected = selected.substring(0, selected.length - 2) + ' }';
-            if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){
-                selected+= "\n" + this._props.titleCol + ": "
+            if (this._props.titleCol != '' && (!this._currSelected['frequency'] || this._currSelected['frequency'] < 10)) {
+                selected += '\n' + this._props.titleCol + ': ';
                 this._tableData.forEach(each => {
-                    if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) {
-                        if (this._props.axes[1]){
-                            if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", ";
-                        }
-                        else selected+= each[this._props.titleCol] + ", ";
+                    if (this._currSelected[this._props.axes[0]] == each[this._props.axes[0]]) {
+                        if (this._props.axes[1]) {
+                            if (this._currSelected[this._props.axes[1]] == each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', ';
+                        } else selected += each[this._props.titleCol] + ', ';
                     }
-                })
-                selected = selected.slice(0,-1).slice(0,-1);
+                });
+                selected = selected.slice(0, -1).slice(0, -1);
             }
         }
         var selectedBarColor;
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index e093ec648..c667a15de 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -11,10 +11,11 @@ import { Docs } from '../../../../documents/Documents';
 import { DocumentManager } from '../../../../util/DocumentManager';
 import { undoable } from '../../../../util/UndoManager';
 import { ObservableReactComponent } from '../../../ObservableReactComponent';
-import { PinProps, PresBox } from '../../trails';
+import { PresBox } from '../../trails';
 import { DataVizBox } from '../DataVizBox';
 import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
 import './Chart.scss';
+import { PinProps } from '../../../DocComponent';
 
 export interface DataPoint {
     x: number;
@@ -258,17 +259,18 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
             .attr('transform', `translate(${margin.left}, ${margin.top})`));
 
         var validSecondData;
-        if (this._props.axes.length>2){ // for when there are 2 lines on the chart
+        if (this._props.axes.length > 2) {
+            // for when there are 2 lines on the chart
             var next = this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[2]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
             validSecondData = next.filter(d => {
-                if (!d.x || Number.isNaN(d.x) || !d.y || Number.isNaN(d.y)) return false;
+                if (!d.x || isNaN(d.x) || !d.y || isNaN(d.y)) return false;
                 return true;
             });
             var secondDataRange = minMaxRange([validSecondData]);
-            if (secondDataRange.xMax!>xMax) xMax = secondDataRange.xMax;
-            if (secondDataRange.yMax!>yMax) yMax = secondDataRange.yMax;
-            if (secondDataRange.xMin!<xMin) xMin = secondDataRange.xMin;
-            if (secondDataRange.yMin!<yMin) yMin = secondDataRange.yMin;
+            if (secondDataRange.xMax! > xMax) xMax = secondDataRange.xMax;
+            if (secondDataRange.yMax! > yMax) yMax = secondDataRange.yMax;
+            if (secondDataRange.xMin! < xMin) xMin = secondDataRange.xMin;
+            if (secondDataRange.yMin! < yMin) yMin = secondDataRange.yMin;
         }
 
         // creating the x and y scales
@@ -285,37 +287,45 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
         if (validSecondData) {
             drawLine(svg.append('path'), validSecondData, lineGen, true);
             this.drawDataPoints(validSecondData, 0, xScale, yScale);
-            svg.append('path').attr("stroke", "red");
+            svg.append('path').attr('stroke', 'red');
 
             // legend
-            var color = d3.scaleOrdinal()
-                .range(["black", "blue"])
-                .domain([this._props.axes[1], this._props.axes[2]])
-            svg.selectAll("mydots")
+            var color = d3.scaleOrdinal().range(['black', 'blue']).domain([this._props.axes[1], this._props.axes[2]]);
+            svg.selectAll('mydots')
                 .data([this._props.axes[1], this._props.axes[2]])
                 .enter()
-                .append("circle")
-                    .attr("cx", 5)
-                    .attr("cy", function(d,i){ return -30 + i*15})
-                    .attr("r", 7)
-                    .style("fill", function(d){ return color(d)})
-            svg.selectAll("mylabels")
+                .append('circle')
+                .attr('cx', 5)
+                .attr('cy', function (d, i) {
+                    return -30 + i * 15;
+                })
+                .attr('r', 7)
+                .style('fill', function (d) {
+                    return color(d);
+                });
+            svg.selectAll('mylabels')
                 .data([this._props.axes[1], this._props.axes[2]])
                 .enter()
-                .append("text")
-                    .attr("x", 25)
-                    .attr("y", function(d,i){ return -30 + i*15})
-                    .style("fill", function(d){ return color(d)})
-                    .text(function(d){ return d})
-                    .attr("text-anchor", "left")
-                    .style("alignment-baseline", "middle")
+                .append('text')
+                .attr('x', 25)
+                .attr('y', function (d, i) {
+                    return -30 + i * 15;
+                })
+                .style('fill', function (d) {
+                    return color(d);
+                })
+                .text(function (d) {
+                    return d;
+                })
+                .attr('text-anchor', 'left')
+                .style('alignment-baseline', 'middle');
         }
 
         // get valid data points
         const data = dataSet[0];
         var validData = data.filter(d => {
             Object.keys(data[0]).map(key => {
-                if (!d[key] || Number.isNaN(d[key])) return false;
+                if (!d[key] || isNaN(d[key])) return false;
             });
             return true;
         });
@@ -399,16 +409,16 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
         else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
         if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
         const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
-        var selectedTitle = "";
-        if (this._currSelected && this._props.titleCol){
-            selectedTitle+= "\n" + this._props.titleCol + ": "
+        var selectedTitle = '';
+        if (this._currSelected && this._props.titleCol) {
+            selectedTitle += '\n' + this._props.titleCol + ': ';
             this._tableData.forEach(each => {
-                 var mapThisEntry = false;
-                if (this._currSelected.x==each[this._props.axes[0]] && this._currSelected.y==each[this._props.axes[1]]) mapThisEntry = true;
-                else if (this._currSelected.y==each[this._props.axes[0]] && this._currSelected.x==each[this._props.axes[1]]) mapThisEntry = true;
-                if (mapThisEntry) selectedTitle += each[this._props.titleCol] + ", ";
-            })
-            selectedTitle = selectedTitle.slice(0,-1).slice(0,-1);
+                var mapThisEntry = false;
+                if (this._currSelected.x == each[this._props.axes[0]] && this._currSelected.y == each[this._props.axes[1]]) mapThisEntry = true;
+                else if (this._currSelected.y == each[this._props.axes[0]] && this._currSelected.x == each[this._props.axes[1]]) mapThisEntry = true;
+                if (mapThisEntry) selectedTitle += each[this._props.titleCol] + ', ';
+            });
+            selectedTitle = selectedTitle.slice(0, -1).slice(0, -1);
         }
         if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) {
             return this._props.axes.length >= 2 && /\d/.test(this._props.records[0][this._props.axes[0]]) && /\d/.test(this._props.records[0][this._props.axes[1]]) ? (
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index fc23f47de..2735a40d5 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -12,8 +12,9 @@ import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
 import { undoable } from '../../../../util/UndoManager';
 import { ObservableReactComponent } from '../../../ObservableReactComponent';
-import { PinProps, PresBox } from '../../trails';
+import { PresBox } from '../../trails';
 import './Chart.scss';
+import { PinProps } from '../../../DocComponent';
 
 export interface PieChartProps {
     Document: Doc;
@@ -122,7 +123,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
 
     // cleans data by converting numerical data to numbers and taking out empty cells
     data = (dataSet: any) => {
-        const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+        const validData = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
         const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined;
         return !field
             ? undefined
@@ -192,7 +193,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
 
         // converts data into Objects
         var data = this.data(dataSet);
-        var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key])));
+        var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
         if (this.byCategory) {
             let uniqueCategories = [...new Set(data)];
             var pieStringDataSet: { frequency: number }[] = [];
@@ -348,17 +349,16 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
             });
             selected = selected.substring(0, selected.length - 2);
             selected += ' }';
-            if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){
-                selected+= "\n" + this._props.titleCol + ": "
+            if (this._props.titleCol != '' && (!this._currSelected['frequency'] || this._currSelected['frequency'] < 10)) {
+                selected += '\n' + this._props.titleCol + ': ';
                 this._tableData.forEach(each => {
-                    if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) {
-                        if (this._props.axes[1]){
-                            if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", ";
-                        }
-                        else selected+= each[this._props.titleCol] + ", ";
+                    if (this._currSelected[this._props.axes[0]] == each[this._props.axes[0]]) {
+                        if (this._props.axes[1]) {
+                            if (this._currSelected[this._props.axes[1]] == each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', ';
+                        } else selected += each[this._props.titleCol] + ', ';
                     }
-                })
-                selected = selected.slice(0,-1).slice(0,-1);
+                });
+                selected = selected.slice(0, -1).slice(0, -1);
             }
         } else selected = 'none';
         var selectedSliceColor;
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 53d1869d9..15959c61d 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -2,7 +2,8 @@ import { Button, Type } from 'browndash-components';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../../Utils';
+import { ClientUtils, setupMoveUpEvents } from '../../../../../ClientUtils';
+import { emptyFunction } from '../../../../../Utils';
 import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
 import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
@@ -137,7 +138,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                     embedding.pieSliceColors = Field.Copy(this._props.layoutDoc.pieSliceColors);
                     return embedding;
                 };
-                if (this._props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
+                if (this._props.docView?.() && !ClientUtils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
                     DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
                         dragComplete: e => {
                             if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index e729e2fa2..518158a7f 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -3,7 +3,8 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import JsxParser from 'react-jsx-parser';
 import * as XRegExp from 'xregexp';
-import { OmitKeys, Without, emptyPath } from '../../../Utils';
+import { OmitKeys } from '../../../ClientUtils';
+import { Without, emptyPath } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { AclPrivate, DocData } from '../../../fields/DocSymbols';
 import { ScriptField } from '../../../fields/ScriptField';
@@ -20,7 +21,6 @@ import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
 import { PresElementBox } from '../nodes/trails/PresElementBox';
 import { SearchBox } from '../search/SearchBox';
 import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo';
-import { YoutubeBox } from './../../apis/youtube/YoutubeBox';
 import { AudioBox } from './AudioBox';
 import { ComparisonBox } from './ComparisonBox';
 import { DataVizBox } from './DataVizBox/DataVizBox';
@@ -250,7 +250,6 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
                     AudioBox,
                     RecordingBox,
                     PresBox,
-                    YoutubeBox,
                     PresElementBox,
                     SearchBox,
                     FunctionPlotBox,
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index 4a22766cc..364406197 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -3,11 +3,12 @@ import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { factory } from 'typescript';
-import { Field } from '../../../fields/Doc';
+import { FieldType } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
+import { StrCast } from '../../../fields/Types';
 import { DocumentManager } from '../../util/DocumentManager';
 import { Transformer, ts } from '../../util/Scripting';
-import { SettingsManager } from '../../util/SettingsManager';
+import { SnappingManager } from '../../util/SnappingManager';
 import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DocumentView } from './DocumentView';
@@ -28,22 +29,22 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
         return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.Contains(v)) : DocumentManager.Instance.DocumentViews;
     }
     render() {
-        const view = this._props.view;
-        const { left, top, right, bottom } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 };
+        const { view } = this._props;
+        const { left, top, right } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 };
 
         return (
             <div
                 className="documentIcon-outerDiv"
-                onPointerEnter={action(e => (this._hovered = true))}
-                onPointerLeave={action(e => (this._hovered = false))}
+                onPointerEnter={action(() => { this._hovered = true; })} // prettier-ignore
+                onPointerLeave={action(() => { this._hovered = false; })} // prettier-ignore
                 style={{
                     pointerEvents: 'all',
                     opacity: this._hovered ? 0.3 : 1,
                     position: 'absolute',
-                    background: SettingsManager.userBackgroundColor,
+                    background: SnappingManager.userBackgroundColor,
                     transform: `translate(${(left + right) / 2}px, ${top}px)`,
                 }}>
-                <Tooltip title={<>{this._props.view.Document.title}</>}>
+                <Tooltip title={<div>{StrCast(this._props.view.Document?.title)}</div>}>
                     <p>d{this._props.index}</p>
                 </Tooltip>
             </div>
@@ -56,40 +57,40 @@ export class DocumentIconContainer extends React.Component {
     public static getTransformer(): Transformer {
         const usedDocuments = new Set<number>();
         return {
-            transformer: context => {
-                return root => {
-                    function visit(node: ts.Node) {
-                        node = ts.visitEachChild(node, visit, context);
+            transformer: context => root => {
+                function visit(nodeIn: ts.Node) {
+                    const node = ts.visitEachChild(nodeIn, visit, context);
 
-                        if (ts.isIdentifier(node)) {
-                            const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
-                            const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
-                            const isntParameter = !ts.isParameter(node.parent);
-                            if (isntPropAccess && isntPropAssign && isntParameter && !(node.text in globalThis)) {
-                                const match = node.text.match(/d([0-9]+)/);
-                                if (match) {
-                                    const m = parseInt(match[1]);
-                                    const doc = DocumentIcon.DocViews[m].Document;
-                                    usedDocuments.add(m);
-                                    return factory.createIdentifier(`idToDoc("${doc[Id]}")`);
-                                }
+                    if (ts.isIdentifier(node)) {
+                        const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+                        const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+                        const isntParameter = !ts.isParameter(node.parent);
+                        if (isntPropAccess && isntPropAssign && isntParameter && !(node.text in globalThis)) {
+                            const match = node.text.match(/d([0-9]+)/);
+                            if (match) {
+                                const m = parseInt(match[1]);
+                                const doc = DocumentIcon.DocViews[m].Document;
+                                usedDocuments.add(m);
+                                return factory.createIdentifier(`idToDoc("${doc[Id]}")`);
                             }
                         }
-
-                        return node;
                     }
-                    return ts.visitNode(root, visit);
-                };
+
+                    return node;
+                }
+                return ts.visitNode(root, visit);
             },
             getVars() {
                 const docs = DocumentIcon.DocViews;
-                const capturedVariables: { [name: string]: Field } = {};
-                usedDocuments.forEach(index => (capturedVariables[`d${index}`] = docs.length > index ? docs[index].Document : `d${index}`));
+                const capturedVariables: { [name: string]: FieldType } = {};
+                usedDocuments.forEach(index => {
+                    capturedVariables[`d${index}`] = docs.length > index ? docs[index].Document : `d${index}`;
+                });
                 return capturedVariables;
             },
         };
     }
     render() {
-        return DocumentIcon.DocViews.map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
+        return DocumentIcon.DocViews.map((dv, i) => <DocumentIcon key={dv.DocUniqueId} index={i} view={dv} />);
     }
 }
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 2a68d2bf6..d378082f8 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -3,21 +3,22 @@ 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 { StopEvent, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { StrCast } from '../../../fields/Types';
 import { DocUtils } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { Hypothesis } from '../../util/HypothesisUtils';
 import { LinkManager } from '../../util/LinkManager';
 import { UndoManager, undoBatch } from '../../util/UndoManager';
+import { PinProps } from '../DocComponent';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import './DocumentLinksButton.scss';
 import { DocumentView } from './DocumentView';
 import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { TaskCompletionBox } from './TaskCompletedBox';
-import { PinProps } from './trails';
-import { DocData } from '../../../fields/DocSymbols';
 
 interface DocumentLinksButtonProps {
     View: DocumentView;
@@ -151,7 +152,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
                 DocumentLinksButton.StartLinkView = undefined;
                 DocumentLinksButton.AnnotationId = undefined;
                 DocumentLinksButton.AnnotationUri = undefined;
-                //!this._props.StartLink
+                // !this._props.StartLink
             } else if (startLink !== endLink) {
                 endLink = endLinkView?.ComponentView?.getAnchor?.(true, pinProps) || endLink;
                 startLink = DocumentLinksButton.StartLinkView?.ComponentView?.getAnchor?.(true) || startLink;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ee7bbbdba..dbd2ebe0d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,11 +1,15 @@
+/* eslint-disable no-use-before-define */
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { Howl } from 'howler';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
-import { DivWidth, Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
+import { ClientUtils, DivWidth, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../ClientUtils';
+import { Utils, emptyFunction, emptyPath } from '../../../Utils';
+import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
 import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -23,14 +27,14 @@ import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'
 import { DocUtils, Docs } from '../../documents/Documents';
 import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter';
 import { FollowLinkScript } from '../../util/LinkFollower';
-import { LinkManager } from '../../util/LinkManager';
+import { LinkManager, UPDATE_SERVER_CACHE } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SearchUtil } from '../../util/SearchUtil';
 import { SelectionManager } from '../../util/SelectionManager';
-import { SettingsManager } from '../../util/SettingsManager';
 import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
@@ -42,6 +46,7 @@ import { FieldsDropdown } from '../FieldsDropdown';
 import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
 import { AudioAnnoState, StyleProp } from '../StyleProvider';
+import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
 import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
 import { DocumentLinksButton } from './DocumentLinksButton';
 import './DocumentView.scss';
@@ -50,12 +55,6 @@ import { KeyValueBox } from './KeyValueBox';
 import { LinkAnchorBox } from './LinkAnchorBox';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { PresEffect, PresEffectDirection } from './trails';
-interface Window {
-    MediaRecorder: MediaRecorder;
-}
-declare class MediaRecorder {
-    constructor(e: any); // whatever MediaRecorder has
-}
 
 export enum OpenWhereMod {
     none = '',
@@ -83,9 +82,6 @@ export enum OpenWhere {
     addRightKeyvalue = 'add:right:keyValue',
 }
 
-export function returnEmptyDocViewList() {
-    return [] as DocumentView[];
-}
 export interface DocumentViewProps extends FieldViewSharedProps {
     hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
     hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected
@@ -183,12 +179,12 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
     @computed get disableClickScriptFunc() {
         const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
-        // prettier-ignore
         return (
+            // eslint-disable-next-line no-use-before-define
             DocumentView.LongPress ||
             onScriptDisable === 'always' ||
             (onScriptDisable !== 'never' && (this.rootSelected() || this._componentView?.isAnyChildContentActive?.()))
-        );
+        ); // prettier-ignore
     }
     @computed get _rootSelected() {
         return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
@@ -237,7 +233,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     }
 
     componentDidMount() {
-        runInAction(() => (this._mounted = true));
+        runInAction(() => {
+            this._mounted = true;
+        });
         this.setupHandlers();
         this._disposers.contentActive = reaction(
             () =>
@@ -249,19 +247,23 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     : Doc.ActiveTool !== InkTool.None || SnappingManager.CanEmbed || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive()
                       ? true
                       : undefined,
-            active => (this._isContentActive = active),
+            active => {
+                this._isContentActive = active;
+            },
             { fireImmediately: true }
         );
         this._disposers.pointerevents = reaction(
             () => this.style(this.Document, StyleProp.PointerEvents),
-            pointerevents => (this._pointerEvents = pointerevents),
+            pointerevents => {
+                this._pointerEvents = pointerevents;
+            },
             { fireImmediately: true }
         );
     }
     preDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => {
         const dragData = de.complete.docDragData;
         if (dragData && this.isContentActive() && !this.props.dontRegisterView) {
-            dragData.dropAction = dropAction ? dropAction : dragData.dropAction;
+            dragData.dropAction = dropAction || dragData.dropAction;
             e.stopPropagation();
         }
     };
@@ -291,7 +293,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             dragData.removeDocument = this._props.removeDocument;
             dragData.moveDocument = this._props.moveDocument;
             dragData.draggedViews = [docView];
-            dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
+            dragData.canEmbed = !!(this.Document.dragAction ?? this._props.dragAction);
             (this._props.dragConfig ?? this._componentView?.dragConfig)?.(dragData);
             DragManager.StartDocumentDrag(
                 selected.map(dv => dv.ContentDiv!),
@@ -311,7 +313,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
         if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
         const documentView = this._docView;
-        if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+        if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
             let stopPropagate = true;
             let preventDefault = true;
             !this.layoutDoc._keepZWhenDragged && this._props.bringToFront?.(this.Document);
@@ -368,6 +370,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
                     this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
                     this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300);
+                    // eslint-disable-next-line no-use-before-define
                 } else if (!DocumentView.LongPress) {
                     this._singleClickFunc();
                     this._singleClickFunc = undefined;
@@ -380,6 +383,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
     onPointerDown = (e: React.PointerEvent): void => {
         if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
+        // eslint-disable-next-line no-use-before-define
         this._longPressSelector = setTimeout(() => DocumentView.LongPress && this._props.select(false), 1000);
         if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._docView;
 
@@ -412,7 +416,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     onPointerMove = (e: PointerEvent): void => {
         if (e.buttons !== 1 || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
 
-        if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
+        if (!ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
             this.cleanupPointerEvents();
             this._longPressSelector && clearTimeout(this._longPressSelector);
             this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && dropActionType.embed) || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
@@ -430,14 +434,15 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
         if (this.onPointerUpHandler?.script) {
             this.onPointerUpHandler.script.run({ this: this.Document }, console.log);
-        } else if (e.button === 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
-            this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < Utils.CLICK_TIME;
+        } else if (e.button === 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+            this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < ClientUtils.CLICK_TIME;
             if (!this.isContentActive()) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
         }
+        // eslint-disable-next-line no-use-before-define
         if (DocumentView.LongPress) e.preventDefault();
     };
 
-    toggleFollowLink = undoable((zoom?: boolean, setTargetToggle?: boolean): void => {
+    toggleFollowLink = undoable((): void => {
         const hadOnClick = this.Document.onClick;
         this.noOnClick();
         this.Document.onClick = hadOnClick ? undefined : FollowLinkScript();
@@ -458,16 +463,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     }, 'default on click');
 
     deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
-    setToggleDetail = undoable(
-        (scriptFieldKey: 'onClick') =>
-            (this.Document[scriptFieldKey] = ScriptField.MakeScript(
-                `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
-                    .replace('layout_', '')
-                    .replace(/^layout$/, 'detail')}")`,
-                { documentView: 'any' }
-            )),
-        'set toggle detail'
-    );
+    setToggleDetail = undoable((scriptFieldKey: 'onClick') => {
+        this.Document[scriptFieldKey] = ScriptField.MakeScript(
+            `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
+                .replace('layout_', '')
+                .replace(/^layout$/, 'detail')}")`,
+            { documentView: 'any' }
+        );
+    }, 'set toggle detail');
 
     drop = undoable((e: Event, de: DragManager.DropEvent) => {
         if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
@@ -505,7 +508,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         const input = document.createElement('input');
         input.type = 'file';
         input.accept = '.zip';
-        input.onchange = _e => {
+        input.onchange = () => {
             if (input.files) {
                 const batch = UndoManager.StartBatch('importing');
                 Doc.importDocument(input.files[0]).then(doc => {
@@ -523,7 +526,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         if (e && this.layoutDoc.layout_hideContextMenu && Doc.noviceMode) {
             e.preventDefault();
             e.stopPropagation();
-            //!this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false);
+            // !this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false);
         }
         // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
         if (e) {
@@ -535,7 +538,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             e.stopPropagation();
             e.persist();
 
-            if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) {
+            if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - (e?.clientX ?? 0)) > 3 || Math.abs(this._downY - (e?.clientY ?? 0)) > 3)) {
                 return;
             }
         }
@@ -587,7 +590,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, true)), icon: 'arrow-down' });
                 zorderItems.push({
                     description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
-                    event: undoBatch(action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged))),
+                    event: undoBatch(
+                        action(() => {
+                            this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged;
+                        })
+                    ),
                     icon: 'hand-point-up',
                 });
                 !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'layer-group' });
@@ -597,7 +604,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 const existingOnClick = cm.findByDescription('OnClick...');
                 const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
 
-                onClicks.push({ description: 'Enter Portal', event: undoable(e => DocUtils.makeIntoPortal(this.Document, this.layoutDoc, this._allLinks), 'make into portal'), icon: 'window-restore' });
+                onClicks.push({ description: 'Enter Portal', event: undoable(() => DocUtils.makeIntoPortal(this.Document, this.layoutDoc, this._allLinks), 'make into portal'), icon: 'window-restore' });
                 !Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
 
                 if (!this.Document.annotationOn) {
@@ -613,9 +620,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
             const funcs: ContextMenuProps[] = [];
             if (!Doc.noviceMode && this.layoutDoc.onDragStart) {
-                funcs.push({ description: 'Drag an Embedding', icon: 'edit', event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getEmbedding(this.dragFactory)')) });
-                funcs.push({ description: 'Drag a Copy', icon: 'edit', event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
-                funcs.push({ description: 'Drag Document', icon: 'edit', event: () => (this.layoutDoc.onDragStart = undefined) });
+                funcs.push({ description: 'Drag an Embedding', icon: 'edit', event: () => { this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getEmbedding(this.dragFactory)')); } }); // prettier-ignore
+                funcs.push({ description: 'Drag a Copy', icon: 'edit', event: () => { this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')); } }); // prettier-ignore
+                funcs.push({ description: 'Drag Document', icon: 'edit', event: () => { this.layoutDoc.onDragStart = undefined; } }); // prettier-ignore
                 cm.addItem({ description: 'OnDrag...', noexpand: true, subitems: funcs, icon: 'asterisk' });
             }
 
@@ -624,14 +631,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             if (!Doc.IsSystem(this.Document)) {
                 if (!Doc.noviceMode) {
                     moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' });
-                    moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => (this.Document._chromeHidden = !this.Document._chromeHidden), icon: 'project-diagram' });
+                    moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => { this.Document._chromeHidden = !this.Document._chromeHidden; }, icon: 'project-diagram' }); // prettier-ignore
 
                     if (Cast(Doc.GetProto(this.Document).data, listSpec(Doc))) {
                         moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.Document }).then(console.log), icon: 'caret-square-right' });
                         moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this.Document), icon: 'caret-square-right' });
                         moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this.Document), icon: 'caret-square-right' });
                     }
-                    moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' });
+                    moreItems.push({ description: 'Copy ID', event: () => ClientUtils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' });
                 }
             }
 
@@ -639,7 +646,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         }
         const constantItems: ContextMenuProps[] = [];
         if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) {
-            constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.Document) });
+            constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => DocUtils.Zip(this.Document) });
             (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._docView), icon: 'users' });
             if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) {
                 // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
@@ -655,8 +662,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.Document), icon: 'hand-point-right' });
         !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.dataDoc), icon: 'hand-point-right' });
 
-        let documentationDescription: string | undefined = undefined;
-        let documentationLink: string | undefined = undefined;
+        let documentationDescription: string | undefined;
+        let documentationLink: string | undefined;
         switch (this.Document.type) {
             case DocumentType.COL:
                 documentationDescription = 'See collection documentation';
@@ -690,6 +697,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 documentationDescription = 'See DataViz node documentation';
                 documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/dataViz/';
                 break;
+            default:
         }
         // Add link to help documentation (unless the doc contents have been overriden in which case the documentation isn't relevant)
         if (!this.docContents && documentationDescription && documentationLink) {
@@ -710,8 +718,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     panelHeight = () => this._props.PanelHeight() - this.headerMargin;
     screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler;
-    setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height));
-    setContentView = action((view: ViewBoxInterface) => (this._componentView = view));
+    setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height)); } // prettier-ignore
+    setContentView = action((view: ViewBoxInterface) => { this._componentView = view; }); // prettier-ignore
     isContentActive = (): boolean | undefined => this._isContentActive;
     childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
 
@@ -729,11 +737,13 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
                 return filtered.some(link => link._link_displayArrow) ? 0 : undefined;
             }
+            default:
         }
         return this._props.styleProvider?.(doc, props, property);
     };
 
-    removeLinkByHiding = (link: Doc) => () => (link.link_displayLine = false);
+    // eslint-disable-next-line no-return-assign
+    removeLinkByHiding = (link: Doc) => () => link.link_displayLine = false; // prettier-ignore
     @computed get allLinkEndpoints() {
         // the small blue dots that mark the endpoints of links
         if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
@@ -748,8 +758,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     PanelHeight={this.anchorPanelHeight}
                     dontRegisterView={false}
                     layout_showTitle={returnEmptyString}
-                    hideCaptions={true}
-                    hideLinkAnchors={true}
+                    hideCaptions
+                    hideLinkAnchors
                     layout_fitWidth={returnTrue}
                     removeDocument={this.removeLinkByHiding(link)}
                     styleProvider={this.anchorStyleProvider}
@@ -792,32 +802,30 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     }
 
     captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
-    fieldsDropdown = (placeholder: string) => {
-        return (
-            <div
-                ref={action((r: any) => r && (this._titleDropDownInnerWidth = DivWidth(r)))}
-                onPointerDown={action(e => (this._changingTitleField = true))}
-                style={{ width: 'max-content', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
-                <FieldsDropdown
-                    Document={this.Document}
-                    placeholder={placeholder}
-                    selectFunc={action((field: string | number) => {
-                        if (this.layoutDoc.layout_showTitle) {
-                            this.layoutDoc._layout_showTitle = field;
-                        } else if (!this._props.layout_showTitle) {
-                            Doc.UserDoc().layout_showTitle = field;
-                        }
-                        this._changingTitleField = false;
-                    })}
-                    menuClose={action(() => (this._changingTitleField = false))}
-                />
-            </div>
-        );
-    };
+    fieldsDropdown = (placeholder: string) => (
+        <div
+            ref={action((r: any) => { r && (this._titleDropDownInnerWidth = DivWidth(r));} )} // prettier-ignore
+            onPointerDown={action(() => { this._changingTitleField = true; })} // prettier-ignore
+            style={{ width: 'max-content', background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
+            <FieldsDropdown
+                Document={this.Document}
+                placeholder={placeholder}
+                selectFunc={action((field: string | number) => {
+                    if (this.layoutDoc.layout_showTitle) {
+                        this.layoutDoc._layout_showTitle = field;
+                    } else if (!this._props.layout_showTitle) {
+                        Doc.UserDoc().layout_showTitle = field;
+                    }
+                    this._changingTitleField = false;
+                })}
+                menuClose={action(() => { this._changingTitleField = false; })} // prettier-ignore
+            />
+        </div>
+    );
     /**
      * displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by
      * setting layout_showTitle using the format:   field1[:hover]
-     **/
+     * */
     @computed get titleView() {
         const showTitle = this.layout_showTitle?.split(':')[0];
         const showTitleHover = this.layout_showTitle?.includes(':hover');
@@ -825,7 +833,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.Document;
         const background = StrCast(
             this.layoutDoc.layout_headingColor,
-            StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor))
+            StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SnappingManager.userBackgroundColor))
         );
         const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0;
         const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
@@ -839,7 +847,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     position: this.headerMargin ? 'relative' : 'absolute',
                     height: this.titleHeight,
                     width: 100 - sidebarWidthPercent + '%',
-                    color: background === 'transparent' ? SettingsManager.userColor : lightOrDark(background),
+                    color: background === 'transparent' ? SnappingManager.userColor : lightOrDark(background),
                     background,
                     pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
                 }}>
@@ -860,11 +868,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                         contents={
                             showTitle
                                 .split(';')
-                                .map(field => Field.toJavascriptString(this.Document[field] as Field))
+                                .map(field => Field.toJavascriptString(this.Document[field] as FieldType))
                                 .join(' \\ ') || '-unset-'
                         }
                         display="block"
-                        oneLine={true}
+                        oneLine
                         fontSize={(this.titleHeight / 15) * 10}
                         GetValue={() =>
                             showTitle
@@ -905,10 +913,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     xPadding={10}
                     fieldKey={this.layout_showCaption}
                     styleProvider={this.captionStyleProvider}
-                    dontRegisterView={true}
+                    dontRegisterView
                     rootSelected={this.rootSelected}
-                    noSidebar={true}
-                    dontScale={true}
+                    noSidebar
+                    dontScale
                     renderDepth={this._props.renderDepth}
                     isContentActive={this.isContentActive}
                 />
@@ -952,8 +960,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
     render() {
         TraceMobx();
-        const highlighting = this.highlighting;
-        const borderPath = this.borderPath;
+        const { highlighting, borderPath } = this;
         const boxShadow = !highlighting
             ? this.boxShadow
             : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
@@ -968,23 +975,22 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         });
 
         return (
+            // eslint-disable-next-line jsx-a11y/click-events-have-key-events
             <div
                 className={`${DocumentView.ROOT_DIV} docView-hack`}
                 ref={this._mainCont}
                 onContextMenu={this.onContextMenu}
                 onPointerDown={this.onPointerDown}
                 onClick={this.onClick}
-                onPointerEnter={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
-                onPointerOver={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
+                onPointerEnter={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
+                onPointerOver={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
                 onPointerLeave={e => !isParentOf(this._contentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)}
                 style={{
                     borderRadius: this.borderRounding,
                     pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
                 }}>
-                <>
-                    {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
-                    {borderPath?.jsx}
-                </>
+                {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation])}
+                {borderPath?.jsx}
             </div>
         );
     }
@@ -994,7 +1000,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
      * @param presEffectDoc presentation effects document that specifies the animation effect parameters
      * @returns a function that will wrap a JSX animation element wrapping any JSX element
      */
-    public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) {
+    public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc> /* , root: Doc */) {
         const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection;
         const effectProps = {
             left: dir === PresEffectDirection.Left,
@@ -1005,10 +1011,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             delay: 0,
             duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)),
         };
-        //prettier-ignore
+        // prettier-ignore
         switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
-            default: 
-            case PresEffect.None:       return renderDoc;
             case PresEffect.Zoom:       return <Zoom {...effectProps}>{renderDoc}</Zoom>;
             case PresEffect.Fade:       return <Fade {...effectProps}>{renderDoc}</Fade>;
             case PresEffect.Flip:       return <Flip {...effectProps}>{renderDoc}</Flip>;
@@ -1016,17 +1020,19 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             case PresEffect.Bounce:     return <Bounce {...effectProps}>{renderDoc}</Bounce>;
             case PresEffect.Roll:       return <Roll {...effectProps}>{renderDoc}</Roll>;
             case PresEffect.Lightspeed: return <JackInTheBox {...effectProps}>{renderDoc}</JackInTheBox>;
+            case PresEffect.None:       
+            default:                    return renderDoc;
         }
     }
     public static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
         let gumStream: any;
         let recorder: any;
-        navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
+        navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
             let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
             if (audioTextAnnos) audioTextAnnos.push('');
             else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
             DictationManager.Controls.listen({
-                interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
+                interimHandler: value => { audioTextAnnos[audioTextAnnos.length - 1] = value; }, // prettier-ignore
                 continuous: { indefinite: false },
             }).then(results => {
                 if (results && [DictationManager.Controls.Infringed].includes(results)) {
@@ -1060,9 +1066,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 }
 
 @observer
-export class DocumentView extends DocComponent<DocumentViewProps>() {
+export class DocumentView extends DocComponent<DocumentViewProps & { CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView }>() {
     public static ROOT_DIV = 'documentView-effectsWrapper';
-    public get displayName() {  return 'DocumentView(' + this.Document?.title + ')';  } // prettier-ignore
+    public get displayName() {  return 'DocumentView(' + (this.Document?.title??"") + ')';  } // prettier-ignore
     public ContentRef = React.createRef<HTMLDivElement>();
     private _htmlOverlayEffect: Opt<Doc>;
     private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -1084,7 +1090,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
     }
 
-    constructor(props: DocumentViewProps) {
+    constructor(props: DocumentViewProps & { CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView }) {
         super(props);
         makeObservable(this);
     }
@@ -1161,7 +1167,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
     }
 
-    public set IsSelected(val)  { runInAction(() => (this._selected = val)); } // prettier-ignore
+    public set IsSelected(val)  { runInAction(() => { this._selected = val; }); } // prettier-ignore
     public get IsSelected()     { return this._selected; } // prettier-ignore
     public get topMost()        { return this._props.renderDepth === 0; } // prettier-ignore
     public get ContentDiv()     { return this._docViewInternal?._contentDiv; } // prettier-ignore
@@ -1176,7 +1182,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
     }
     @computed get anchorViewDoc() {
-        return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
+        return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document.link_anchor_2) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document.link_anchor_1) : undefined;
     }
 
     @computed get getBounds(): Opt<{ left: number; top: number; right: number; bottom: number; transition?: string }> {
@@ -1213,6 +1219,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     public get containerViewPath() { return this._props.containerViewPath; } // prettier-ignore
     public get CollectionFreeFormView() { return this.CollectionFreeFormDocumentView?.CollectionFreeFormView; } // prettier-ignore
     public get CollectionFreeFormDocumentView() { return this._props.CollectionFreeFormDocumentView?.(); } // prettier-ignore
+    public get LocalRotation() { return this._props.LocalRotation?.(); } // prettier-ignore
 
     public clearViewTransition = () => {
         this._viewTimer && clearTimeout(this._viewTimer);
@@ -1231,18 +1238,18 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     public iconify(finished?: () => void, animateTime?: number) {
         this.ComponentView?.updateIcon?.();
         const animTime = this._docViewInternal?.animateScaleTime();
-        runInAction(() => this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime));
+        runInAction(() => { this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime); }); // prettier-ignore
         const finalFinished = action(() => {
             finished?.();
             this._docViewInternal && (this._docViewInternal._animateScaleTime = animTime);
         });
-        const layout_fieldKey = Cast(this.Document.layout_fieldKey, 'string', null);
-        if (layout_fieldKey !== 'layout_icon') {
+        const layoutFieldKey = Cast(this.Document.layout_fieldKey, 'string', null);
+        if (layoutFieldKey !== 'layout_icon') {
             this.switchViews(true, 'icon', finalFinished);
-            if (layout_fieldKey && layout_fieldKey !== 'layout' && layout_fieldKey !== 'layout_icon') this.Document.deiconifyLayout = layout_fieldKey.replace('layout_', '');
+            if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') this.Document.deiconifyLayout = layoutFieldKey.replace('layout_', '');
         } else {
             const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
-            this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished, true);
+            this.switchViews(!!deiconifyLayout, deiconifyLayout, finalFinished, true);
             this.Document.deiconifyLayout = undefined;
             this._props.bringToFront?.(this.Document);
         }
@@ -1262,7 +1269,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                         autoplay: true,
                         loop: false,
                         volume: 0.5,
-                        onend: action(() => (self.dataDoc.audioAnnoState = AudioAnnoState.stopped)),
+                        onend: action(() => { self.dataDoc.audioAnnoState = AudioAnnoState.stopped; }), // prettier-ignore
                     });
                     this.dataDoc.audioAnnoState = AudioAnnoState.playing;
                     break;
@@ -1270,6 +1277,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                     this.dataDoc[AudioPlay]?.stop();
                     this.dataDoc.audioAnnoState = AudioAnnoState.stopped;
                     break;
+                default:
             }
         }
     };
@@ -1284,10 +1292,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
             this._docViewInternal._animateScaleTime = time;
         }
     });
-    public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
+    public setAnimEffect = (presEffect: Doc, timeInMs: number /* , afterTrans?: () => void */) => {
         this._animEffectTimer && clearTimeout(this._animEffectTimer);
         this.Document[Animation] = presEffect;
-        this._animEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs);
+        this._animEffectTimer = setTimeout(() => { this.Document[Animation] = undefined; }, timeInMs); // prettier-ignore
     };
     public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
         this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, afterTrans, dataTrans);
@@ -1304,7 +1312,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         }
         const view = SelectionManager.Views[0]?._props.renderDepth > 0 ? SelectionManager.Views[0] : undefined;
         undoable(() => {
-            var tempDoc: Opt<Doc>;
+            let tempDoc: Opt<Doc>;
             if (view) {
                 if (!view.layoutDoc.isTemplateDoc) {
                     tempDoc = view.Document;
@@ -1322,6 +1330,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
             }
             Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
         }, 'set default template')();
+        return undefined;
     }
 
     /**
@@ -1335,12 +1344,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         const curLayout = StrCast(this.Document.layout_fieldKey).replace('layout_', '').replace('layout', '');
         if (!this.Document.layout_default && curLayout !== detailLayoutKeySuffix) this.Document.layout_default = curLayout;
         const defaultLayout = StrCast(this.Document.layout_default);
-        if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true);
+        if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(!!defaultLayout, defaultLayout, undefined, true);
         else this.switchViews(true, detailLayoutKeySuffix, undefined, true);
     };
     public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
         const batch = UndoManager.StartBatch('switchView:' + view);
-        runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc
+        // shrink doc first..
+        runInAction(() => { this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1); }); // prettier-ignore
         setTimeout(
             action(() => {
                 if (useExistingLayout && custom && this.Document['layout_' + view]) {
@@ -1348,7 +1358,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                 } else {
                     this.setCustomView(custom, view);
                 }
-                this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // expand it
+                this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // now expand it
                 setTimeout(
                     action(() => {
                         this._docViewInternal && (this._docViewInternal._animateScalingTo = 0);
@@ -1366,7 +1376,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
      */
     public docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]);
 
-    layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
+    layout_fitWidthFunc = (/* doc: Doc */) => BoolCast(this.layout_fitWidth);
     screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale;
     isSelected = () => this.IsSelected;
     select = (extendSelection: boolean, focusSelection?: boolean) => {
@@ -1390,7 +1400,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     PanelWidth = () => this.panelWidth;
     PanelHeight = () => this.panelHeight;
     NativeDimScaling = () => this.nativeScaling;
-    hideLinkCount = () => (this.hideLinkButton ? true : false);
+    hideLinkCount = () => !!this.hideLinkButton;
     selfView = () => this;
     /**
      * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView)
@@ -1413,17 +1423,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                 ref={r => {
                     const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition
                     if (r && val !== this._enableHtmlOverlayTransitions) {
-                        setTimeout(action(() => (this._enableHtmlOverlayTransitions = val)));
+                        setTimeout(action(() => { this._enableHtmlOverlayTransitions = val; })); // prettier-ignore
                     }
                 }}
                 style={{ display: !this._htmlOverlayText ? 'none' : undefined }}>
                 <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this._enableHtmlOverlayTransitions ? 0.9 : 0 }}>
                     {DocumentViewInternal.AnimationEffect(
                         <div className="webBox-textHighlight">
-                            <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
+                            <ObserverJsxParser autoCloseVoidElements key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
                         </div>,
-                        { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc,
-                        this.Document
+                        { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc
                     )}
                 </div>
             </div>
@@ -1436,7 +1445,15 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
 
         return (
-            <div id={this.ViewGuid} className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
+            <div
+                id={this.ViewGuid}
+                className="contentFittingDocumentView"
+                onPointerEnter={action(() => {
+                    this._isHovering = true;
+                })}
+                onPointerLeave={action(() => {
+                    this._isHovering = false;
+                })}>
                 {!this.Document || !this._props.PanelWidth() ? null : (
                     <div
                         className="contentFittingDocumentView-previewDoc"
@@ -1462,14 +1479,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                             layout_fitWidth={this.layout_fitWidthFunc}
                             ScreenToLocalTransform={this.screenToContentsTransform}
                             focus={this._props.focus || emptyFunction}
-                            ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))}
+                            ref={action((r: DocumentViewInternal | null) => {
+                                r && (this._docViewInternal = r);
+                            })}
                         />
                         {this.htmlOverlay()}
                         {this.ComponentView?.infoUI?.()}
                     </div>
                 )}
                 {/* display link count button */}
-                <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />
+                <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover Bottom={this.topMost} ShowCount />
             </div>
         );
     }
@@ -1493,7 +1512,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
 
     // shows a stacking view collection (by default, but the user can change) of all documents linked to the source
     public static showBackLinks(linkAnchor: Doc) {
-        const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
+        const docId = ClientUtils.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
         // prettier-ignore
         DocServer.GetRefField(docId).then(docx =>
             LightboxView.Instance.SetLightboxDoc(
@@ -1504,19 +1523,27 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     }
 }
 
+export function returnEmptyDocViewList() {
+    return emptyPath;
+}
+
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
     documentView.iconify();
     documentView.select(false);
 });
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) {
-    LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0);
+    LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); // , 0);
 });
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
     dv.toggleDetail(detailLayoutKeySuffix);
 });
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
     const collectedLinks = DocListCast(linkCollection[DocData].data);
     let wid = NumCast(linkSource._width);
@@ -1534,9 +1561,10 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
             Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', embedding);
         }
     });
-    embedding && DocServer.UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise
+    embedding && UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise
     return links;
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
     const tag = StrCast(collection.title).split('-->')[1];
     const matchedTags = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, tag, false, ['tags']).keys());
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index a557cff4f..9be66ba4a 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -1,7 +1,7 @@
 import { action, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { DivHeight, DivWidth } from '../../../Utils';
+import { DivHeight, DivWidth } from '../../../ClientUtils';
 import { Id } from '../../../fields/FieldSymbols';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 771856788..14454ff61 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,3 +1,4 @@
+import { computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DateField } from '../../../fields/DateField';
@@ -5,13 +6,10 @@ import { Doc, Field, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { ScriptField } from '../../../fields/ScriptField';
 import { WebField } from '../../../fields/URLField';
-import { dropActionType } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { Transform } from '../../util/Transform';
-import { ViewBoxInterface } from '../DocComponent';
-import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
+import { PinProps, ViewBoxInterface } from '../DocComponent';
 import { DocumentView, OpenWhere } from './DocumentView';
-import { PinProps } from './trails';
-import { computed } from 'mobx';
 
 export interface FocusViewOptions {
     willPan?: boolean; // determines whether to pan to target document
@@ -56,7 +54,7 @@ export interface FieldViewSharedProps {
     disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
     hideClickBehaviors?: boolean; // whether to suppress menu item options for changing click behaviors
     ignoreUsePath?: boolean; // ignore the usePath field for selecting the fieldKey (eg., on text docs)
-    CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
+    LocalRotation?: () => number | undefined; // amount of rotation applied to freeformdocumentview containing document view
     containerViewPath?: () => DocumentView[];
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
     isGroupActive?: () => string | undefined; // is this document part of a group that is active
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 57ae92359..70fc63115 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -6,7 +6,8 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
 import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, Utils } from '../../../../Utils';
+import { ClientUtils, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
@@ -183,7 +184,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                     if (selected.length > 1) {
                         text = selected.length + ' selected';
                     } else {
-                        text = Utils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType);
+                        text = ClientUtils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType);
                         icon = Doc.toIcon(selected.lastElement());
                     }
                     return (
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index a86bdbd79..180c651fb 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -2,18 +2,18 @@ import functionPlot from 'function-plot';
 import { computed, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { DocListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { Cast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { DocUtils, Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
+import { LinkManager } from '../../util/LinkManager';
 import { undoBatch } from '../../util/UndoManager';
-import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent } from '../DocComponent';
 import { FieldView, FieldViewProps } from './FieldView';
-import { PinProps, PresBox } from './trails';
-import { LinkManager } from '../../util/LinkManager';
+import { PresBox } from './trails';
 
 @observer
 export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@@ -89,7 +89,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.docDragData?.droppedDocuments.length) {
             const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => {
-                ///const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc);
+                // const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc);
                 if (res) {
                     const link = DocUtils.MakeLink(doc, this.Document, { link_relationship: 'function', link_description: 'input' });
                     link && this._props.addDocument?.(link);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index bb1f70f97..231300a65 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,10 +1,11 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } 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';
+import { ClientUtils, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -14,7 +15,7 @@ import { ObjectField } from '../../../fields/ObjectField';
 import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
@@ -24,7 +25,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../../views/ContextMenu';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { OverlayView } from '../OverlayView';
 import { AnchorMenu } from '../pdf/AnchorMenu';
@@ -32,26 +33,29 @@ import { StyleProp } from '../StyleProvider';
 import { OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
 import './ImageBox.scss';
-import { PinProps, PresBox } from './trails';
+import { PresBox } from './trails';
 
 export class ImageEditorData {
+    // eslint-disable-next-line no-use-before-define
     private static _instance: ImageEditorData;
     private static get imageData() { return (ImageEditorData._instance ?? new ImageEditorData()).imageData; } // prettier-ignore
     @observable imageData: { rootDoc: Doc | undefined; open: boolean; source: string; addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean> } = observable({ rootDoc: undefined, open: false, source: '', addDoc: undefined });
-    @action private static set = (open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => (this._instance.imageData = { open, rootDoc, source, addDoc });
+    @action private static set = (open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => {
+        this._instance.imageData = { open, rootDoc, source, addDoc };
+    };
 
     constructor() {
         makeObservable(this);
         ImageEditorData._instance = this;
     }
 
-    public static get Open()    { return ImageEditorData.imageData.open;  } // prettier-ignore
-    public static get Source()  { return ImageEditorData.imageData.source; } // prettier-ignore
-    public static get RootDoc() { return ImageEditorData.imageData.rootDoc; } // prettier-ignore
-    public static get AddDoc()  { return ImageEditorData.imageData.addDoc; } // prettier-ignore
+    public static get Open()                     { return ImageEditorData.imageData.open;  } // prettier-ignore
     public static set Open(open: boolean)        { ImageEditorData.set(open, this.imageData.rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore
+    public static get Source()                   { return ImageEditorData.imageData.source; } // prettier-ignore
     public static set Source(source: string)     { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, source, this.imageData.addDoc); } // prettier-ignore
+    public static get RootDoc()                  { return ImageEditorData.imageData.rootDoc; } // prettier-ignore
     public static set RootDoc(rootDoc: Opt<Doc>) { ImageEditorData.set(this.imageData.open, rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore
+    public static get AddDoc()                   { return ImageEditorData.imageData.addDoc; } // prettier-ignore
     public static set AddDoc(addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, this.imageData.source, addDoc); } // prettier-ignore
 }
 @observer
@@ -93,7 +97,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         if (anchor) {
             if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
             addAsAnnotation && this.addDocument(anchor);
-            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: visibleAnchor ? false : true } }, this.Document);
+            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !visibleAnchor } }, this.Document);
             return anchor;
         }
         return this.Document;
@@ -106,10 +110,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                 scrSize: (this.ScreenToLocalBoxXf().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 1),
                 selected: this._props.isSelected(),
             }),
-            ({ forceFull, scrSize, selected }) => (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'),
+            ({ forceFull, scrSize, selected }) => {
+                this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o';
+            },
             { fireImmediately: true, delay: 1000 }
         );
-        const layoutDoc = this.layoutDoc;
+        const { layoutDoc } = this;
         this._disposers.path = reaction(
             () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }),
             ({ nativeSize, width }) => {
@@ -121,10 +127,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         );
         this._disposers.scroll = reaction(
             () => this.layoutDoc.layout_scrollTop,
-            s_top => {
+            sTop => {
                 this._forcedScroll = true;
-                !this._ignoreScroll && this._mainCont.current && (this._mainCont.current.scrollTop = NumCast(s_top));
-                this._mainCont.current?.scrollTo({ top: NumCast(s_top) });
+                !this._ignoreScroll && this._mainCont.current && (this._mainCont.current.scrollTop = NumCast(sTop));
+                this._mainCont.current?.scrollTo({ top: NumCast(sTop) });
                 this._forcedScroll = false;
             },
             { fireImmediately: true }
@@ -138,7 +144,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     @undoBatch
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.docDragData) {
-            let added: boolean | undefined = undefined;
+            let added: boolean | undefined;
             const targetIsBullseye = (ele: HTMLElement): boolean => {
                 if (!ele) return false;
                 if (ele === this._overlayIconRef.current) return true;
@@ -168,7 +174,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     };
 
     @undoBatch
-    resolution = () => (this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes);
+    resolution = () => {
+        this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes;
+    };
 
     @undoBatch
     setNativeSize = action(() => {
@@ -189,7 +197,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         const nh = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']);
         const w = this.layoutDoc._width;
         const h = this.layoutDoc._height;
-        this.dataDoc[this.fieldKey + '-rotation'] = (NumCast(this.dataDoc[this.fieldKey + '-rotation']) + 90) % 360;
+        this.dataDoc[this.fieldKey + '_rotation'] = (NumCast(this.dataDoc[this.fieldKey + '_rotation']) + 90) % 360;
         this.dataDoc[this.fieldKey + '_nativeWidth'] = nh;
         this.dataDoc[this.fieldKey + '_nativeHeight'] = nw;
         this.layoutDoc._width = h;
@@ -197,7 +205,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     });
 
     crop = (region: Doc | undefined, addCrop?: boolean) => {
-        if (!region) return;
+        if (!region) return undefined;
         const cropping = Doc.MakeCopy(region, true);
         const regionData = region[DocData];
         regionData.lockedPosition = true;
@@ -223,8 +231,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         croppingProto.type = DocumentType.IMG;
         croppingProto.layout = ImageBox.LayoutString('data');
         croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
-        croppingProto['data_nativeWidth'] = anchw;
-        croppingProto['data_nativeHeight'] = anchh;
+        croppingProto.data_nativeWidth = anchw;
+        croppingProto.data_nativeHeight = anchh;
         croppingProto.freeform_scale = viewScale;
         croppingProto.freeform_scale_min = viewScale;
         croppingProto.freeform_panX = anchx / viewScale;
@@ -244,14 +252,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         return cropping;
     };
 
-    specificContextMenu = (e: React.MouseEvent): void => {
+    specificContextMenu = (): void => {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField);
         if (field) {
             const funcs: ContextMenuProps[] = [];
             funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' });
             funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' });
             funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' });
-            funcs.push({ description: 'Copy path', event: () => Utils.CopyText(this.choosePath(field.url)), icon: 'copy' });
+            funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' });
             funcs.push({
                 description: 'Open Image Editor',
                 event: action(() => {
@@ -270,7 +278,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         if (!url?.href) return '';
         const lower = url.href.toLowerCase();
         if (url.protocol === 'data') return url.href;
-        if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href);
+        if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return ClientUtils.CorsProxy(url.href);
         if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`;
 
         const ext = extname(url.href);
@@ -282,7 +290,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         TraceMobx();
         const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500));
         const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500));
-        const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '-nativeOrientation'], 1);
+        const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1);
         return { nativeWidth, nativeHeight, nativeOrientation };
     }
     @computed get overlayImageIcon() {
@@ -307,7 +315,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                 <div
                     className="imageBox-alternateDropTarget"
                     ref={this._overlayIconRef}
-                    onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
+                    onPointerDown={e =>
+                        setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => {
+                            this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
+                        })
+                    }
                     style={{
                         display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || this.dataDoc[this.fieldKey + '_alternates'] ? 'block' : 'none',
                         width: 'min(10%, 25px)',
@@ -324,7 +336,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     @computed get paths() {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
         const alts = this.dataDoc[this.fieldKey + '_alternates'] as any as List<Doc>; // retrieve alternate documents that may be rendered as alternate images
-        const defaultUrl = new URL(Utils.prepend('/assets/unknown-file-icon-hi.png'));
+        const defaultUrl = new URL(ClientUtils.prepend('/assets/unknown-file-icon-hi.png'));
         const altpaths =
             alts
                 ?.map(doc => (doc instanceof Doc ? ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl : defaultUrl))
@@ -344,8 +356,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1;
         const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
         const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
-        const { nativeWidth, nativeHeight, nativeOrientation } = this.nativeSize;
-        const rotation = NumCast(this.dataDoc[this.fieldKey + '-rotation']);
+        const { nativeWidth, nativeHeight /* , nativeOrientation */ } = this.nativeSize;
+        const rotation = NumCast(this.dataDoc[this.fieldKey + '_rotation']);
         const aspect = rotation % 180 ? nativeHeight / nativeWidth : 1;
         let transformOrigin = 'center center';
         let transform = `translate(0%, 0%) rotate(${rotation}deg) scale(${aspect})`;
@@ -361,12 +373,32 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`];
 
         return (
-            <div className="imageBox-cont" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))} key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}>
+            <div
+                className="imageBox-cont"
+                onPointerEnter={action(() => {
+                    this._isHovering = true;
+                })}
+                onPointerLeave={action(() => {
+                    this._isHovering = false;
+                })}
+                key={this.layoutDoc[Id]}
+                ref={this.createDropTarget}
+                onPointerDown={this.marqueeDown}>
                 <div className="imageBox-fader" style={{ opacity: backAlpha }}>
-                    <img key="paths" src={srcpath} style={{ transform, transformOrigin }} onError={action(e => (this._error = e.toString()))} draggable={false} width={nativeWidth} />
+                    <img
+                        alt=""
+                        key="paths"
+                        src={srcpath}
+                        style={{ transform, transformOrigin }}
+                        onError={action(e => {
+                            this._error = e.toString();
+                        })}
+                        draggable={false}
+                        width={nativeWidth}
+                    />
                     {fadepath === srcpath ? null : (
                         <div className={`imageBox-fadeBlocker${(this._isHovering && usePath === 'alternate:hover') || usePath === 'alternate' ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}>
-                            <img className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
+                            <img alt="" className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
                         </div>
                     )}
                 </div>
@@ -384,8 +416,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     }
     screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
     marqueeDown = (e: React.PointerEvent) => {
-        if (!this.dataDoc[this.fieldKey]) return this.chooseImage();
-        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!this.dataDoc[this.fieldKey]) {
+            this.chooseImage();
+        } else if (
+            !e.altKey &&
+            e.button === 0 &&
+            NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) &&
+            this._props.isContentActive() &&
+            ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)
+        ) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -419,7 +458,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                 className="imageBox"
                 onContextMenu={this.specificContextMenu}
                 ref={this._mainCont}
-                onScroll={action(e => {
+                onScroll={action(() => {
                     if (!this._forcedScroll) {
                         if (this.layoutDoc._layout_scrollTop || this._mainCont.current?.scrollTop) {
                             this._ignoreScroll = true;
@@ -444,8 +483,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                     renderDepth={this._props.renderDepth + 1}
                     fieldKey={this.annotationKey}
                     styleProvider={this._props.styleProvider}
-                    isAnnotationOverlay={true}
-                    annotationLayerHostsContent={true}
+                    isAnnotationOverlay
+                    annotationLayerHostsContent
                     PanelWidth={this._props.PanelWidth}
                     PanelHeight={this._props.PanelHeight}
                     ScreenToLocalTransform={this.screenToLocalTransform}
@@ -476,7 +515,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                         selectionText={returnEmptyString}
                         annotationLayer={this._annotationLayer.current}
                         marqueeContainer={this._mainCont.current}
-                        highlightDragSrcColor={''}
+                        highlightDragSrcColor=""
                         anchorMenuCrop={this.crop}
                     />
                 )}
@@ -489,7 +528,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         input.type = 'file';
         input.multiple = true;
         input.accept = 'image/*';
-        input.onchange = async _e => {
+        input.onchange = async () => {
             const file = input.files?.[0];
             if (file) {
                 const disposer = OverlayView.ShowSpinner();
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 31a2367fc..74773b244 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,8 +1,8 @@
 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 { returnAlways, returnTrue } from '../../../ClientUtils';
+import { Doc, Field, FieldType, FieldResult } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
@@ -96,15 +96,15 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
 
     public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) {
         const { script, type, onDelegate } = kvpScript;
-        //const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
+        // const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
         const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : DocCast(doc.proto, doc);
-        let field: Field | undefined;
+        let field: FieldType | undefined;
         switch (type) {
             case 'computed': field = new ComputedField(script); break; // prettier-ignore
             case 'script': field = new ScriptField(script); break; // prettier-ignore
             default: {
                 const _setCacheResult_ = (value: FieldResult) => {
-                    field = value as Field;
+                    field = value as FieldType;
                     if (setResult) setResult?.(value);
                     else target[key] = field;
                 };
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index f9e8ce4f3..c3afc198d 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -2,7 +2,8 @@ import { Tooltip } from '@mui/material';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
 import { Doc, Field } from '../../../fields/Doc';
 import { DocCast } from '../../../fields/Types';
 import { DocumentOptions, FInfo } from '../../documents/Documents';
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 74e78c671..d1c8c62ed 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -1,21 +1,21 @@
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, Field } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxBaseComponent } from '../DocComponent';
+import { PinProps, ViewBoxBaseComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import BigText from './LabelBigText';
 import './LabelBox.scss';
-import { PinProps, PresBox } from './trails';
-import { Docs } from '../../documents/Documents';
+import { PresBox } from './trails';
 
 @observer
 export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get Title() {
-        return Field.toString(this.dataDoc[this.fieldKey] as Field) || StrCast(this.Document.title);
+        return Field.toString(this.dataDoc[this.fieldKey] as FieldType) || StrCast(this.Document.title);
     }
 
     protected createDropTarget = (ele: HTMLDivElement) => {
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index ff1e62885..0155defb7 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,11 +1,13 @@
 import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { setupMoveUpEvents } from '../../../ClientUtils';
+import { Utils, emptyFunction } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { DragManager, dropActionType } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { LinkFollower } from '../../util/LinkFollower';
 import { SelectionManager } from '../../util/SelectionManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
@@ -39,7 +41,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     onPointerDown = (e: React.PointerEvent) => {
-        const linkSource = this.linkSource;
+        const { linkSource } = this;
         linkSource &&
             setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
                 if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false);
@@ -58,11 +60,10 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 dragData.dropPropertiesToRemove = ['link_anchor_1_x', 'link_anchor_1_y', 'link_anchor_2_x', 'link_anchor_2_y', 'onClick'];
                 DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]);
                 return true;
-            } else {
-                this.layoutDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100;
-                this.layoutDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100;
-                this.layoutDoc.link_autoMoveAnchors = false;
             }
+            this.layoutDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100;
+            this.layoutDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100;
+            this.layoutDoc.link_autoMoveAnchors = false;
         }
         return false;
     });
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 3a2509c3d..2593491cc 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,12 +2,13 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import Xarrow from 'react-xarrows';
+import { DashColor, lightOrDark, returnFalse } from '../../../ClientUtils';
 import { FieldResult } from '../../../fields/Doc';
 import { DocCss, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
 import { DocumentManager } from '../../util/DocumentManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index c9c8f9260..539daf0bd 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -4,7 +4,8 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx'
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import wiki from 'wikijs';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
 import { DocServer } from '../../DocServer';
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
index 7e99795b5..fe7f8d8b3 100644
--- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -4,7 +4,8 @@ import { IconButton } from 'browndash-components';
 import { IReactionDisposer, ObservableMap, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { returnFalse, unimplementedFunction } from '../../../../Utils';
+import { returnFalse } from '../../../../ClientUtils';
+import { unimplementedFunction } from '../../../../Utils';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { SelectionManager } from '../../../util/SelectionManager';
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index 08bea5d9d..d17c4298c 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -7,7 +7,8 @@ import { IReactionDisposer, ObservableMap, action, makeObservable, observable, r
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { CirclePicker, ColorResult } from 'react-color';
-import { returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../../Utils';
+import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
+import { unimplementedFunction } from '../../../../Utils';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { CalendarManager } from '../../../util/CalendarManager';
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index b73898f59..7855f8fe8 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -12,7 +12,8 @@ import * as React from 'react';
 import { CirclePicker, ColorResult } from 'react-color';
 import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl';
 import { MarkerEvent } from 'react-map-gl/dist/esm/types';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
+import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
 import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { DocumentType } from '../../../documents/DocumentTypes';
@@ -22,14 +23,14 @@ import { DragManager } from '../../../util/DragManager';
 import { LinkManager } from '../../../util/LinkManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
-import { PinProps, PresBox } from '../trails';
+import { PresBox } from '../trails';
 import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
 import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility';
 import { MapAnchorMenu } from './MapAnchorMenu';
@@ -401,8 +402,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
     scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
-    transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
-    opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
+    transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this._props.childFilters(), ClientUtils.OpaqueBackgroundFilter];
     infoWidth = () => this._props.PanelWidth() / 5;
     infoHeight = () => this._props.PanelHeight() / 5;
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 3eb051dbf..e857ef722 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -4,9 +4,11 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { MapProvider, Map as MapboxMap } from 'react-map-gl';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
-import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
+import { Doc, DocListCast, LinkedTo, Opt } from '../../../../fields/Doc';
 import { DocCss, Highlight } from '../../../../fields/DocSymbols';
+import { Id } from '../../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { DocUtils, Docs } from '../../../documents/Documents';
@@ -16,15 +18,15 @@ import { LinkManager } from '../../../util/LinkManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent } from '../../DocComponent';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
 import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
-import { PinProps, PresBox } from '../trails';
+import { PresBox } from '../trails';
 import './MapBox.scss';
 
 /**
@@ -268,7 +270,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
     sidebarDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
     };
-    sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
+    sidebarMove = (e: PointerEvent) => {
         const bounds = this._ref.current!.getBoundingClientRect();
         this.layoutDoc._layout_sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%';
         this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%';
@@ -276,7 +278,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         return false;
     };
 
-    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
+    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => {
+        this._setPreviewCursor = func;
+    };
 
     addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
 
@@ -285,8 +289,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
     scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
-    transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
-    opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
+    transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this._props.childFilters(), ClientUtils.OpaqueBackgroundFilter];
     infoWidth = () => this._props.PanelWidth() / 5;
     infoHeight = () => this._props.PanelHeight() / 5;
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
@@ -306,11 +310,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
     //        center: new this.MicrosoftMaps.Location(loc.latitude, loc.longitude),
     //     });
     //
-    bingGeocode = (map: any, query: string) => {
-        return new Promise<{ latitude: number; longitude: number }>((res, reject) => {
-            //If search manager is not defined, load the search module.
+    bingGeocode = (map: any, query: string) =>
+        new Promise<{ latitude: number; longitude: number }>((res, reject) => {
+            // If search manager is not defined, load the search module.
             if (!this._bingSearchManager) {
-                //Create an instance of the search manager and call the geocodeQuery function again.
+                // Create an instance of the search manager and call the geocodeQuery function again.
                 this.MicrosoftMaps.loadModule('Microsoft.Maps.Search', () => {
                     this._bingSearchManager = new this.MicrosoftMaps.Search.SearchManager(map.current);
                     res(this.bingGeocode(map, query));
@@ -319,11 +323,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                 this._bingSearchManager.geocode({
                     where: query,
                     callback: action((r: any) => res(r.results[0].location)),
-                    errorCallback: (e: any) => reject(),
+                    errorCallback: () => reject(),
                 });
             }
         });
-    };
 
     @observable
     bingSearchBarContents: any = this.Document.map; // For Bing Maps: The contents of the Bing search bar (string)
@@ -368,7 +371,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                 this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp));
             }
             const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude));
-            this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(temp as Doc));
+            this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(temp as Doc));
             if (!this._unmounting) {
                 this._bingMap.current.entities.push(newpin);
             }
@@ -383,7 +386,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
             this.toggleSidebar();
             options.didMove = true;
         }
-        return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+        return new Promise<Opt<DocumentView>>(res => {
+            DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+        });
     };
     /*
      * Pushpin onclick
@@ -418,7 +423,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
      * Map OnClick
      */
     @action
-    mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
+    mapOnClick = (/* e: { location: { latitude: any; longitude: any } } */) => {
         this._props.select(false);
         this.deselectPin();
     };
@@ -442,22 +447,23 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
      * Updates maptype
      */
     @action
-    updateMapType = () => (this.dataDoc.map_type = this._bingMap.current.getMapTypeId());
+    updateMapType = () => {
+        this.dataDoc.map_type = this._bingMap.current.getMapTypeId();
+    };
 
     /*
      * For Bing Maps
      * Called by search button's onClick
      * Finds the geocode of the searched contents and sets location to that location
-     **/
+     * */
     @action
-    bingSearch = () => {
-        return this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => {
+    bingSearch = () =>
+        this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => {
             this.dataDoc.latitude = location.latitude;
             this.dataDoc.longitude = location.longitude;
             this.dataDoc.map_zoom = this._bingMap.current.getZoom();
             this.dataDoc.map = this.bingSearchBarContents;
         });
-    };
 
     /*
      * Returns doc w/ relevant info
@@ -502,7 +508,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
 
         this._bingMap.current.entities.push(pushPin);
 
-        this.MicrosoftMaps.Events.addHandler(pushPin, 'click', (e: any) => this.pushpinClicked(pin));
+        this.MicrosoftMaps.Events.addHandler(pushPin, 'click', () => this.pushpinClicked(pin));
         // this.MicrosoftMaps.Events.addHandler(pushPin, 'dblclick', (e: any) => this.pushpinDblClicked(pushPin, pin));
         this.map_docToPinMap.set(pin, pushPin);
     };
@@ -591,7 +597,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
     };
 
     @action
-    searchbarOnEdit = (newText: string) => (this.bingSearchBarContents = newText);
+    searchbarOnEdit = (newText: string) => {
+        this.bingSearchBarContents = newText;
+    };
 
     recolorPin = (pin: Doc, color?: string) => {
         this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
@@ -620,7 +628,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
 
         this._disposers.mapLocation = reaction(
             () => this.Document.map,
-            mapLoc => (this.bingSearchBarContents = mapLoc),
+            mapLoc => {
+                this.bingSearchBarContents = mapLoc;
+            },
             { fireImmediately: true }
         );
         this._disposers.highlight = reaction(
@@ -720,6 +730,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                         MapBoxContainer._rerenderDelay = 0;
                     }
                     this._rerenderTimeout = undefined;
+                    // eslint-disable-next-line operator-assignment
                     this.Document[DocCss] = this.Document[DocCss] + 1;
                 }), MapBoxContainer._rerenderDelay);
             return null;
@@ -752,18 +763,19 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                                 <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF">
                                     <path
                                         fill="currentColor"
-                                        d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path>
+                                        d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"
+                                    />
                                 </svg>
                             }
                             onClick={this.bingSearch}
                             type={Type.TERT}
                         />
                         <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
-                            <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
+                            <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size="lg" icon="bullseye" />} />
                         </div>
                     </div>
                     <MapProvider>
-                        <MapboxMap id="mabox-map" mapStyle={`mapbox://styles/mapbox/streets-v9`} mapboxAccessToken={mapboxApiKey} />
+                        <MapboxMap id="mabox-map" mapStyle="mapbox://styles/mapbox/streets-v9" mapboxAccessToken={mapboxApiKey} />
                     </MapProvider>
 
                     {/* 
@@ -782,7 +794,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                                   .filter(anno => !anno.layout_unrendered)
                                   .map((pushpin, i) => (
                                       <DocumentView
-                                          key={i}
+                                          key={pushpin[Id]}
+                                          // eslint-disable-next-line react/jsx-props-no-spreading
                                           {...this._props}
                                           renderDepth={this._props.renderDepth + 1}
                                           Document={pushpin}
@@ -821,12 +834,13 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                 <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
                     <SidebarAnnos
                         ref={this._sidebarRef}
+                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
                         fieldKey={this.fieldKey}
                         Document={this.Document}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
-                        usePanelWidth={true}
+                        usePanelWidth
                         showSidebar={this.SidebarShown}
                         nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 1274220b6..8140c0ca7 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -4,6 +4,7 @@ import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
 import * as React from 'react';
+import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -12,7 +13,7 @@ import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField, PdfField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -23,16 +24,16 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { CollectionStackingView } from '../collections/CollectionStackingView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { Colors } from '../global/globalEnums';
 import { CreateImage } from '../nodes/WebBoxRenderer';
 import { PDFViewer } from '../pdf/PDFViewer';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { DocumentView, OpenWhere } from './DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
 import { ImageBox } from './ImageBox';
 import './PDFBox.scss';
-import { PinProps, PresBox } from './trails';
+import { PresBox } from './trails';
 
 @observer
 export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -128,9 +129,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
         croppingProto.type = DocumentType.IMG;
         croppingProto.layout = ImageBox.LayoutString('data');
-        croppingProto.data = new ImageField(Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png'));
-        croppingProto['data_nativeWidth'] = anchw;
-        croppingProto['data_nativeHeight'] = anchh;
+        croppingProto.data = new ImageField(ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png'));
+        croppingProto.data_nativeWidth = anchw;
+        croppingProto.data_nativeHeight = anchh;
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
         }
@@ -146,8 +147,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             (NumCast(region.x) * this._props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
             4
         )
-            .then((data_url: any) => {
-                Utils.convertDataUri(data_url, region[Id]).then(returnedfilename =>
+            .then((dataUrl: any) => {
+                ClientUtils.convertDataUri(dataUrl, region[Id]).then(returnedfilename =>
                     setTimeout(
                         action(() => {
                             croppingProto.data = new ImageField(returnedfilename);
@@ -182,8 +183,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 (iconFile: string, nativeWidth: number, nativeHeight: number) => {
                     setTimeout(() => {
                         this.dataDoc.icon = new ImageField(iconFile);
-                        this.dataDoc['icon_nativeWidth'] = nativeWidth;
-                        this.dataDoc['icon_nativeHeight'] = nativeHeight;
+                        this.dataDoc.icon_nativeWidth = nativeWidth;
+                        this.dataDoc.icon_nativeHeight = nativeHeight;
                     }, 500);
                 }
             );
@@ -231,11 +232,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             options.didMove = true;
             this.toggleSidebar(false);
         }
-        return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
+        return new Promise<Opt<DocumentView>>(res => {
+            DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+        });
     };
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
-        let ele: Opt<HTMLDivElement> = undefined;
+        let ele: Opt<HTMLDivElement>;
         if (this._pdfViewer?.selectionContent()) {
             ele = document.createElement('div');
             ele.append(this._pdfViewer.selectionContent()!);
@@ -451,7 +454,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'asterisk' });
         const help = cm.findByDescription('Help...');
         const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
-        helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
+        helpItems.push({ description: 'Copy path', event: () => this.pdfUrl && ClientUtils.CopyText(ClientUtils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
         !help && ContextMenu.Instance.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'asterisk' });
     };
 
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 1f976f926..40199cce1 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -11,17 +11,18 @@ import { Upload } from '../../../../server/SharedMediaTypes';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
 import { Presentation } from '../../../util/TrackMovements';
 import { undoBatch } from '../../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
-import { media_state } from '../AudioBox';
+import { mediaState } from '../AudioBox';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { VideoBox } from '../VideoBox';
 import { RecordingView } from './RecordingView';
 import { DocData } from '../../../../fields/DocSymbols';
+import { dropActionType } from '../../../util/DropActionTypes';
 
 @observer
 export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -78,7 +79,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
             }, 100);
             //could break if recording takes too long to turn into videobox. If so, either increase time on setTimeout below or find diff place to do this
             setTimeout(() => Doc.RemFromMyOverlay(remDoc), 1000);
-            Doc.UserDoc().workspaceRecordingState = media_state.Paused;
+            Doc.UserDoc().workspaceRecordingState = mediaState.Paused;
             Doc.AddDocToList(Doc.UserDoc(), 'workspaceRecordings', remDoc);
         }
     }
@@ -110,7 +111,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
             RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
             RecordingBox.screengrabber.Record?.();
         });
-        Doc.UserDoc().workspaceRecordingState = media_state.Recording;
+        Doc.UserDoc().workspaceRecordingState = mediaState.Recording;
     }
 
     /**
@@ -150,7 +151,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (docView?.ComponentView instanceof VideoBox) {
             docView.ComponentView.Play();
         }
-        Doc.UserDoc().workspaceReplayingState = media_state.Playing;
+        Doc.UserDoc().workspaceReplayingState = mediaState.Playing;
     }
 
     public static pauseWorkspaceReplaying(doc: Doc) {
@@ -159,7 +160,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (videoBox) {
             videoBox.Pause();
         }
-        Doc.UserDoc().workspaceReplayingState = media_state.Paused;
+        Doc.UserDoc().workspaceReplayingState = mediaState.Paused;
     }
 
     public static stopWorkspaceReplaying(value: Doc) {
@@ -224,7 +225,7 @@ ScriptingGlobals.add(function getWorkspaceRecordings() {
     return new List<any>(['Record Workspace', `Record Webcam`, ...DocListCast(Doc.UserDoc().workspaceRecordings)]);
 });
 ScriptingGlobals.add(function isWorkspaceRecording() {
-    return Doc.UserDoc().workspaceRecordingState === media_state.Recording;
+    return Doc.UserDoc().workspaceRecordingState === mediaState.Recording;
 });
 ScriptingGlobals.add(function isWorkspaceReplaying() {
     return Doc.UserDoc().workspaceReplayingState;
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index f7ed82643..9c05a3e94 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -4,7 +4,7 @@ import { IconContext } from 'react-icons';
 import { FaCheckCircle } from 'react-icons/fa';
 import { MdBackspace } from 'react-icons/md';
 import { Upload } from '../../../../server/SharedMediaTypes';
-import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Networking } from '../../../Network';
 import { Presentation, TrackMovements } from '../../../util/TrackMovements';
 import { ProgressBar } from './ProgressBar';
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 1e3933ac3..e29e47514 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -4,30 +4,31 @@ import * as React from 'react';
 import { computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 // import { BufferAttribute, Camera, Vector2, Vector3 } from 'three';
+import { returnFalse, returnOne, returnZero } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
 import { DateField } from '../../../fields/DateField';
 import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DocCast, NumCast } from '../../../fields/Types';
 import { AudioField, VideoField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
-import { DocUtils } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/Documents';
 import { CaptureManager } from '../../util/CaptureManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { TrackMovements } from '../../util/TrackMovements';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
-import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
-import { media_state } from './AudioBox';
+import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { mediaState } from './AudioBox';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import './ScreenshotBox.scss';
 import { VideoBox } from './VideoBox';
-import { DocData } from '../../../fields/DocSymbols';
 
 declare class MediaRecorder {
     constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -175,7 +176,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 ref={r => {
                     this._videoRef = r;
                     setTimeout(() => {
-                        if (this.layoutDoc.mediaState === media_state.PendingRecording && this._videoRef) {
+                        if (this.layoutDoc.mediaState === mediaState.PendingRecording && this._videoRef) {
                             this.toggleRecording();
                         }
                     }, 100);
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 8c65fd34e..60d7e4b00 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -2,7 +2,7 @@ let ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { returnAlways, returnEmptyString } from '../../../Utils';
+import { returnAlways, returnEmptyString } from '../../../ClientUtils';
 import { Doc } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 4773a21c9..60141b2a6 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -3,6 +3,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, Observ
 import { observer } from 'mobx-react';
 import { basename } from 'path';
 import * as React from 'react';
+import { ClientUtils, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
 import { Doc, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -10,11 +11,11 @@ import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction, formatTime } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
-import { dropActionType } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
 import { FollowLinkScript } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { ReplayMovements } from '../../util/ReplayMovements';
@@ -23,14 +24,14 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { StyleProp } from '../StyleProvider';
 import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
 import { RecordingBox } from './RecordingBox';
-import { PinProps, PresBox } from './trails';
+import { PresBox } from './trails';
 import './VideoBox.scss';
 
 /**
@@ -303,7 +304,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
             const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[\.\/\?\=]/g, '_'));
             const filename = basename(encodedFilename);
-            Utils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
+            ClientUtils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
         }
     };
 
@@ -318,7 +319,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
 
     // creates link for snapshot
     createSnapshotLink = (imagePath: string, downX?: number, downY?: number) => {
-        const url = !imagePath.startsWith('/') ? Utils.CorsProxy(imagePath) : imagePath;
+        const url = !imagePath.startsWith('/') ? ClientUtils.CorsProxy(imagePath) : imagePath;
         const width = NumCast(this.layoutDoc._width) || 1;
         const height = NumCast(this.layoutDoc._height);
         const imageSnapshot = Docs.Create.ImageDocument(url, {
@@ -399,7 +400,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             canvas.getContext('2d')?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 100, 100);
             const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
             const encodedFilename = encodeURIComponent('thumbnail' + retitled + '_' + video.currentTime.toString().replace(/\./, '_'));
-            thumbnailPromises?.push(Utils.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true));
+            thumbnailPromises?.push(ClientUtils.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true));
             const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1);
             if (newTime < video.duration) {
                 video.currentTime = newTime;
@@ -480,7 +481,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             subitems.push({
                 description: 'Copy path',
                 event: () => {
-                    Utils.CopyText(url);
+                    ClientUtils.CopyText(url);
                 },
                 icon: 'expand-arrows-alt',
             });
@@ -644,7 +645,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => {
         clearTimeout(this._playRegionTimer);
         this._playRegionTimer = undefined;
-        if (Number.isNaN(this.player?.duration)) {
+        if (isNaN(this.player?.duration)) {
             setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
         } else if (this.player) {
             // trimBounds override requested playback bounds
@@ -806,7 +807,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
 
     marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0];
 
-    timelineDocFilter = () => [`_isTimelineLabel:true,${Utils.noRecursionHack}:x`];
+    timelineDocFilter = () => [`_isTimelineLabel:true,${ClientUtils.noRecursionHack}:x`];
 
     // renders video controls
     componentUI = (boundsLeft: number, boundsTop: number) => {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 033b01d24..446e83dd3 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -4,7 +4,8 @@ import { action, computed, IReactionDisposer, makeObservable, observable, Observ
 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 { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivHeight, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils';
+import { Doc, DocListCast, Field, FieldType, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { HtmlField } from '../../../fields/HtmlField';
 import { InkTool } from '../../../fields/InkField';
@@ -14,7 +15,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, DivHeight, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, stringHash, Utils } from '../../../Utils';
+import { emptyFunction, stringHash, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -24,7 +25,7 @@ import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { Colors } from '../global/globalEnums';
 import { LightboxView } from '../LightboxView';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
@@ -36,7 +37,7 @@ import { StyleProp } from '../StyleProvider';
 import { DocumentView, OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
 import { LinkInfo } from './LinkDocPreview';
-import { PinProps, PresBox } from './trails';
+import { PresBox } from './trails';
 import './WebBox.scss';
 const { CreateImage } = require('./WebBoxRenderer');
 const _global = (window /* browser */ || global) /* node */ as any;
@@ -143,7 +144,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         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();
+            htmlString = await (await fetch(ClientUtils.CorsProxy(this.webField!.href))).text();
         }
         this.layoutDoc.thumb = undefined;
         this.Document.thumbLockout = true; // lock to prevent multiple thumb updates.
@@ -219,7 +220,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 }
             } // else it's an HTMLfield
         } else if (this.webField && !this.dataDoc.text) {
-            WebRequest.get(Utils.CorsProxy(this.webField.href)) //
+            WebRequest.get(ClientUtils.CorsProxy(this.webField.href)) //
                 .then(result => result && (this.dataDoc.text = htmlToText(result.content)));
         }
 
@@ -254,7 +255,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
-                const mainrect = this._url ? { translateX: 0, translateY: 0, scale: 1 } : Utils.GetScreenTransform(this._mainCont.current);
+                const mainrect = this._url ? { translateX: 0, translateY: 0, scale: 1 } : ClientUtils.GetScreenTransform(this._mainCont.current);
                 if (rect && rect.width !== this._mainCont.current.clientWidth) {
                     const annoBox = document.createElement('div');
                     annoBox.className = 'marqueeAnnotator-annotationBox';
@@ -283,7 +284,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     focus = (anchor: Doc, options: FocusViewOptions) => {
         if (anchor !== this.Document && this._outerRef.current) {
             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));
+            const scrollTo = ClientUtils.scrollIntoView(
+                NumCast(anchor.y),
+                NumCast(anchor._height),
+                NumCast(this.layoutDoc._layout_scrollTop),
+                windowHeight,
+                windowHeight * 0.1,
+                Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight)
+            );
             if (scrollTo !== undefined) {
                 if (this._initialScroll === undefined) {
                     const focusTime = options.zoomTime ?? 500;
@@ -356,7 +364,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         this._textAnnotationCreator = undefined;
         this.DocumentView?.()?.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 mainContBounds = ClientUtils.GetScreenTransform(this._mainCont.current!);
             const scale = (this._props.NativeDimScaling?.() || 1) * mainContBounds.scale;
             const sel = this._iframe.contentWindow.getSelection();
             if (sel) {
@@ -491,7 +499,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             runInAction(() => this._warning++);
             href = undefined;
         }
-        let requrlraw = decodeURIComponent(href?.replace(Utils.prepend('') + '/corsProxy/', '') ?? this._url.toString());
+        let requrlraw = decodeURIComponent(href?.replace(ClientUtils.prepend('') + '/corsProxy/', '') ?? this._url.toString());
         if (requrlraw !== this._url.toString()) {
             if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) {
                 const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g);
@@ -553,7 +561,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                             const batch = UndoManager.StartBatch('webclick');
                             e.stopPropagation();
                             setTimeout(() => {
-                                this.setData(href.replace(Utils.prepend(''), origin));
+                                this.setData(href.replace(ClientUtils.prepend(''), origin));
                                 batch.end();
                             });
                             if (this._outerRef.current) {
@@ -698,7 +706,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     };
 
     @action
-    setData = (data: Field | Promise<RefField | undefined>) => {
+    setData = (data: FieldType | Promise<RefField | undefined>) => {
         if (!(typeof data === 'string') && !(data instanceof WebField)) return false;
         if (Field.toString(data) === this._url) return false;
         this._scrollHeight = 0;
@@ -816,7 +824,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             );
         }
         if (field instanceof WebField) {
-            const url = this.layoutDoc[this.fieldKey + '_useCors'] ? Utils.CorsProxy(this._webUrl) : this._webUrl;
+            const url = this.layoutDoc[this.fieldKey + '_useCors'] ? ClientUtils.CorsProxy(this._webUrl) : this._webUrl;
             const scripts = this.dataDoc[this.fieldKey + '_allowScripts'] || this._webUrl.includes('wikipedia.org') || this._webUrl.includes('google.com') || this._webUrl.startsWith('https://bing');
             //if (!scripts) console.log('No scripts for: ' + url);
             return (
@@ -1081,8 +1089,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
     scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
-    transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
-    opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [Utils.OpaqueBackgroundFilter])];
+    transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this._props.childFilters(), ClientUtils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [ClientUtils.OpaqueBackgroundFilter])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc)) return 'none';
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 748c3322e..8577510e3 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -4,7 +4,7 @@ import multiMonthPlugin from '@fullcalendar/multimonth';
 import { makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { dateRangeStrToDates } from '../../../../Utils';
+import { dateRangeStrToDates } from '../../../../ClientUtils';
 import { Doc } from '../../../../fields/Doc';
 import { StrCast } from '../../../../fields/Types';
 import { ViewBoxBaseComponent } from '../../DocComponent';
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 7335c9286..dee7d70bb 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -3,10 +3,11 @@ import { observer } from 'mobx-react';
 import { NodeSelection } from 'prosemirror-state';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
+import { Utils } from '../../../../Utils';
+import { returnFalse } from '../../../../ClientUtils';
 import { Doc } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { NumCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { Transform } from '../../../util/Transform';
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 439d4785e..eaa8fffaa 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,15 +1,17 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import { NodeSelection } from 'prosemirror-state';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
+import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Doc, DocListCast, Field } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
 import { Cast, DocCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { Transform } from '../../../util/Transform';
@@ -21,8 +23,6 @@ import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { OpenWhere } from '../DocumentView';
 import './DashFieldView.scss';
 import { FormattedTextBox } from './FormattedTextBox';
-import { DocData } from '../../../../fields/DocSymbols';
-import { NodeSelection } from 'prosemirror-state';
 
 export class DashFieldView {
     dom: HTMLDivElement; // container for label and value
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a2db2a1cc..31252e0ab 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
@@ -12,8 +13,9 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from '
 import { EditorView } from 'prosemirror-view';
 import * as React from 'react';
 import { BsMarkdownFill } from 'react-icons/bs';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, smoothScroll } from '../../../../ClientUtils';
 import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
@@ -23,14 +25,15 @@ import { RichTextField } from '../../../../fields/RichTextField';
 import { ComputedField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { emptyFunction, numberRange, unimplementedFunction, Utils } from '../../../../Utils';
 import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { DictationManager } from '../../../util/DictationManager';
 import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
 import { MakeTemplate } from '../../../util/DropConverter';
 import { LinkManager } from '../../../util/LinkManager';
 import { RTFMarkup } from '../../../util/RTFMarkup';
@@ -42,18 +45,18 @@ import { CollectionStackingView } from '../../collections/CollectionStackingView
 import { CollectionTreeView } from '../../collections/CollectionTreeView';
 import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
+import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
 import { SidebarAnnos } from '../../SidebarAnnos';
-import { StyleProp } from '../../StyleProvider';
-import { media_state } from '../AudioBox';
+import { styleFromLayoutString, StyleProp } from '../../StyleProvider';
+import { mediaState } from '../AudioBox';
 import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
 import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
 import { LinkInfo } from '../LinkDocPreview';
-import { PinProps, PresBox } from '../trails';
+import { PresBox } from '../trails';
 import { DashDocCommentView } from './DashDocCommentView';
 import { DashDocView } from './DashDocView';
 import { DashFieldView } from './DashFieldView';
@@ -152,10 +155,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins);
     }
     @computed get _recordingDictation() {
-        return this.dataDoc?.mediaState === media_state.Recording;
+        return this.dataDoc?.mediaState === mediaState.Recording;
     }
     set _recordingDictation(value) {
-        !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? media_state.Recording : undefined);
+        !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? mediaState.Recording : undefined);
     }
     @computed get config() {
         this._keymap = buildKeymap(schema, this._props);
@@ -275,7 +278,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     anchor.followLinkAudio = true;
                     let stopFunc: any;
                     const targetData = target[DocData];
-                    targetData.mediaState = media_state.Recording;
+                    targetData.mediaState = mediaState.Recording;
                     DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
                     const reactionDisposer = reaction(
                         () => target.mediaState,
@@ -313,7 +316,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         });
         const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
         this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
-        let ele: Opt<HTMLDivElement> = undefined;
+        let ele: Opt<HTMLDivElement>;
         try {
             const contents = window.getSelection()?.getRangeAt(0).cloneContents();
             if (contents) {
@@ -330,7 +333,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             const fieldKey = StrCast(node.attrs.fieldKey);
             return (
                 (node.attrs.hideKey ? '' : fieldKey + ':') + //
-                (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as Field))
+                (node.attrs.hideValue ? '' : Field.toJavascriptString(refDoc[fieldKey] as FieldType))
             );
         }
         return '';
@@ -345,8 +348,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     tryUpdateDoc = (force: boolean) => {
         if (this._editorView && (this._editorView as any).docView) {
-            const state = this._editorView.state;
-            const dataDoc = this.dataDoc;
+            const { state } = this._editorView;
+            const { dataDoc } = this;
             const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText);
             const newJson = JSON.stringify(state.toJSON());
             const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
@@ -386,7 +389,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     } else {
                         // if we've deleted all the text in a note driven by a template, then restore the template data
                         dataDoc[this.fieldKey] = undefined;
-                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData).Data)));
+                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData)?.Data)));
                         ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText });
                         unchanged = false;
                     }
@@ -417,7 +420,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         let link;
         LinkManager.Links(this.dataDoc).forEach((l, i) => {
             const anchor = (l.link_anchor_1 as Doc).annotationOn ? (l.link_anchor_1 as Doc) : (l.link_anchor_2 as Doc).annotationOn ? (l.link_anchor_2 as Doc) : undefined;
-            if (anchor && (anchor.annotationOn as Doc).mediaState === media_state.Recording) {
+            if (anchor && (anchor.annotationOn as Doc).mediaState === mediaState.Recording) {
                 linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
                 linkAnchor = anchor;
                 link = l;
@@ -426,7 +429,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         if (this._editorView && linkTime) {
             const state = this._editorView.state;
             const now = Date.now();
-            let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) });
+            let mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(now / 1000) });
             if (!this._break && state.selection.to !== state.selection.from) {
                 for (let i = state.selection.from; i <= state.selection.to; i++) {
                     const pos = state.doc.resolve(i);
@@ -507,7 +510,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
         const editorView = this._editorView;
         if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
-            const autoLinkTerm = Field.toString(target.title as Field).replace(/^@/, '');
+            const autoLinkTerm = Field.toString(target.title as FieldType).replace(/^@/, '');
             var alink: Doc | undefined;
             this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
                 if (
@@ -587,7 +590,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     };
     adoptAnnotation = (start: number, end: number, mark: Mark) => {
         const view = this._editorView!;
-        const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
+        const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: ClientUtils.CurrentUserEmail });
         view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
     };
     protected createDropTarget = (ele: HTMLDivElement) => {
@@ -726,7 +729,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
         }
         if (highlights.includes('My Text')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail.replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
         }
         if (highlights.includes('Todo Items')) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' });
@@ -745,12 +748,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-ignore', { 'font-size': '1' });
         }
         if (highlights.includes('By Recent Minute')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
             const min = Math.round(Date.now() / 1000 / 60);
             numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
         }
         if (highlights.includes('By Recent Hour')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' });
             const hr = Math.round(Date.now() / 1000 / 60 / 60);
             numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
         }
@@ -1214,18 +1217,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             width => this.tryUpdateScrollHeight()
         );
         this._disposers.scrollHeight = reaction(
-            () => ({ scrollHeight: this.scrollHeight, layout_autoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }),
-            ({ width, scrollHeight, layout_autoHeight }) => width && layout_autoHeight && this.resetNativeHeight(scrollHeight),
+            () => ({ scrollHeight: this.scrollHeight, layoutAutoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }),
+            ({ width, scrollHeight, layoutAutoHeight }) => width && layoutAutoHeight && this.resetNativeHeight(scrollHeight),
             { fireImmediately: true }
         );
         this._disposers.componentHeights = reaction(
             // set the document height when one of the component heights changes and layout_autoHeight is on
-            () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layout_autoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }),
-            ({ sidebarHeight, textHeight, layout_autoHeight, marginsHeight }) => {
+            () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }),
+            ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => {
                 const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
                 if (
                     (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && //
-                    layout_autoHeight &&
+                    layoutAutoHeight &&
                     newHeight &&
                     newHeight !== this.layoutDoc.height &&
                     !this._props.dontRegisterView
@@ -1273,14 +1276,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         this._disposers.search = reaction(
             () => Doc.IsSearchMatch(this.Document),
             search => (search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms()),
-            { fireImmediately: Doc.IsSearchMatchUnmemoized(this.Document) ? true : false }
+            { fireImmediately: !!Doc.IsSearchMatchUnmemoized(this.Document) }
         );
 
         this._disposers.selected = reaction(
             () => this._props.rootSelected?.(),
             action(selected => {
-                //selected && setTimeout(() => this.prepareForTyping());
+                // selected && setTimeout(() => this.prepareForTyping());
                 if (FormattedTextBox._globalHighlights.has('Bold Text')) {
+                    // eslint-disable-next-line operator-assignment
                     this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
                 }
                 if (RichTextMenu.Instance?.view === this._editorView && !selected) {
@@ -1321,10 +1325,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     }
 
     clipboardTextSerializer = (slice: Slice): string => {
-        let text = '',
-            separated = true;
-        const from = 0,
-            to = slice.content.size;
+        let text = '';
+        let separated = true;
+        const from = 0;
+        const to = slice.content.size;
         slice.content.nodesBetween(
             from,
             to,
@@ -1346,7 +1350,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
         const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor');
-        return pdfAnchorId && this.addPdfReference(pdfAnchorId) ? true : false;
+        return !!(pdfAnchorId && this.addPdfReference(pdfAnchorId));
     };
 
     addPdfReference = (pdfAnchorId: string) => {
@@ -1389,7 +1393,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const self = this;
         return new Plugin({
             view(newView) {
-                runInAction(() => self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
+                runInAction(() => {
+                    self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView);
+                });
                 return new RichTextMenuPlugin({ editorProps: this._props });
             },
         });
@@ -1414,7 +1420,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                         const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
                         const scrollPos = scrollRef.scrollTop + shift * self.ScreenToLocalBoxXf().Scale;
                         if (this._focusSpeed !== undefined) {
-                            setTimeout(() => scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper)));
+                            setTimeout(() => {
+                                scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed || 0, scrollRef, scrollPos, 'ease', this._scrollStopper));
+                            });
                         } else {
                             scrollRef.scrollTo({ top: scrollPos });
                         }
@@ -1424,25 +1432,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 },
                 dispatchTransaction: this.dispatchTransaction,
                 nodeViews: {
-                    dashComment(node: any, view: any, getPos: any) {
-                        return new DashDocCommentView(node, view, getPos);
-                    },
-                    dashDoc(node: any, view: any, getPos: any) {
-                        return new DashDocView(node, view, getPos, self);
-                    },
-                    dashField(node: any, view: any, getPos: any) {
-                        return new DashFieldView(node, view, getPos, self);
-                    },
-                    equation(node: any, view: any, getPos: any) {
-                        return new EquationView(node, view, getPos, self);
-                    },
-                    summary(node: any, view: any, getPos: any) {
-                        return new SummaryView(node, view, getPos);
-                    },
-                    //ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); },
-                    footnote(node: any, view: any, getPos: any) {
-                        return new FootnoteView(node, view, getPos);
-                    },
+                    dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); }, // prettier-ignore
+                    dashDoc(node: any, view: any, getPos: any)     { return new DashDocView(node, view, getPos, self); }, // prettier-ignore
+                    dashField(node: any, view: any, getPos: any)   { return new DashFieldView(node, view, getPos, self); }, // prettier-ignore
+                    equation(node: any, view: any, getPos: any)    { return new EquationView(node, view, getPos, self); }, // prettier-ignore
+                    summary(node: any, view: any, getPos: any)     { return new SummaryView(node, view, getPos); }, // prettier-ignore
+                    footnote(node: any, view: any, getPos: any)    { return new FootnoteView(node, view, getPos); }, // prettier-ignore
                 },
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
@@ -1450,7 +1445,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             const { state, dispatch } = this._editorView;
             if (!rtfField) {
                 const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
-                const startupText = Field.toString(dataDoc[fieldKey] as Field);
+                const startupText = Field.toString(dataDoc[fieldKey] as FieldType);
                 if (startupText) {
                     dispatch(state.tr.insertText(startupText));
                 }
@@ -1474,7 +1469,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             this._props.select(false);
             if (selLoadChar) {
                 const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
-                const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
+                const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
                 const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
                 const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
                 const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks);
@@ -1510,7 +1505,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) })] : []),
             ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) })] : []),
             ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
-            ...[schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })],
+            ...[schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })],
         ];
         this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks));
     };
@@ -1761,7 +1756,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         let stopPropagation = true;
         for (var i = state.selection.from; i <= state.selection.to; i++) {
             const node = state.doc.resolve(i);
-            if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) {
+            if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== ClientUtils.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) {
                 e.preventDefault();
             }
         }
@@ -1781,11 +1776,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 this._editorView?.state.selection.empty && (stopPropagation = false);
                 break;
             default:
-                if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break;
+                if (this._lastTimedMark?.attrs.userid === ClientUtils.CurrentUserEmail) break;
             case ' ':
                 if (e.code !== 'Space' && e.code !== 'Backspace') {
                     [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document)) &&
-                        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
+                        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
                 }
                 break;
         }
@@ -1892,7 +1887,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     Document={this.Document}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
-                    usePanelWidth={true}
+                    usePanelWidth
                     nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
                     showSidebar={this.SidebarShown}
                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -2000,11 +1995,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
         if (this._props.isContentActive()) {
             const scale = this._props.NativeDimScaling?.() || 1;
-            const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._header_height}px' >
-            const height = Number(styleFromLayoutString.height?.replace('px', ''));
+            const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._header_height}px' >
+            const height = Number(styleFromLayout.height?.replace('px', ''));
             // prevent default if selected || child is active  but this doc isn't scrollable
             if (
-                !Number.isNaN(height) &&
+                !isNaN(height) &&
                 (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
                 (this._props.rootSelected?.() || this.isAnyChildContentActive())
             ) {
@@ -2033,15 +2028,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
         const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0);
         const paddingY = NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
-        const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._header_height}px' >
-        return styleFromLayoutString?.height === '0px' ? null : (
+        const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._header_height}px' >
+        return styleFromLayout?.height === '0px' ? null : (
             <div
                 className="formattedTextBox"
                 onPointerEnter={action(() => {
                     this._isHovering = true;
                     this.layoutDoc[`_${this._props.fieldKey}_usePath`] && (this.Document.isHovering = true);
                 })}
-                onPointerLeave={action(() => (this.Document.isHovering = this._isHovering = false))}
+                onPointerLeave={action(() => { this.Document.isHovering = this._isHovering = false; })} // prettier-ignore
                 ref={r => {
                     this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
                     this._oldWheel = r;
@@ -2061,7 +2056,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     fontSize: this.fontSize,
                     fontFamily: this.fontFamily,
                     fontWeight: this.fontWeight,
-                    ...styleFromLayoutString,
+                    ...styleFromLayout,
                 }}>
                 <div
                     className="formattedTextBox-cont"
@@ -2083,7 +2078,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     onDoubleClick={this.onDoubleClick}>
                     <div
                         className="formattedTextBox-outer"
-                        ref={r => (this._scrollRef = r)}
+                        ref={r => {
+                            this._scrollRef = r;
+                        }}
                         style={{
                             width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
                             overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index ce17af6ca..c0cb60c6d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,15 +1,16 @@
 import { Mark, ResolvedPos } from 'prosemirror-model';
-import { EditorState, NodeSelection } from 'prosemirror-state';
+import { EditorState } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
+import { ClientUtils } from '../../../../ClientUtils';
 import { Doc } from '../../../../fields/Doc';
 import { DocServer } from '../../../DocServer';
-import { LinkDocPreview, LinkInfo } from '../LinkDocPreview';
+import { LinkInfo } from '../LinkDocPreview';
 import { FormattedTextBox } from './FormattedTextBox';
 import './FormattedTextBoxComment.scss';
 import { schema } from './schema_rts';
 
 export function findOtherUserMark(marks: readonly Mark[]): Mark | undefined {
-    return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+    return marks.find(m => m.attrs.userid && m.attrs.userid !== ClientUtils.CurrentUserEmail);
 }
 export function findUserMark(marks: readonly Mark[]): Mark | undefined {
     return marks.find(m => m.attrs.userid);
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 03c902580..e9ed2549e 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,28 +1,28 @@
 import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands';
 import { redo, undo } from 'prosemirror-history';
 import { Schema } from 'prosemirror-model';
-import { splitListItem, wrapInList, sinkListItem, liftListItem } from 'prosemirror-schema-list';
+import { liftListItem, sinkListItem, splitListItem, wrapInList } from 'prosemirror-schema-list';
 import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
 import { liftTarget } from 'prosemirror-transform';
+import { EditorView } from 'prosemirror-view';
+import { ClientUtils } from '../../../../ClientUtils';
+import { Utils } from '../../../../Utils';
 import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
 import { GetEffectiveAcl } from '../../../../fields/util';
-import { Utils } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { RTFMarkup } from '../../../util/RTFMarkup';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { OpenWhere } from '../DocumentView';
-import { Doc } from '../../../../fields/Doc';
-import { EditorView } from 'prosemirror-view';
 
 const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
 
 export type KeyMap = { [key: string]: any };
 
-export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => {
+export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => {
     let mapStyle = assignedMapStyle;
-    tx2.doc.descendants((node: any, offset: any, index: any) => {
+    tx2.doc.descendants((node: any, offset: any /* , index: any */) => {
         if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) {
-            const path = (tx2.doc.resolve(offset) as any).path;
+            const { path } = tx2.doc.resolve(offset) as any;
             let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty('type') && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
             if (node.type === schema.nodes.ordered_list) {
                 if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle;
@@ -49,23 +49,27 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     const canEdit = (state: any) => {
         switch (GetEffectiveAcl(props.TemplateDataDocument)) {
             case AclAugment:
-                const prevNode = state.selection.$cursor.nodeBefore;
-                const prevUser = !prevNode ? Doc.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid;
-                if (prevUser != Doc.CurrentUserEmail) {
-                    return false;
+                {
+                    const prevNode = state.selection.$cursor.nodeBefore;
+                    const prevUser = !prevNode ? ClientUtils.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid;
+                    if (prevUser !== ClientUtils.CurrentUserEmail) {
+                        return false;
+                    }
                 }
+                break;
+            default:
         }
         return true;
     };
 
     const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
 
-    //History commands
+    // History commands
     bind('Mod-z', undo);
     bind('Shift-Mod-z', redo);
     !mac && bind('Mod-y', redo);
 
-    //Commands to modify Mark
+    // Commands to modify Mark
     bind('Mod-b', toggleEditableMark(schema.marks.strong));
     bind('Mod-B', toggleEditableMark(schema.marks.strong));
 
@@ -77,7 +81,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     bind('Mod-u', toggleEditableMark(schema.marks.underline));
     bind('Mod-U', toggleEditableMark(schema.marks.underline));
 
-    //Commands for lists
+    // Commands for lists
     bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any));
 
     bind('Ctrl-Tab', () => (props.onKey?.(event, props) ? true : true));
@@ -103,8 +107,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
             if (
                 !wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
                     const tx25 = updateBullets(tx2, schema);
-                    const ol_node = tx25.doc.nodeAt(range!.start)!;
-                    const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, ol_node.attrs, marks);
+                    const olNode = tx25.doc.nodeAt(range!.start)!;
+                    const tx3 = tx25.setNodeMarkup(range!.start, olNode.type, olNode.attrs, marks);
                     // when promoting to a list, assume list will format things so don't copy the stored marks.
                     marks && tx3.ensureMarks([...marks]);
                     marks && tx3.setStoredMarks([...marks]);
@@ -134,13 +138,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
         }
     });
 
-    //Command to create a new Tab with a PDF of all the command shortcuts
+    // Command to create a new Tab with a PDF of all the command shortcuts
     bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => {
-        const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
+        const newDoc = Docs.Create.PdfDocument(ClientUtils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
         props.addDocTab(newDoc, OpenWhere.addRight);
     });
 
-    //Commands to modify BlockType
+    // Commands to modify BlockType
     bind('Ctrl->', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any)));
     bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any));
     bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any));
@@ -156,11 +160,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
         bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any));
     }
 
-    //Command to create a horizontal break line
+    // Command to create a horizontal break line
     const hr = schema.nodes.horizontal_rule;
     bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
 
-    //Command to unselect all
+    // Command to unselect all
     bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => {
         dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
         (document.activeElement as any).blur?.();
@@ -189,7 +193,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     });
     bind('Cmd-]', (state: EditorState, dispatch: (tx: Transaction) => void) => {
         const resolved = state.doc.resolve(state.selection.from) as any;
-        const tr = state.tr;
+        const { tr } = state;
         if (resolved?.parent.type.name === 'paragraph') {
             tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
         } else {
@@ -204,7 +208,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     });
     bind('Cmd-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => {
         const resolved = state.doc.resolve(state.selection.from) as any;
-        const tr = state.tr;
+        const { tr } = state;
         if (resolved?.parent.type.name === 'paragraph') {
             tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
         } else {
@@ -219,7 +223,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     });
     bind('Cmd-[', (state: EditorState, dispatch: (tx: Transaction) => void) => {
         const resolved = state.doc.resolve(state.selection.from) as any;
-        const tr = state.tr;
+        const { tr } = state;
         if (resolved?.parent.type.name === 'paragraph') {
             tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
         } else {
@@ -236,7 +240,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     bind('Cmd-f', (state: EditorState, dispatch: (tx: Transaction) => void) => {
         const content = state.tr.selection.empty ? undefined : state.tr.selection.content().content.textBetween(0, state.tr.selection.content().size + 1);
         const newNode = schema.nodes.footnote.create({}, content ? state.schema.text(content) : undefined);
-        const tr = state.tr;
+        const { tr } = state;
         tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
         dispatch(
             tr.setSelection(
@@ -288,8 +292,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     };
     bind('Backspace', backspace);
 
-    //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
-    //command to break line
+    // newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
+    // command to break line
 
     const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
         if (props.onKey?.(event, props)) return true;
@@ -356,7 +360,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     };
     bind('Enter', enter);
 
-    //Command to create a blank space
+    // Command to create a blank space
     bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
         if (props.TemplateDataDocument && GetEffectiveAcl(props.TemplateDataDocument) != AclEdit && GetEffectiveAcl(props.TemplateDataDocument) != AclAugment && GetEffectiveAcl(props.TemplateDataDocument) != AclAdmin) return true;
         return false;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index cecf106a3..ec9c1a15d 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -3,7 +3,7 @@ import { Tooltip } from '@mui/material';
 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 { Mark, MarkType } from 'prosemirror-model';
 import { wrapInList } from 'prosemirror-schema-list';
 import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 42665830f..78ea99592 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,5 +1,6 @@
 import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
 import { NodeSelection, TextSelection } from 'prosemirror-state';
+import { ClientUtils } from '../../../../ClientUtils';
 import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc';
 import { DocData } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
@@ -48,13 +49,9 @@ export class RichTextRules {
                 /^A\.\s$/,
                 schema.nodes.ordered_list,
                 // match => {
-                () => {
-                    return { mapStyle: 'multi', bulletStyle: 1 };
-                    // return ({ order: +match[1] })
-                },
-                (match: any, node: any) => {
-                    return node.childCount + node.attrs.order === +match[1];
-                },
+                () => ({ mapStyle: 'multi', bulletStyle: 1 }),
+                // return ({ order: +match[1] })
+                (match: any, node: any) => node.childCount + node.attrs.order === +match[1],
                 ((type: any) => ({ type: type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as any
             ),
 
@@ -70,7 +67,7 @@ export class RichTextRules {
 
             // ``` create code block
             new InputRule(/^```$/, (state, match, start, end) => {
-                let $start = state.doc.resolve(start);
+                const $start = state.doc.resolve(start);
                 if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), schema.nodes.code_block)) return null;
 
                 // this enables text with code blocks to be used as a 'paint' function via a styleprovider button that is added to Docs that have an onPaint script
@@ -86,13 +83,13 @@ export class RichTextRules {
             }),
 
             // %<font-size> set the font size
-            new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => {
+            new InputRule(/%([0-9]+)\s$/, (state, match, start, end) => {
                 const size = Number(match[1]);
                 return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
             }),
 
-            //Create annotation to a field on the text document
-            new InputRule(new RegExp(/>::$/), (state, match, start, end) => {
+            // Create annotation to a field on the text document
+            new InputRule(/>::$/, (state, match, start, end) => {
                 const creator = (doc: Doc) => {
                     const textDoc = this.Document[DocData];
                     const numInlines = NumCast(textDoc.inlineTextCount);
@@ -107,7 +104,7 @@ export class RichTextRules {
                                   .insert(start, newNode)
                                   .replaceRangeWith(start + 1, end + 2, dashDoc)
                                   .insertText(' ', start + 2)
-                                  .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+                                  .setStoredMarks([...node.marks, ...(sm || [])])
                             : this.TextBox.EditorView.state.tr
                     );
                 };
@@ -117,8 +114,8 @@ export class RichTextRules {
 
                 return null;
             }),
-            //Create annotation to a field on the text document
-            new InputRule(new RegExp(/>>$/), (state, match, start, end) => {
+            // Create annotation to a field on the text document
+            new InputRule(/>>$/, (state, match, start, end) => {
                 const textDoc = this.Document[DocData];
                 const numInlines = NumCast(textDoc.inlineTextCount);
                 textDoc.inlineTextCount = numInlines + 1;
@@ -150,13 +147,13 @@ export class RichTextRules {
                           .insert(start, newNode)
                           .replaceRangeWith(start + 1, end + 1, dashDoc)
                           .insertText(' ', start + 2)
-                          .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+                          .setStoredMarks([...node.marks, ...(sm || [])])
                     : state.tr;
                 return replaced;
             }),
 
             // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
-            new InputRule(new RegExp(/(%d|d)$/), (state, match, start, end) => {
+            new InputRule(/(%d|d)$/, (state, match, start, end) => {
                 if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
                 const pos = state.doc.resolve(start) as any;
                 for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
@@ -171,7 +168,7 @@ export class RichTextRules {
             }),
 
             // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
-            new InputRule(new RegExp(/(%h|h)$/), (state, match, start, end) => {
+            new InputRule(/(%h|h)$/, (state, match, start, end) => {
                 if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
                 const pos = state.doc.resolve(start) as any;
                 for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
@@ -186,11 +183,11 @@ export class RichTextRules {
             }),
 
             // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
-            new InputRule(new RegExp(/(%q|q)$/), (state, match, start, end) => {
+            new InputRule(/(%q|q)$/, (state, match, start, end) => {
                 if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
                 const pos = state.doc.resolve(start) as any;
                 if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
-                    const node = state.selection.node;
+                    const { node } = state.selection;
                     return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
                 }
                 for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
@@ -205,46 +202,43 @@ export class RichTextRules {
             }),
 
             // center justify text
-            new InputRule(new RegExp(/%\^/), (state, match, start, end) => {
+            new InputRule(/%\^/, (state, match, start, end) => {
                 const resolved = state.doc.resolve(start) as any;
                 if (resolved?.parent.type.name === 'paragraph') {
                     return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
-                } else {
-                    const node = resolved.nodeAfter;
-                    const sm = state.storedMarks || undefined;
-                    const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
-                    return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
                 }
+                const node = resolved.nodeAfter;
+                const sm = state.storedMarks || undefined;
+                const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr;
+                return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
             }),
 
             // left justify text
-            new InputRule(new RegExp(/%\[/), (state, match, start, end) => {
+            new InputRule(/%\[/, (state, match, start, end) => {
                 const resolved = state.doc.resolve(start) as any;
                 if (resolved?.parent.type.name === 'paragraph') {
                     return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
-                } else {
-                    const node = resolved.nodeAfter;
-                    const sm = state.storedMarks || undefined;
-                    const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
-                    return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
                 }
+                const node = resolved.nodeAfter;
+                const sm = state.storedMarks || undefined;
+                const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr;
+                return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
             }),
 
             // right justify text
-            new InputRule(new RegExp(/%\]/), (state, match, start, end) => {
+            new InputRule(/%\]/, (state, match, start, end) => {
                 const resolved = state.doc.resolve(start) as any;
                 if (resolved?.parent.type.name === 'paragraph') {
                     return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
-                } else {
-                    const node = resolved.nodeAfter;
-                    const sm = state.storedMarks || undefined;
-                    const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
-                    return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
                 }
+                const node = resolved.nodeAfter;
+                const sm = state.storedMarks || undefined;
+                const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr;
+                return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
             }),
 
             // activate a style by name using prefix '%<color name>'
-            new InputRule(new RegExp(/%[a-zA-Z_]+$/), (state, match, start, end) => {
+            new InputRule(/%[a-zA-Z_]+$/, (state, match, start, end) => {
                 const color = match[0].substring(1, match[0].length);
                 const marks = RichTextMenu.Instance?._brushMap.get(color);
 
@@ -276,13 +270,13 @@ export class RichTextRules {
             }),
 
             // toggle alternate text UI  %/
-            new InputRule(new RegExp(/%\//), (state, match, start, end) => {
+            new InputRule(/%\//, (state, match, start, end) => {
                 setTimeout(() => this.TextBox.cycleAlternateText(true));
                 return state.tr.deleteRange(start, end);
             }),
 
             // stop using active style
-            new InputRule(new RegExp(/%%$/), (state, match, start, end) => {
+            new InputRule(/%%$/, (state, match, start, end) => {
                 const tr = state.tr.deleteRange(start, end);
                 const marks = state.tr.selection.$anchor.nodeBefore?.marks;
 
@@ -295,7 +289,7 @@ export class RichTextRules {
 
             // create a hyperlink to a titled document
             // @(<doctitle>)
-            new InputRule(new RegExp(/@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => {
+            new InputRule(/@\(([a-zA-Z_@.? \-0-9]+)\)/, (state, match, start, end) => {
                 const docTitle = match[1];
                 const prefixLength = '@('.length;
                 if (docTitle) {
@@ -335,7 +329,7 @@ export class RichTextRules {
             // [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value]
             // [@{this,doctitle,}.fieldKey]
             new InputRule(
-                new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_\(\)\.@\?\+\-\*\/\ 0-9\(\)]*))?\]/),
+                /\[(@|@this\.|@[a-zA-Z_? \-0-9]+\.)([a-zA-Z_?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_().@?+\-*/ 0-9()]*))?\]/,
                 (state, match, start, end) => {
                     const docTitle = match[1].substring(1).replace(/\.$/, '');
                     const fieldKey = match[2];
@@ -349,8 +343,17 @@ export class RichTextRules {
                         const strs = values.some(v => !v.match(/^[-]?[0-9.]$/));
                         this.Document[DocData][fieldKey] = strs ? new List<string>(values) : new List<number>(values.map(v => Number(v)));
                     } else if (value) {
-                        KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign.includes(":=") ? undefined:
-                            (gptval: FieldResult) => (dataDoc ? this.Document[DocData]:this.Document)[fieldKey] = gptval as string ); // prettier-ignore
+                        KeyValueBox.SetField(
+                            this.Document,
+                            fieldKey,
+                            assign + value,
+                            Doc.IsDataProto(this.Document) ? true : undefined,
+                            assign.includes(':=')
+                                ? undefined
+                                : (gptval: FieldResult) => {
+                                      (dataDoc ? this.Document[DocData] : this.Document)[fieldKey] = gptval as string;
+                                  }
+                        );
                         if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr;
                     }
                     const target = docTitle ? getTitledDoc(docTitle) : undefined;
@@ -361,8 +364,8 @@ export class RichTextRules {
             ),
 
             // pass the contents between '((' and '))' to chatGPT and append the result
-            new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))$/), (state, match, start, end) => {
-                var count = 0; // ignore first return value which will be the notation that chat is pending a result
+            new InputRule(/(^|[^=])(\(\(.*\)\))$/, (state, match, start, end) => {
+                let count = 0; // ignore first return value which will be the notation that chat is pending a result
                 KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => {
                     if (count) {
                         const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string));
@@ -376,7 +379,7 @@ export class RichTextRules {
 
             // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
             // @(wiki:title)
-            new InputRule(new RegExp(/@\(wiki:([a-zA-Z_@:\.\?\-0-9 ]+)\)$/), (state, match, start, end) => {
+            new InputRule(/@\(wiki:([a-zA-Z_@:.?\-0-9 ]+)\)$/, (state, match, start, end) => {
                 const title = match[1].trim().replace(/ /g, '_');
                 this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
 
@@ -392,7 +395,7 @@ export class RichTextRules {
 
             // create an inline equation node
             // %eq
-            new InputRule(new RegExp(/%eq/), (state, match, start, end) => {
+            new InputRule(/%eq/, (state, match, start, end) => {
                 const fieldKey = 'math' + Utils.GenerateGuid();
                 this.TextBox.dataDoc[fieldKey] = 'y=';
                 const tr = state.tr.setSelection(new TextSelection(state.tr.doc.resolve(end - 3), state.tr.doc.resolve(end))).replaceSelectionWith(schema.nodes.equation.create({ fieldKey }));
@@ -400,10 +403,10 @@ export class RichTextRules {
             }),
 
             // create an inline view of a tag stored under the '#' field
-            new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => {
+            new InputRule(/#([a-zA-Z_-]+[a-zA-Z_\-0-9]*)\s$/, (state, match, start, end) => {
                 const tag = match[1];
                 if (!tag) return state.tr;
-                //this.Document[DocData]['#' + tag] = '#' + tag;
+                // this.Document[DocData]['#' + tag] = '#' + tag;
                 const tags = StrListCast(this.Document[DocData].tags);
                 if (!tags.includes(tag)) {
                     tags.push(tag);
@@ -417,29 +420,25 @@ export class RichTextRules {
             }),
 
             // # heading
-            textblockTypeInputRule(new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, match => {
-                return { level: match[1].length };
-            }),
+            textblockTypeInputRule(/^(#{1,6})\s$/, schema.nodes.heading, match => ({ level: match[1].length })),
 
             // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
-            new InputRule(new RegExp(/[ti!x]$/), (state, match, start, end) => {
+            new InputRule(/[ti!x]$/, (state, match, start, end) => {
                 if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
 
                 const tag = match[0] === 't' ? 'todo' : match[0] === 'i' ? 'ignore' : match[0] === 'x' ? 'disagree' : match[0] === '!' ? 'important' : '??';
                 const node = (state.doc.resolve(start) as any).nodeAfter;
 
                 if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
-                if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_mark) !== -1) {
-                }
                 return node
                     ? state.tr
                           .removeMark(start, end, schema.marks.user_mark)
-                          .addMark(start, end, schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))
-                          .addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) }))
+                          .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))
+                          .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) }))
                     : state.tr;
             }),
 
-            new InputRule(new RegExp(/%\(/), (state, match, start, end) => {
+            new InputRule(/%\(/, (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks?.slice() || [];
                 const mark = state.schema.marks.summarizeInclusive.create();
@@ -452,9 +451,7 @@ export class RichTextRules {
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end))).setStoredMarks([...node.marks, ...sm]);
             }),
 
-            new InputRule(new RegExp(/%\)/), (state, match, start, end) => {
-                return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
-            }),
+            new InputRule(/%\)/, (state, match, start, end) => state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create())),
         ],
     };
 }
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index ccf7de4a1..8f716ad7a 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,6 +1,5 @@
-import * as React from 'react';
-import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model';
-import { Doc } from '../../../../fields/Doc';
+import { DOMOutputSpec, MarkSpec } from 'prosemirror-model';
+import { ClientUtils } from '../../../../ClientUtils';
 import { Utils } from '../../../../Utils';
 
 const emDOM: DOMOutputSpec = ['em', 0];
@@ -336,7 +335,7 @@ export const marks: { [index: string]: MarkSpec } = {
             const min = Math.round(node.attrs.modified / 60);
             const hr = Math.round(min / 60);
             const day = Math.round(hr / 60 / 24);
-            const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : '';
+            const remote = node.attrs.userid !== ClientUtils.CurrentUserEmail ? ' UM-remote' : '';
             return ['span', { class: 'UM-' + uid + remote + ' UM-min-' + min + ' UM-hr-' + hr + ' UM-day-' + day }, 0];
         },
     },
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 62b8b03d6..70b6604ab 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -2,7 +2,7 @@ import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model';
 import { listItem, orderedList } from 'prosemirror-schema-list';
 import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
 import { DocServer } from '../../../DocServer';
-import { Doc, Field } from '../../../../fields/Doc';
+import { Doc, Field, FieldType } from '../../../../fields/Doc';
 
 const blockquoteDOM: DOMOutputSpec = ['blockquote', 0],
     hrDOM: DOMOutputSpec = ['hr'],
@@ -266,7 +266,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             hideValue: { default: false },
             editable: { default: true },
         },
-        leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field),
+        leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as FieldType),
         group: 'inline',
         draggable: false,
         toDOM(node) {
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index a485ea4c3..95eb86720 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -1,27 +1,31 @@
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/img-redundant-alt */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/function-component-definition */
 import { Checkbox, FormControlLabel, Slider, TextField } from '@mui/material';
 import { IconButton } from 'browndash-components';
+import * as React from 'react';
 import { useEffect, useRef, useState } from 'react';
 import { CgClose } from 'react-icons/cg';
 import { IoMdRedo, IoMdUndo } from 'react-icons/io';
+import { ClientUtils } from '../../../../ClientUtils';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { NumCast } from '../../../../fields/Types';
-import { Utils } from '../../../../Utils';
-import { Docs, DocUtils } from '../../../documents/Documents';
 import { Networking } from '../../../Network';
+import { DocUtils, Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
 import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { OpenWhereMod } from '../DocumentView';
-import { ImageBox, ImageEditorData } from '../ImageBox';
+import { ImageEditorData } from '../ImageBox';
 import './GenerativeFill.scss';
 import Buttons from './GenerativeFillButtons';
 import { BrushHandler } from './generativeFillUtils/BrushHandler';
-import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
-import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
 import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
 import { PointerHandler } from './generativeFillUtils/PointerHandler';
-import * as React from 'react';
+import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
+import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
 
 enum BrushStyle {
     ADD,
@@ -332,7 +336,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         const startY = NumCast(parentDoc.current.y);
         const children = DocListCast(parentDoc.current.gen_fill_children);
         const len = children.length;
-        let initialYPositions: number[] = [];
+        const initialYPositions: number[] = [];
         for (let i = 0; i < len; i++) {
             initialYPositions.push(startY + i * offsetDistanceY);
         }
@@ -348,9 +352,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     // creates a new image document and returns its reference
     const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise<Doc | undefined> => {
         if (!imageRootDoc) return;
-        const src = img.src;
+        const { src } = img;
         const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
-        const source = Utils.prepend(result.accessPaths.agnostic.client);
+        const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
 
         if (firstDoc) {
             const x = 0;
@@ -370,51 +374,51 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             }
             parentDoc.current = newImg;
             return newImg;
-        } else {
-            if (!parentDoc.current) return;
-            const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
-            const initialY = 0;
-
-            const newImg = Docs.Create.ImageDocument(source, {
-                x: x,
-                y: initialY,
-                _height: freeformRenderSize,
-                _width: freeformRenderSize,
-                data_nativeWidth: result.nativeWidth,
-                data_nativeHeight: result.nativeHeight,
-            });
+        }
+        if (!parentDoc.current) return;
+        const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
+        const initialY = 0;
+
+        const newImg = Docs.Create.ImageDocument(source, {
+            x: x,
+            y: initialY,
+            _height: freeformRenderSize,
+            _width: freeformRenderSize,
+            data_nativeWidth: result.nativeWidth,
+            data_nativeHeight: result.nativeHeight,
+        });
 
-            const parentList = DocListCast(parentDoc.current.gen_fill_children);
-            if (parentList.length > 0) {
-                parentList.push(newImg);
-                parentDoc.current.gen_fill_children = new List<Doc>(parentList);
-            } else {
-                parentDoc.current.gen_fill_children = new List<Doc>([newImg]);
-            }
+        const parentList = DocListCast(parentDoc.current.gen_fill_children);
+        if (parentList.length > 0) {
+            parentList.push(newImg);
+            parentDoc.current.gen_fill_children = new List<Doc>(parentList);
+        } else {
+            parentDoc.current.gen_fill_children = new List<Doc>([newImg]);
+        }
 
-            DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true });
-            adjustImgPositions();
+        DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true });
+        adjustImgPositions();
 
-            if (isNewCollection && newCollectionRef.current) {
-                Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
-            } else {
-                addDoc?.(newImg);
-            }
-            return newImg;
+        if (isNewCollection && newCollectionRef.current) {
+            Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+        } else {
+            addDoc?.(newImg);
         }
+        return newImg;
     };
 
     // Saves an image to the collection
     const onSave = async (src: string) => {
         const img = new Image();
         img.src = src;
-        if (!currImg.current || !originalImg.current || !imageRootDoc) return;
+        if (!currImg.current || !originalImg.current || !imageRootDoc) return undefined;
         try {
             const res = await createNewImgDoc(img, false);
             return res;
         } catch (err) {
             console.log(err);
         }
+        return undefined;
     };
 
     // Closes the editor view
@@ -443,12 +447,12 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                                 }}
                             />
                         }
-                        label={'Create New Collection'}
+                        label="Create New Collection"
                         labelPlacement="end"
                         sx={{ whiteSpace: 'nowrap' }}
                     />
                     <Buttons getEdit={getEdit} loading={loading} onReset={handleReset} />
-                    <IconButton color={activeColor} tooltip="close" icon={<CgClose size={'16px'} />} onClick={handleViewClose} />
+                    <IconButton color={activeColor} tooltip="close" icon={<CgClose size="16px" />} onClick={handleViewClose} />
                 </div>
             </div>
             {/* Main canvas for editing */}
@@ -469,7 +473,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                         width: cursorData.width,
                         height: cursorData.width,
                     }}>
-                    <div className="innerPointer"></div>
+                    <div className="innerPointer" />
                 </div>
                 {/* Icons */}
                 <div className="iconContainer">
@@ -519,11 +523,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                         />
                     </div>
                 </div>
-                {/* Edits thumbnails*/}
+                {/* Edits thumbnails */}
                 <div className="editsBox">
                     {edits.map((edit, i) => (
                         <img
+                            // eslint-disable-next-line react/no-array-index-key
                             key={i}
+                            alt="image edits"
                             width={75}
                             src={edit[0] as string}
                             style={{ cursor: 'pointer' }}
@@ -552,6 +558,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                                 Original
                             </label>
                             <img
+                                alt="image stuff"
                                 width={75}
                                 src={originalImg.current?.src}
                                 style={{ cursor: 'pointer' }}
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 6e7c3e612..dec9a5019 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -1,7 +1,7 @@
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { returnFalse } from '../../../../Utils';
+import { returnFalse } from '../../../../ClientUtils';
 import { Doc } from '../../../../fields/Doc';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { DocumentView } from '../DocumentView';
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 91fdb90fc..518bf66cd 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -3,7 +3,8 @@ import { Tooltip } from '@mui/material';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, Field, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
+import { lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../ClientUtils';
+import { Doc, DocListCast, Field, FieldType, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
 import { Animation, DocData, TransitionTimer } from '../../../../fields/DocSymbols';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
 import { InkField } from '../../../../fields/InkField';
@@ -13,23 +14,23 @@ import { listSpec } from '../../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { AudioField } from '../../../../fields/URLField';
-import { emptyFunction, emptyPath, lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent, stringHash } from '../../../../Utils';
+import { emptyFunction, emptyPath, stringHash } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
-import { dropActionType } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { SerializationHelper } from '../../../util/SerializationHelper';
-import { SettingsManager } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
 import { CollectionView } from '../../collections/CollectionView';
 import { TreeView } from '../../collections/TreeView';
-import { ViewBoxBaseComponent } from '../../DocComponent';
+import { pinDataTypes, PinProps, ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
 import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
@@ -37,32 +38,6 @@ import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
-export interface pinDataTypes {
-    scrollable?: boolean;
-    dataviz?: number[];
-    pannable?: boolean;
-    type_collection?: boolean;
-    inkable?: boolean;
-    filters?: boolean;
-    pivot?: boolean;
-    temporal?: boolean;
-    clippable?: boolean;
-    datarange?: boolean;
-    dataview?: boolean;
-    poslayoutview?: boolean;
-    dataannos?: boolean;
-    map?: boolean;
-}
-export interface PinProps {
-    audioRange?: boolean;
-    activeFrame?: number;
-    currentFrame?: number;
-    hidePresBox?: boolean;
-    pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected)
-    pinDocLayout?: boolean; // pin layout info (width/height/x/y)
-    pinAudioPlay?: boolean; // pin audio annotation
-    pinData?: pinDataTypes;
-}
 
 @observer
 export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -443,7 +418,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             else {
                 const bestTargetData = bestTarget[DocData];
                 const current = bestTargetData[fkey];
-                const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as Field)) : undefined;
+                const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as FieldType)) : undefined;
                 if (hash) bestTargetData[fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current;
                 bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
             }
@@ -623,7 +598,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     /// reserved fields on the pinDoc so that those values can be restored to the
     /// target doc when navigating to it.
     @action
-    static pinDocView(pinDoc: Doc, pinProps: PinProps, targetDoc: Doc) {
+    static pinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) {
+        const pinDoc = pinDocIn;
         pinDoc.presentation = true;
         pinDoc.config = '';
         if (pinProps.pinDocLayout) {
@@ -1479,7 +1455,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 max={max}
                 value={value}
                 readOnly={true}
-                style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)`, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }}
+                style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)`, background: SnappingManager.userColor, color: SnappingManager.userVariantColor }}
                 className={`toolbar-slider ${active ? '' : 'none'}`}
                 onPointerDown={e => {
                     PresBox._sliderBatch = UndoManager.StartBatch('pres slider');
@@ -1521,7 +1497,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <Tooltip title={<div className="dash-tooltip">Hide before presented</div>}>
                             <div
                                 className={`ribbon-toggle ${activeItem.presentation_hideBefore ? 'active' : ''}`}
-                                style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideBefore ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+                                style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hideBefore ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
                                 onClick={() => this.updateHideBefore(activeItem)}>
                                 Hide before
                             </div>
@@ -1529,7 +1505,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <Tooltip title={<div className="dash-tooltip">{'Hide while presented'}</div>}>
                             <div
                                 className={`ribbon-toggle ${activeItem.presentation_hide ? 'active' : ''}`}
-                                style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hide ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+                                style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hide ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
                                 onClick={() => this.updateHide(activeItem)}>
                                 Hide
                             </div>
@@ -1538,7 +1514,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}>
                             <div
                                 className={`ribbon-toggle ${activeItem.presentation_hideAfter ? 'active' : ''}`}
-                                style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideAfter ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+                                style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hideAfter ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
                                 onClick={() => this.updateHideAfter(activeItem)}>
                                 Hide after
                             </div>
@@ -1548,9 +1524,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             <div
                                 className="ribbon-toggle"
                                 style={{
-                                    border: `solid 1px ${SettingsManager.userColor}`,
-                                    color: SettingsManager.userColor,
-                                    background: activeItem.presentation_openInLightbox ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+                                    border: `solid 1px ${SnappingManager.userColor}`,
+                                    color: SnappingManager.userColor,
+                                    background: activeItem.presentation_openInLightbox ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
                                 }}
                                 onClick={() => this.updateOpenDoc(activeItem)}>
                                 Lightbox
@@ -1559,7 +1535,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <Tooltip title={<div className="dash-tooltip">Transition movement style</div>}>
                             <div
                                 className="ribbon-toggle"
-                                style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presEaseFunc === 'ease' ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
+                                style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presEaseFunc === 'ease' ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
                                 onClick={() => this.updateEaseFunc(activeItem)}>
                                 {`${StrCast(activeItem.presEaseFunc, 'ease')}`}
                             </div>
@@ -1569,10 +1545,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <>
                             <div className="ribbon-doubleButton">
                                 <div className="presBox-subheading">Slide Duration</div>
-                                <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
-                                    <input className="presBox-input" type="number" readOnly={true} value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
+                                <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
+                                    <input className="presBox-input" type="number" readOnly value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
                                 </div>
-                                <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}>
+                                <div className="ribbon-propertyUpDown" style={{ color: SnappingManager.userBackgroundColor, background: SnappingManager.userColor }}>
                                     <div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), 1000)}>
                                         <FontAwesomeIcon icon={'caret-up'} />
                                     </div>
@@ -1611,7 +1587,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <div className="presBox-subheading">Progressivize Collection</div>
                         <input
                             className="presBox-checkbox"
-                            style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+                            style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
                             type="checkbox"
                             onChange={() => {
                                 activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined;
@@ -1634,7 +1610,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <div className="presBox-subheading">Progressivize First Bullet</div>
                         <input
                             className="presBox-checkbox"
-                            style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+                            style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
                             type="checkbox"
                             onChange={() => (activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1)}
                             checked={!NumCast(activeItem.presentation_indexedStart)}
@@ -1644,7 +1620,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <div className="presBox-subheading">Expand Current Bullet</div>
                         <input
                             className="presBox-checkbox"
-                            style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+                            style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
                             type="checkbox"
                             onChange={() => (activeItem.presBulletExpand = !activeItem.presBulletExpand)}
                             checked={BoolCast(activeItem.presBulletExpand)}
@@ -1660,16 +1636,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 this._openBulletEffectDropdown = !this._openBulletEffectDropdown;
                             })}
                             style={{
-                                color: SettingsManager.userColor,
-                                background: SettingsManager.userVariantColor,
+                                color: SnappingManager.userColor,
+                                background: SnappingManager.userVariantColor,
                                 borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5,
-                                border: this._openBulletEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
+                                border: this._openBulletEffectDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
                             }}>
                             {effect?.toString()}
                             <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
                             <div
                                 className={'presBox-dropdownOptions'}
-                                style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}
+                                style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
                                 onPointerDown={e => e.stopPropagation()}>
                                 {Object.values(PresEffect)
                                     .filter(v => isNaN(Number(v)))
@@ -1698,7 +1674,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             </div>
         );
         const presDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => {
-            const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? SettingsManager.userVariantColor : SettingsManager.userColor;
+            const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? SnappingManager.userVariantColor : SnappingManager.userColor;
             return (
                 <Tooltip title={<div className="dash-tooltip">{direction}</div>}>
                     <div
@@ -1733,10 +1709,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 this._openMovementDropdown = !this._openMovementDropdown;
                             })}
                             style={{
-                                color: SettingsManager.userColor,
-                                background: SettingsManager.userVariantColor,
+                                color: SnappingManager.userColor,
+                                background: SnappingManager.userVariantColor,
                                 borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5,
-                                border: this._openMovementDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
+                                border: this._openMovementDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
                             }}>
                             {this.movementName(activeItem)}
                             <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
@@ -1745,8 +1721,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 id={'presBoxMovementDropdown'}
                                 onPointerDown={StopEvent}
                                 style={{
-                                    color: SettingsManager.userColor,
-                                    background: SettingsManager.userBackgroundColor,
+                                    color: SnappingManager.userColor,
+                                    background: SnappingManager.userBackgroundColor,
                                     display: this._openMovementDropdown ? 'grid' : 'none',
                                 }}>
                                 {presMovement(PresMovement.None)}
@@ -1758,10 +1734,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         </div>
                         <div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
                             <div className="presBox-subheading">Zoom (% screen filled)</div>
-                            <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
-                                <input className="presBox-input" type="number" readOnly={true} value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
+                            <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
+                                <input className="presBox-input" type="number" readOnly value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
                             </div>
-                            <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}>
+                            <div className="ribbon-propertyUpDown" style={{ color: SnappingManager.userBackgroundColor, background: SnappingManager.userColor }}>
                                 <div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), 0.1)}>
                                     <FontAwesomeIcon icon={'caret-up'} />
                                 </div>
@@ -1773,10 +1749,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         {PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}
                         <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
                             <div className="presBox-subheading">Transition Time</div>
-                            <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
+                            <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
                                 <input className="presBox-input" type="number" readOnly={true} value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
                             </div>
-                            <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}>
+                            <div className="ribbon-propertyUpDown" style={{ color: SnappingManager.userBackgroundColor, background: SnappingManager.userColor }}>
                                 <div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), 1000)}>
                                     <FontAwesomeIcon icon={'caret-up'} />
                                 </div>
@@ -1798,7 +1774,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             <div className="presBox-subheading">Play Audio Annotation</div>
                             <input
                                 className="presBox-checkbox"
-                                style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+                                style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
                                 type="checkbox"
                                 onChange={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))}
                                 checked={BoolCast(activeItem.presPlayAudio)}
@@ -1808,7 +1784,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             <div className="presBox-subheading">Zoom Text Selections</div>
                             <input
                                 className="presBox-checkbox"
-                                style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }}
+                                style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
                                 type="checkbox"
                                 onChange={() => (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))}
                                 checked={BoolCast(activeItem.presentation_zoomText)}
@@ -1821,10 +1797,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 this._openEffectDropdown = !this._openEffectDropdown;
                             })}
                             style={{
-                                color: SettingsManager.userColor,
-                                background: SettingsManager.userVariantColor,
+                                color: SnappingManager.userColor,
+                                background: SnappingManager.userVariantColor,
                                 borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5,
-                                border: this._openEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
+                                border: this._openEffectDropdown ? `solid 2px ${SettingsSnappingManagerManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
                             }}>
                             {effect?.toString()}
                             <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
@@ -1832,8 +1808,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 className="presBox-dropdownOptions"
                                 id={'presBoxMovementDropdown'}
                                 style={{
-                                    color: SettingsManager.userColor,
-                                    background: SettingsManager.userBackgroundColor,
+                                    color: SnappingManager.userColor,
+                                    background: SnappingManager.userBackgroundColor,
                                     display: this._openEffectDropdown ? 'grid' : 'none',
                                 }}
                                 onPointerDown={e => e.stopPropagation()}>
@@ -1844,7 +1820,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         </div>
                         <div className="ribbon-doubleButton" style={{ display: effect === PresEffectDirection.None ? 'none' : 'inline-flex' }}>
                             <div className="presBox-subheading">Effect direction</div>
-                            <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
+                            <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
                                 {StrCast(this.activeItem.presentation_effectDirection)}
                             </div>
                         </div>
@@ -1882,7 +1858,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     <div className="slider-text" style={{ fontWeight: 500 }}>
                                         Start time (s)
                                     </div>
-                                    <div id="startTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
+                                    <div id="startTime" className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}>
                                         <input
                                             className="presBox-input"
                                             style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
@@ -1898,7 +1874,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     <div className="slider-text" style={{ fontWeight: 500 }}>
                                         Duration (s)
                                     </div>
-                                    <div className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
+                                    <div className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}>
                                         {Math.round((config_clipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10}
                                     </div>
                                 </div>
@@ -1906,7 +1882,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     <div className="slider-text" style={{ fontWeight: 500 }}>
                                         End time (s)
                                     </div>
-                                    <div id="endTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
+                                    <div id="endTime" className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}>
                                         <input
                                             className="presBox-input"
                                             onKeyDown={e => e.stopPropagation()}
@@ -1926,14 +1902,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     min={clipStart}
                                     max={clipEnd}
                                     value={config_clipEnd}
-                                    style={{ gridColumn: 1, gridRow: 1, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }}
+                                    style={{ gridColumn: 1, gridRow: 1, background: SnappingManager.userColor, color: SnappingManager.userVariantColor }}
                                     className={`toolbar-slider ${'end'}`}
                                     id="toolbar-slider"
                                     onPointerDown={e => {
                                         this._batch = UndoManager.StartBatch('config_clipEnd');
                                         const endBlock = document.getElementById('endTime');
                                         if (endBlock) {
-                                            endBlock.style.backgroundColor = SettingsManager.userVariantColor;
+                                            endBlock.style.backgroundColor = SnappingManager.userVariantColor;
                                         }
                                         e.stopPropagation();
                                     }}
@@ -1941,7 +1917,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         this._batch?.end();
                                         const endBlock = document.getElementById('endTime');
                                         if (endBlock) {
-                                            endBlock.style.backgroundColor = SettingsManager.userBackgroundColor;
+                                            endBlock.style.backgroundColor = SnappingManager.userBackgroundColor;
                                         }
                                     }}
                                     onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1962,7 +1938,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         this._batch = UndoManager.StartBatch('config_clipStart');
                                         const startBlock = document.getElementById('startTime');
                                         if (startBlock) {
-                                            startBlock.style.backgroundColor = SettingsManager.userVariantColor;
+                                            startBlock.style.backgroundColor = SnappingManager.userVariantColor;
                                         }
                                         e.stopPropagation();
                                     }}
@@ -1970,7 +1946,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         this._batch?.end();
                                         const startBlock = document.getElementById('startTime');
                                         if (startBlock) {
-                                            startBlock.style.backgroundColor = SettingsManager.userBackgroundColor;
+                                            startBlock.style.backgroundColor = SnappingManager.userBackgroundColor;
                                         }
                                     }}
                                     onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1993,7 +1969,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     <input
                                         className="presBox-checkbox"
                                         type="checkbox"
-                                        style={{ border: `solid 1px ${SettingsManager.userColor}` }}
+                                        style={{ border: `solid 1px ${SnappingManager.userColor}` }}
                                         onChange={() => (activeItem.presentation_mediaStart = 'manual')}
                                         checked={activeItem.presentation_mediaStart === 'manual'}
                                     />
@@ -2002,7 +1978,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 <div className="checkbox-container">
                                     <input
                                         className="presBox-checkbox"
-                                        style={{ border: `solid 1px ${SettingsManager.userColor}` }}
+                                        style={{ border: `solid 1px ${SnappingManager.userColor}` }}
                                         type="checkbox"
                                         onChange={() => (activeItem.presentation_mediaStart = 'auto')}
                                         checked={activeItem.presentation_mediaStart === 'auto'}
@@ -2016,7 +1992,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     <input
                                         className="presBox-checkbox"
                                         type="checkbox"
-                                        style={{ border: `solid 1px ${SettingsManager.userColor}` }}
+                                        style={{ border: `solid 1px ${SnappingManager.userColor}` }}
                                         onChange={() => (activeItem.presentation_mediaStop = 'manual')}
                                         checked={activeItem.presentation_mediaStop === 'manual'}
                                     />
@@ -2026,7 +2002,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     <input
                                         className="presBox-checkbox"
                                         type="checkbox"
-                                        style={{ border: `solid 1px ${SettingsManager.userColor}` }}
+                                        style={{ border: `solid 1px ${SnappingManager.userColor}` }}
                                         onChange={() => (activeItem.presentation_mediaStop = 'auto')}
                                         checked={activeItem.presentation_mediaStop === 'auto'}
                                     />
@@ -2270,15 +2246,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @action
-    toggleProperties = () => (SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250);
+    toggleProperties = () => (SnappingManager.Instance.propertiesWidth = SnappingManager.Instance.propertiesWidth > 0 ? 0 : 250);
 
     @computed get toolbar() {
-        const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
-        const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
+        const propIcon = SnappingManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+        const propTitle = SnappingManager.Instance.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
         const mode = StrCast(this.Document._type_collection) as CollectionViewType;
         const isMini: boolean = this.toolbarWidth <= 100;
-        const activeColor = SettingsManager.userVariantColor;
-        const inactiveColor = lightOrDark(SettingsManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SettingsManager.userBackgroundColor;
+        const activeColor = SnappingManager.userVariantColor;
+        const inactiveColor = lightOrDark(SnappingManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SnappingManager.userBackgroundColor;
         return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.Document) ? null : (
             <div id="toolbarContainer" className={'presBox-toolbar'}>
                 {/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
@@ -2303,7 +2279,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         </Tooltip>
                         <Tooltip title={<div className="dash-tooltip">{propTitle}</div>}>
                             <div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}>
-                                <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} />
+                                <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SnappingManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} />
                             </div>
                         </Tooltip>
                     </>
@@ -2378,12 +2354,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         // Case 1: There are still other frames and should go through all frames before going to next slide
         return (
             <div className="presPanelOverlay" style={{ display: this.layoutDoc.presentation_status !== 'edit' ? 'inline-flex' : 'none' }}>
-                <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
+                <Tooltip title={<div className="dash-tooltip">Loop</div>}>
                     <div
                         className="presPanel-button"
                         style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }}
-                        onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}>
-                        <FontAwesomeIcon icon={'redo-alt'} />
+                        onPointerDown={e =>
+                            setupMoveUpEvents(
+                                this,
+                                e,
+                                returnFalse,
+                                emptyFunction,
+                                () => {
+                                    this.layoutDoc.presLoop = !this.layoutDoc.presLoop;
+                                },
+                                false,
+                                false
+                            )
+                        }>
+                        <FontAwesomeIcon icon="redo-alt" />
                     </div>
                 </Tooltip>
                 <div className="presPanel-divider" />
@@ -2617,12 +2605,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 childXPadding={Doc.IsComicStyle(this.Document) ? 20 : undefined}
                                 filterAddDocument={this.addDocumentFilter}
                                 removeDocument={returnFalse}
-                                dontRegisterView={true}
+                                dontRegisterView
                                 focus={this.focusElement}
                                 ScreenToLocalTransform={this.getTransform}
                                 AddToMap={this.AddToMap}
                                 RemFromMap={this.RemFromMap}
-                                hierarchyIndex={emptyPath as any as number[]}
+                                hierarchyIndex={emptyPath}
                             />
                         ) : null}
                     </div>
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 28139eb14..fca5a2770 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -7,7 +7,8 @@ import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction } from '../../../../Utils';
+import { returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 59f191af0..9c4080154 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -4,7 +4,8 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ColorResult } from 'react-color';
-import { Utils, returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../Utils';
+import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { unimplementedFunction } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -17,6 +18,7 @@ import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
 
 @observer
 export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: AnchorMenu;
 
     private _disposer: IReactionDisposer | undefined;
@@ -37,7 +39,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     // GPT additions
     @observable private selectedText: string = '';
     @action
-    public setSelectedText = (txt: string) => (this.selectedText = txt);
+    public setSelectedText = (txt: string) => {
+        this.selectedText = txt;
+    };
 
     public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
 
@@ -64,7 +68,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     componentDidMount() {
         this._disposer = reaction(
             () => SelectionManager.Views.slice(),
-            sel => AnchorMenu.Instance.fadeOut(true)
+            () => AnchorMenu.Instance.fadeOut(true)
         );
     }
 
@@ -72,7 +76,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * Invokes the API with the selected text and stores it in the summarized text.
      * @param e pointer down event
      */
-    gptSummarize = async (e: React.PointerEvent) => {
+    gptSummarize = async () => {
         // move this logic to gptpopup, need to implement generate again
         GPTPopup.Instance.setVisible(true);
         GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
@@ -128,7 +132,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             <Group>
                 <IconButton
                     icon={<FontAwesomeIcon icon="highlighter" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />}
-                    tooltip={'Click to Highlight'}
+                    tooltip="Click to Highlight"
                     onClick={this.highlightClicked}
                     colorPicker={this.highlightColor}
                     color={SettingsManager.userColor}
@@ -144,7 +148,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             hsl: { a: 0, h: 0, s: 0, l: 0 },
             rgb: { a: 0, r: 0, b: 0, g: 0 },
         };
-        this.highlightColor = Utils.colorString(col);
+        this.highlightColor = ClientUtils.colorString(col);
     };
 
     /**
@@ -167,7 +171,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             color={SettingsManager.userColor}
                         />
                     </div>
-                    {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
+                    {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */}
                     {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
                         <IconButton
                             tooltip="Summarize with AI" //
@@ -187,7 +191,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                     <Popup
                         tooltip="Find document to link to selected text" //
                         type={Type.PRIM}
-                        icon={<FontAwesomeIcon icon={'search'} />}
+                        icon={<FontAwesomeIcon icon="search" />}
                         popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />}
                         color={SettingsManager.userColor}
                     />
@@ -230,7 +234,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                     )}
                     {this.IsTargetToggler !== returnFalse && (
                         <Toggle
-                            tooltip={'Make target visibility toggle on click'}
+                            tooltip="Make target visibility toggle on click"
                             type={Type.PRIM}
                             toggleType={ToggleType.BUTTON}
                             toggleStatus={this.IsTargetToggler()}
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index da8a88803..cd13d4cbc 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -6,7 +6,7 @@ import * as React from 'react';
 import { CgClose } from 'react-icons/cg';
 import ReactLoading from 'react-loading';
 import { TypeAnimation } from 'react-type-animation';
-import { Utils } from '../../../../Utils';
+import { ClientUtils } from '../../../../ClientUtils';
 import { Doc } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { Networking } from '../../../Network';
@@ -26,6 +26,7 @@ interface GPTPopupProps {}
 
 @observer
 export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: GPTPopup;
 
     @observable
@@ -71,8 +72,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     @observable
     public highlightRange: number[] = [];
     @action callSummaryApi = () => {};
-    @action callEditApi = () => {};
-    @action replaceText = (replacement: string) => {};
 
     @observable
     private done: boolean = false;
@@ -110,24 +109,25 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
      * Generates a Dalle image and uploads it to the server.
      */
     generateImage = async () => {
-        if (this.imgDesc === '') return;
+        if (this.imgDesc === '') return undefined;
         this.setImgUrls([]);
         this.setMode(GPTPopupMode.IMAGE);
         this.setVisible(true);
         this.setLoading(true);
 
         try {
-            let image_urls = await gptImageCall(this.imgDesc);
-            if (image_urls && image_urls[0]) {
-                const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
-                const source = Utils.prepend(result.accessPaths.agnostic.client);
-                this.setImgUrls([[image_urls[0], source]]);
+            const imageUrls = await gptImageCall(this.imgDesc);
+            if (imageUrls && imageUrls[0]) {
+                const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [imageUrls[0]] });
+                const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
+                this.setImgUrls([[imageUrls[0], source]]);
             }
         } catch (err) {
             console.log(err);
             return '';
         }
         this.setLoading(false);
+        return undefined;
     };
 
     /**
@@ -188,55 +188,43 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         }
     };
 
-    imageBox = () => {
-        return (
-            <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
-                {this.heading('GENERATED IMAGE')}
-                <div className="image-content-wrapper">
-                    {this.imgUrls.map(rawSrc => (
-                        <div className="img-wrapper">
-                            <div className="img-container">
-                                <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
-                            </div>
-                            <div className="btn-container">
-                                <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
-                            </div>
+    imageBox = () => (
+        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+            {this.heading('GENERATED IMAGE')}
+            <div className="image-content-wrapper">
+                {this.imgUrls.map(rawSrc => (
+                    <div className="img-wrapper">
+                        <div className="img-container">
+                            <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
                         </div>
-                    ))}
-                </div>
-                {!this.loading && (
-                    <>
-                        <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
-                    </>
-                )}
+                        <div className="btn-container">
+                            <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
+                        </div>
+                    </div>
+                ))}
             </div>
-        );
-    };
+            {!this.loading && <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />}
+        </div>
+    );
 
-    data = () => {
-        return (
-            <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
-                {this.heading('GENERATED IMAGE')}
-                <div className="image-content-wrapper">
-                    {this.imgUrls.map(rawSrc => (
-                        <div className="img-wrapper">
-                            <div className="img-container">
-                                <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
-                            </div>
-                            <div className="btn-container">
-                                <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
-                            </div>
+    data = () => (
+        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+            {this.heading('GENERATED IMAGE')}
+            <div className="image-content-wrapper">
+                {this.imgUrls.map(rawSrc => (
+                    <div className="img-wrapper">
+                        <div className="img-container">
+                            <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
                         </div>
-                    ))}
-                </div>
-                {!this.loading && (
-                    <>
-                        <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
-                    </>
-                )}
+                        <div className="btn-container">
+                            <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
+                        </div>
+                    </div>
+                ))}
             </div>
-        );
-    };
+            {!this.loading && <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />}
+        </div>
+    );
 
     summaryBox = () => (
         <>
@@ -255,7 +243,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                                         }, 500);
                                     },
                                 ]}
-                                //cursor={{ hideWhenDone: true }}
+                                // cursor={{ hideWhenDone: true }}
                             />
                         ) : (
                             this.text
@@ -294,9 +282,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                 <FontAwesomeIcon icon="exclamation-circle" size="sm" style={{ paddingRight: '5px' }} />
                 AI generated responses can contain inaccurate or misleading content.
             </div>
-        ) : (
-            <></>
-        );
+        ) : null;
 
     heading = (headingText: string) => (
         <div className="summary-heading">
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index aaff2a342..0ab952e84 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -10,7 +10,8 @@ import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils';
 import { DocUtils } from '../../documents/Documents';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -98,9 +99,13 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     }
 
     componentDidMount() {
-        runInAction(() => (this._showWaiting = true));
+        runInAction(() => {
+            this._showWaiting = true;
+        });
         this.setupPdfJsViewer();
-        this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0));
+        this._mainCont.current?.addEventListener('scroll', e => {
+            (e.target as any).scrollLeft = 0;
+        });
 
         this._disposers.layout_autoHeight = reaction(
             () => this._props.layoutDoc._layout_autoHeight,
@@ -176,7 +181,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         let focusSpeed: Opt<number>;
         if (doc !== this._props.Document && mainCont) {
             const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
+            const scrollTo = ClientUtils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
             if (scrollTo !== undefined && scrollTo !== this._props.layoutDoc._layout_scrollTop) {
                 if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc };
                 else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper);
@@ -456,7 +461,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
     onClick = (e: React.MouseEvent) => {
         this._scrollStopper?.();
-        if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+        if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < ClientUtils.DRAG_THRESHOLD) {
             this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
         }
         // e.stopPropagation();  // bcz: not sure why this was here.  We need to allow the DocumentView to get clicks to process doubleClicks
@@ -496,8 +501,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     overlayTransform = () => this.scrollXf().scale(1 / NumCast(this._props.layoutDoc._freeform_scale, 1));
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1);
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
-    transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
-    opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
+    transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this._props.childFilters(), ClientUtils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [ClientUtils.OpaqueBackgroundFilter])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
@@ -532,7 +537,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 PanelWidth={this.panelWidth}
                 ScreenToLocalTransform={this.overlayTransform}
                 isAnyChildContentActive={returnFalse}
-                isAnnotationOverlayScrollable={true}
+                isAnnotationOverlayScrollable
                 childFilters={childFilters}
                 select={emptyFunction}
                 styleProvider={this.childStyleProvider}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 9f153e86d..af9f05a14 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -2,7 +2,7 @@ import { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCastAsync, Field } from '../../../fields/Doc';
+import { Doc, DocListCastAsync, Field, FieldType } from '../../../fields/Doc';
 import { DirectLinks, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, StrCast } from '../../../fields/Types';
@@ -145,7 +145,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
                     .filter(d => d)
                     .map(async d => {
                         const fieldKey = Doc.LayoutFieldKey(d);
-                        const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
+                        const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView');
                         const data = d[annos ? fieldKey + '_annotations' : fieldKey];
                         const docs = await DocListCastAsync(data);
                         docs && newarray.push(...docs);
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index eab33114e..b87e5cdde 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -5,14 +5,14 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Flip } from 'react-awesome-reveal';
 import { FaBug } from 'react-icons/fa';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { AclAdmin, DashVersion } from '../../../fields/DocSymbols';
 import { StrCast } from '../../../fields/Types';
 import { GetEffectiveAcl } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { DocumentManager } from '../../util/DocumentManager';
-import { dropActionType } from '../../util/DragManager';
 import { PingManager } from '../../util/PingManager';
 import { ReportManager } from '../../util/reportManager/ReportManager';
 import { ServerStats } from '../../util/ServerStats';
@@ -28,6 +28,7 @@ import { DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentV
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider } from '../StyleProvider';
 import './TopBar.scss';
+import { dropActionType } from '../../util/DropActionTypes';
 
 /**
  * ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index ceff01fc2..fc62463e6 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -8,7 +8,7 @@ import { CompileScript } from '../client/util/Scripting';
 import { EditableView } from '../client/views/EditableView';
 import CursorField from '../fields/CursorField';
 import { DateField } from '../fields/DateField';
-import { Doc, Field, FieldResult } from '../fields/Doc';
+import { Doc, Field, FieldType, FieldResult } from '../fields/Doc';
 import { Id } from '../fields/FieldSymbols';
 import { List } from '../fields/List';
 import { RichTextField } from '../fields/RichTextField';
@@ -39,7 +39,7 @@ configure({
 });
 
 @observer
-class ListViewer extends React.Component<{ field: List<Field> }> {
+class ListViewer extends React.Component<{ field: List<FieldType> }> {
     @observable
     expanded = false;
 
@@ -144,7 +144,7 @@ class Viewer extends React.Component {
     private idToAdd: string = '';
 
     @observable
-    private fields: Field[] = [];
+    private fields: FieldType[] = [];
 
     @action
     inputOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts
index 1e5222fb6..56a3177f8 100644
--- a/src/fields/DateField.ts
+++ b/src/fields/DateField.ts
@@ -1,5 +1,5 @@
-import { Deserializable } from '../client/util/SerializationHelper';
 import { serializable, date } from 'serializr';
+import { Deserializable } from '../client/util/SerializationHelper';
 import { ObjectField } from './ObjectField';
 import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
@@ -38,6 +38,7 @@ export class DateField extends ObjectField {
     }
 }
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function d(...dateArgs: any[]) {
     return new DateField(new (Date as any)(...dateArgs));
 });
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 48214cf25..60c6402d4 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,26 +1,20 @@
-import { saveAs } from 'file-saver';
 import { action, computed, makeObservable, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { alias, map, serializable } from 'serializr';
 import { DocServer } from '../client/DocServer';
 import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
-import { LinkManager } from '../client/util/LinkManager';
 import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper';
 import { undoable } from '../client/util/UndoManager';
-import { DocumentView } from '../client/views/nodes/DocumentView';
-import { decycle } from '../decycler/decycler';
-import * as JSZipUtils from '../JSZipUtils';
-import { incrementTitleCopy, Utils } from '../Utils';
-import { DateField } from './DateField';
+import { ClientUtils, incrementTitleCopy } from '../ClientUtils';
 import {
     AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks,
     DocAcl, DocCss, DocData, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
     Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width
 } from './DocSymbols'; // prettier-ignore
 import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
-import { InkField, InkTool } from './InkField';
-import { List, ListFieldName } from './List';
+import { InkTool } from './InkField';
+import { List } from './List';
 import { ObjectField } from './ObjectField';
 import { PrefetchProxy, ProxyField } from './Proxy';
 import { FieldId, RefField } from './RefField';
@@ -28,10 +22,8 @@ import { RichTextField } from './RichTextField';
 import { listSpec } from './Schema';
 import { ComputedField, ScriptField } from './ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
-import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
 import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util';
-import * as JSZip from 'jszip';
-import { FieldViewProps } from '../client/views/nodes/FieldView';
+
 export const LinkedTo = '-linkedTo';
 export namespace Field {
     /**
@@ -45,7 +37,7 @@ export namespace Field {
     export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean): string {
         const onDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
         const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-        const valFunc = (field: Field): string => {
+        const valFunc = (field: FieldType): string => {
             const res =
                 field instanceof ComputedField && showComputedValue
                     ? field.value(doc)
@@ -63,7 +55,7 @@ export namespace Field {
         };
         return !Field.IsField(field) ? (key.startsWith('_') ? '=' : '') : (onDelegate ? '=' : '') + valFunc(field);
     }
-    export function toScriptString(field: Field) {
+    export function toScriptString(field: FieldType) {
         switch (typeof field) {
             case 'string': if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ".  In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s
                            return !field.includes('`') ? `\`${field}\`` : `"${field}"`;
@@ -72,8 +64,8 @@ export namespace Field {
             default:       return field?.[ToScriptString]?.() ?? 'null';
         } // prettier-ignore
     }
-    export function toJavascriptString(field: Field) {
-        var rawjava = '';
+    export function toJavascriptString(field: FieldType) {
+        let rawjava = '';
 
         switch (typeof field) {
             case 'string':
@@ -82,7 +74,7 @@ export namespace Field {
                            break;
             default:       rawjava = field?.[ToJavascriptString]?.() ?? '';
         } // prettier-ignore
-        var script = rawjava;
+        let script = rawjava;
         // this is a bit hacky, but we treat '^@' references to a published document
         // as a kind of macro to include the content of those documents
         Doc.MyPublishedDocs.forEach(doc => {
@@ -95,23 +87,23 @@ export namespace Field {
         });
         return script;
     }
-    export function toString(field: Field) {
+    export function toString(field: FieldType) {
         if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field);
         return field?.[ToString]?.() || '';
     }
-    export function IsField(field: any): field is Field;
-    export function IsField(field: any, includeUndefined: true): field is Field | undefined;
-    export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined {
+    export function IsField(field: any): field is FieldType;
+    export function IsField(field: any, includeUndefined: true): field is FieldType | undefined;
+    export function IsField(field: any, includeUndefined: boolean = false): field is FieldType | undefined {
         return ['string', 'number', 'boolean'].includes(typeof field) || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined);
     }
     export function Copy(field: any) {
         return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
     }
 }
-export type Field = number | string | boolean | ObjectField | RefField;
+export type FieldType = number | string | boolean | ObjectField | RefField;
 export type Opt<T> = T | undefined;
 export type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>;
-export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
+export type FieldResult<T extends FieldType = FieldType> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
 
 /**
  * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.
@@ -159,7 +151,7 @@ export function updateCachedAcls(doc: Doc) {
     if (!doc) return;
 
     const target = (doc as any)?.__fieldTuples ?? doc;
-    const permissions: { [key: string]: symbol } = !target.author || target.author === Doc.CurrentUserEmail ? { 'acl-Me': AclAdmin } : {};
+    const permissions: { [key: string]: symbol } = !target.author || target.author === ClientUtils.CurrentUserEmail ? { 'acl-Me': AclAdmin } : {};
     Object.keys(target).filter(key => key.startsWith('acl') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl));
     if (Object.keys(permissions).length || doc[DocAcl]?.length) {
         runInAction(() => (doc[DocAcl] = permissions));
@@ -178,7 +170,8 @@ export class Doc extends RefField {
     @observable public static GuestDashboard: Doc | undefined = undefined;
     @observable public static GuestTarget: Doc | undefined = undefined;
     @observable public static GuestMobile: Doc | undefined = undefined;
-    public static CurrentUserEmail: string = '';
+
+    public static AddLink: undefined | ((link: Doc, checkExists?: boolean) => void);
 
     public static get MySharedDocs()          { return DocCast(Doc.UserDoc().mySharedDocs); } // prettier-ignore
     public static get MyUserDocView()         { return DocCast(Doc.UserDoc().myUserDocView); } // prettier-ignore
@@ -320,7 +313,7 @@ export class Doc extends RefField {
     @observable public [Animation]: Opt<Doc> = undefined;
     @observable public [Highlight]: boolean = false;
     @observable public [Brushed]: boolean = false;
-    @observable public [DocViews] = new ObservableSet<DocumentView>();
+    @observable public [DocViews] = new ObservableSet<any /* DocumentView */>();
 
     private [Self] = this;
     private [SelfProxy]: any;
@@ -329,7 +322,7 @@ export class Doc extends RefField {
     private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
 
     public [Initializing]: boolean = false;
-    public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
+    public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: FieldType[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
         if (!this[UpdatingFromServer] || this[ForceServerWrite]) {
             DocServer.UpdateField(this[Id], serverOp);
         }
@@ -363,7 +356,7 @@ export class Doc extends RefField {
 
     public async [HandleUpdate](diff: any) {
         const set = diff.$set;
-        const sameAuthor = this.author === Doc.CurrentUserEmail;
+        const sameAuthor = this.author === ClientUtils.CurrentUserEmail;
         if (set) {
             for (const key in set) {
                 const fprefix = 'fields.';
@@ -454,7 +447,7 @@ export namespace Doc {
             return doc;
         }
     }
-    export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
+    export function GetT<T extends FieldType>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
         return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
     }
     export function isTemplateDoc(doc: Doc) {
@@ -482,7 +475,7 @@ export namespace Doc {
     // 2) if the data doc has the field, then it's written there.
     // 3) if neither already has the field, then 'defaultProto' determines whether to write it to the data doc (or the embedding)
     //
-    export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
+    export async function SetInPlace(doc: Doc, key: string, value: FieldType | undefined, defaultProto: boolean) {
         if (key.startsWith('_')) key = key.substring(1);
         const hasProto = doc[DocData] !== doc ? doc[DocData] : undefined;
         const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
@@ -500,7 +493,6 @@ export namespace Doc {
         }
         return protos;
     }
-
     /**
      * This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies
      * the values of the properties of a source object into the target.
@@ -512,7 +504,7 @@ export namespace Doc {
      * @param fields the fields to project onto the target. Its type signature defines a mapping from some string key
      * to a potentially undefined field, where each entry in this mapping is optional.
      */
-    export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>, skipUndefineds: boolean = false, isInitializing = false) {
+    export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<FieldType>>>, skipUndefineds: boolean = false, isInitializing = false) {
         isInitializing && (doc[Initializing] = true);
         for (const key in fields) {
             if (fields.hasOwnProperty(key)) {
@@ -634,7 +626,7 @@ export namespace Doc {
         embedding.createdFrom = doc;
         embedding.proto_embeddingId = doc[DocData].proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
         !Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`));
-        embedding.author = Doc.CurrentUserEmail;
+        embedding.author = ClientUtils.CurrentUserEmail;
 
         return embedding;
     }
@@ -642,7 +634,7 @@ export namespace Doc {
     export function BestEmbedding(doc: Doc) {
         const dataDoc = doc[DocData];
         const availableEmbeddings = Doc.GetEmbeddings(dataDoc);
-        const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail);
+        const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(doc => !doc.embedContainer && doc.author === ClientUtils.CurrentUserEmail);
         bestEmbedding && Doc.AddEmbedding(doc, doc);
         return bestEmbedding ?? Doc.MakeEmbedding(doc);
     }
@@ -700,7 +692,7 @@ export namespace Doc {
                 };
                 const docAtKey = doc[key];
                 if (key === 'author') {
-                    assignKey(Doc.CurrentUserEmail);
+                    assignKey(ClientUtils.CurrentUserEmail);
                 } else if (docAtKey instanceof Doc) {
                     if (pruneDocs.includes(docAtKey)) {
                         // prune doc and do nothing
@@ -709,7 +701,7 @@ export namespace Doc {
                         (key.includes('layout[') ||
                             key.startsWith('layout') || //
                             ['embedContainer', 'annotationOn', 'proto'].includes(key) ||
-                            (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === Doc.CurrentUserEmail))
+                            (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === ClientUtils.CurrentUserEmail))
                     ) {
                         assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
                     } else {
@@ -771,7 +763,7 @@ export namespace Doc {
         const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.embedContainer ? [DocCast(doc.embedContainer)] : [], cloneLinks, cloneTemplates);
         const repaired = new Set<Doc>();
         const linkedDocs = Array.from(linkMap.values());
-        linkedDocs.map((link: Doc) => LinkManager.Instance.addLink(link, true));
+        linkedDocs.map(link => Doc.AddLink?.(link, true));
         rtfMap.map(({ copy, key, field }) => {
             const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
                 const mapped = cloneMap.get(id);
@@ -790,84 +782,6 @@ export namespace Doc {
         return { clone: copy, map: cloneMap, linkMap };
     }
 
-    export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
-        const { clone, map, linkMap } = await Doc.MakeClone(doc);
-        const proms = new Set<string>();
-        function replacer(key: any, value: any) {
-            if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
-            if (value?.__type === 'image') {
-                const extension = value.url.replace(/.*\./, '');
-                proms.add(value.url.replace('.' + extension, '_o.' + extension));
-                return SerializationHelper.Serialize(new ImageField(value.url));
-            }
-            if (value?.__type === 'pdf') {
-                proms.add(value.url);
-                return SerializationHelper.Serialize(new PdfField(value.url));
-            }
-            if (value?.__type === 'audio') {
-                proms.add(value.url);
-                return SerializationHelper.Serialize(new AudioField(value.url));
-            }
-            if (value?.__type === 'video') {
-                proms.add(value.url);
-                return SerializationHelper.Serialize(new VideoField(value.url));
-            }
-            if (
-                value instanceof Doc ||
-                value instanceof ScriptField ||
-                value instanceof RichTextField ||
-                value instanceof InkField ||
-                value instanceof CsvField ||
-                value instanceof WebField ||
-                value instanceof DateField ||
-                value instanceof ProxyField ||
-                value instanceof ComputedField
-            ) {
-                return SerializationHelper.Serialize(value);
-            }
-            if (value instanceof Array && key !== ListFieldName && key !== InkField.InkDataFieldName) return { fields: value, __type: 'list' };
-            return value;
-        }
-
-        const docs: { [id: string]: any } = {};
-        const links: { [id: string]: any } = {};
-        Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1]));
-        Array.from(linkMap.entries()).forEach(l => (links[l[0]] = l[1]));
-        const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer));
-
-        const zip = new JSZip();
-        var count = 0;
-        const promArr = Array.from(proms)
-            .filter(url => url?.startsWith('/files'))
-            .map(url => url.replace('/', '')); // window.location.origin));
-        console.log(promArr.length);
-        if (!promArr.length) {
-            zip.file('docs.json', jsonDocs);
-            zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
-        } else
-            promArr.forEach((url, i) => {
-                // loading a file and add it in a zip file
-                JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => {
-                    if (err) throw err; // or handle the error
-                    // // Generate a directory within the Zip file structure
-                    // const assets = zip.folder("assets");
-                    // assets.file(filename, data, {binary: true});
-                    const assetPathOnServer = promArr[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%');
-                    zip.file(assetPathOnServer, data, { binary: true });
-                    console.log(' => ' + url);
-                    if (++count === promArr.length) {
-                        zip.file('docs.json', jsonDocs);
-                        zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
-                        // const a = document.createElement("a");
-                        // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
-                        // a.href = url;
-                        // a.download = `DocExport-${this.props.Document[Id]}.zip`;
-                        // a.click();
-                    }
-                });
-            });
-    }
-
     const _pendingMap = new Set<string>();
     //
     // Returns an expanded template layout for a target data document if there is a template relationship
@@ -954,7 +868,7 @@ export namespace Doc {
                 }
             } else {
                 const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-                const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
+                const field = key === 'author' ? ClientUtils.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
                 if (field instanceof RefField) {
                     if (field instanceof Doc) {
                         if (key === 'myLinkDatabase') {
@@ -1000,7 +914,7 @@ export namespace Doc {
                     }
                 } else {
                     const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-                    const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
+                    const field = key === 'author' ? ClientUtils.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
                     if (field instanceof RefField) {
                         copy[key] = field;
                     } else if (cfield instanceof ComputedField) {
@@ -1038,7 +952,7 @@ export namespace Doc {
             delegate[Initializing] = true;
             updateCachedAcls(delegate);
             delegate.proto = doc;
-            delegate.author = Doc.CurrentUserEmail;
+            delegate.author = ClientUtils.CurrentUserEmail;
             Object.keys(doc)
                 .filter(key => key.startsWith('acl'))
                 .forEach(key => (delegate[key] = doc[key]));
@@ -1067,7 +981,7 @@ export namespace Doc {
     export function ApplyTemplate(templateDoc: Doc) {
         if (templateDoc) {
             const proto = new Doc();
-            proto.author = Doc.CurrentUserEmail;
+            proto.author = ClientUtils.CurrentUserEmail;
             const target = Doc.MakeDelegate(proto);
             const targetKey = StrCast(templateDoc.layout_fieldKey, 'layout');
             const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')');
@@ -1123,7 +1037,7 @@ export namespace Doc {
 
     // converts a document id to a url path on the server
     export function globalServerPath(doc: Doc | string = ''): string {
-        return Utils.prepend('/doc/' + (doc instanceof Doc ? doc[Id] : doc));
+        return ClientUtils.prepend('/doc/' + (doc instanceof Doc ? doc[Id] : doc));
     }
     // converts a document id to a url path on the server
     export function localServerPath(doc?: Doc): string {
@@ -1426,20 +1340,6 @@ export namespace Doc {
         });
     }
 
-    export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: number) {
-        const style: { [key: string]: any } = {};
-        const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
-        const replacer = (match: any, expr: string, offset: any, string: any) => {
-            // bcz: this executes a script to convert a property expression string:  { script }  into a value
-            return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? '';
-        };
-        divKeys.map((prop: string) => {
-            const p = (props as any)[prop];
-            typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
-        });
-        return style;
-    }
-
     export function Paste(docids: string[], clone: boolean, addDocument: (doc: Doc | Doc[]) => boolean, ptx?: number, pty?: number, newPoint?: number[]) {
         DocServer.GetRefFields(docids).then(async fieldlist => {
             const list = Array.from(Object.values(fieldlist))
@@ -1505,7 +1405,7 @@ export namespace Doc {
     // If they are not remapped, loading the file will overwrite any existing documents with those ids
     //
     export async function importDocument(file: File, remap = false) {
-        const upload = Utils.prepend('/uploadDoc');
+        const upload = ClientUtils.prepend('/uploadDoc');
         const formData = new FormData();
         if (file) {
             formData.append('file', file);
@@ -1518,7 +1418,7 @@ export namespace Doc {
                 const links = await DocServer.GetRefFields(json.linkids as string[]);
                 Array.from(Object.keys(links))
                     .map(key => links[key])
-                    .forEach(link => link instanceof Doc && LinkManager.Instance.addLink(link));
+                    .forEach(link => link instanceof Doc && Doc.AddLink?.(link));
                 return doc;
             }
         }
@@ -1617,7 +1517,7 @@ export namespace Doc {
             if (hasEntries || !excludeEmptyObjects) {
                 const resolved = target ?? new Doc();
                 if (hasEntries) {
-                    let result: Opt<Field>;
+                    let result: Opt<FieldType>;
                     Object.keys(object).map(key => {
                         // if excludeEmptyObjects is true, any qualifying conversions from toField will
                         // be undefined, and thus the results that would have
@@ -1639,9 +1539,9 @@ export namespace Doc {
          * @returns the list mapped from JSON to field values, where each mapping
          * might involve arbitrary recursion (since toField might itself call convertList)
          */
-        const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<Field>> => {
+        const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<FieldType>> => {
             const target = new List();
-            let result: Opt<Field>;
+            let result: Opt<FieldType>;
             // if excludeEmptyObjects is true, any qualifying conversions from toField will
             // be undefined, and thus the results that would have
             // otherwise been empty (List or Doc)s will just not be written
@@ -1651,7 +1551,7 @@ export namespace Doc {
             }
         };
 
-        const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<Field> => {
+        const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<FieldType> => {
             if (data === null || data === undefined) {
                 return undefined;
             }
@@ -1719,7 +1619,7 @@ ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, ran
     Doc.setDocRangeFilter(container, key, range);
 });
 ScriptingGlobals.add(function toJavascriptString(str: string) {
-    return Field.toJavascriptString(str as Field);
+    return Field.toJavascriptString(str as FieldType);
 });
 ScriptingGlobals.add(function RtfField() {
     return RichTextField.RTFfield();
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index b3e01229a..c4f5f7a24 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -2,6 +2,7 @@ import { Bezier } from 'bezier-js';
 import { alias, createSimpleSchema, list, object, serializable } from 'serializr';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { Deserializable } from '../client/util/SerializationHelper';
+import { PointData } from '../pen-gestures/GestureTypes';
 import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 import { ObjectField } from './ObjectField';
 
@@ -16,12 +17,6 @@ export enum InkTool {
     PresentationPin = 'presentationpin',
 }
 
-// Defines a point in an ink as a pair of x- and y-coordinates.
-export interface PointData {
-    X: number;
-    Y: number;
-}
-
 export type Segment = Array<Bezier>;
 
 // Defines an ink as an array of points.
@@ -62,10 +57,10 @@ const strokeDataSchema = createSimpleSchema({
     '*': true,
 });
 
+export const InkDataFieldName = '__inkData';
 @Deserializable('ink')
 export class InkField extends ObjectField {
-    public static InkDataFieldName = '__inkData';
-    @serializable(alias(InkField.InkDataFieldName, list(object(strokeDataSchema))))
+    @serializable(alias(InkDataFieldName, list(object(strokeDataSchema))))
     readonly inkData: InkData;
 
     constructor(data: InkData) {
diff --git a/src/fields/List.ts b/src/fields/List.ts
index ec31f7dae..f6e0473ea 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,31 +1,30 @@
 import { action, computed, makeObservable, observable } from 'mobx';
 import { alias, list, serializable } from 'serializr';
-import { DocServer } from '../client/DocServer';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { Deserializable, afterDocDeserialize, autoObject } from '../client/util/SerializationHelper';
-import { Field } from './Doc';
+import { Field, FieldType } from './Doc';
 import { FieldTuples, Self, SelfProxy } from './DocSymbols';
 import { Copy, FieldChanged, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
-import { ObjectField } from './ObjectField';
+import { ObjGetRefFields, ObjectField } from './ObjectField';
 import { ProxyField } from './Proxy';
 import { RefField } from './RefField';
 import { listSpec } from './Schema';
 import { Cast } from './Types';
 import { containedFieldChangedHandler, deleteProperty, getter, setter } from './util';
 
-function toObjectField(field: Field) {
+function toObjectField(field: FieldType) {
     return field instanceof RefField ? new ProxyField(field) : field;
 }
 
-function toRealField(field: Field) {
+function toRealField(field: FieldType) {
     return field instanceof ProxyField ? field.value : field;
 }
 
-type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
+type StoredType<T extends FieldType> = T extends RefField ? ProxyField<T> : T;
 
 export const ListFieldName = 'fields';
 @Deserializable('list')
-class ListImpl<T extends Field> extends ObjectField {
+class ListImpl<T extends FieldType> extends ObjectField {
     static listHandlers: any = {
         /// Mutator methods
         copyWithin() {
@@ -44,14 +43,14 @@ class ListImpl<T extends Field> extends ObjectField {
             this[SelfProxy][FieldChanged]?.();
             return field;
         },
-        push: action(function (this: ListImpl<any>, ...items: any[]) {
-            items = items.map(toObjectField);
+        push: action(function (this: ListImpl<any>, ...itemsIn: any[]) {
+            const items = itemsIn.map(toObjectField);
 
             const list = this[Self];
-            const length = list.__fieldTuples.length;
+            const { length } = list.__fieldTuples;
             for (let i = 0; i < items.length; i++) {
                 const item = items[i];
-                //TODO Error checking to make sure parent doesn't already exist
+                // TODO Error checking to make sure parent doesn't already exist
                 if (item instanceof ObjectField) {
                     item[Parent] = list;
                     item[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], i + length, item);
@@ -77,21 +76,21 @@ class ListImpl<T extends Field> extends ObjectField {
             this[SelfProxy][FieldChanged]?.();
             return res;
         },
-        splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
+        splice: action(function (this: any, start: number, deleteCount: number, ...itemsIn: any[]) {
             this[Self].__realFields; // coerce retrieving entire array
-            items = items.map(toObjectField);
+            const items = itemsIn.map(toObjectField);
             const list = this[Self];
             const removed = list.__fieldTuples.filter((item: any, i: number) => i >= start && i < start + deleteCount);
             for (let i = 0; i < items.length; i++) {
                 const item = items[i];
-                //TODO Error checking to make sure parent doesn't already exist
-                //TODO Need to change indices of other fields in array
+                // TODO Error checking to make sure parent doesn't already exist
+                // TODO Need to change indices of other fields in array
                 if (item instanceof ObjectField) {
                     item[Parent] = list;
                     item[FieldChanged] = containedFieldChangedHandler(this, i + start, item);
                 }
             }
-            let hintArray: { val: any; index: number }[] = [];
+            const hintArray: { val: any; index: number }[] = [];
             for (let i = start; i < start + deleteCount; i++) {
                 hintArray.push({ val: list.__fieldTuples[i], index: i });
             }
@@ -107,13 +106,13 @@ class ListImpl<T extends Field> extends ObjectField {
             );
             return res.map(toRealField);
         }),
-        unshift(...items: any[]) {
-            items = items.map(toObjectField);
+        unshift(...itemsIn: any[]) {
+            const items = itemsIn.map(toObjectField);
             const list = this[Self];
             for (let i = 0; i < items.length; i++) {
                 const item = items[i];
-                //TODO Error checking to make sure parent doesn't already exist
-                //TODO Need to change indices of other fields in array
+                // TODO Error checking to make sure parent doesn't already exist
+                // TODO Need to change indices of other fields in array
                 if (item instanceof ObjectField) {
                     item[Parent] = list;
                     item[FieldChanged] = containedFieldChangedHandler(this, i, item);
@@ -131,9 +130,8 @@ class ListImpl<T extends Field> extends ObjectField {
         includes(valueToFind: any, fromIndex: number) {
             if (valueToFind instanceof RefField) {
                 return this[Self].__realFields.includes(valueToFind, fromIndex);
-            } else {
-                return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
             }
+            return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
         },
         indexOf(valueToFind: any, fromIndex: number) {
             if (valueToFind instanceof RefField) {
@@ -151,9 +149,8 @@ class ListImpl<T extends Field> extends ObjectField {
         lastIndexOf(valueToFind: any, fromIndex: number) {
             if (valueToFind instanceof RefField) {
                 return this[Self].__realFields.lastIndexOf(valueToFind, fromIndex);
-            } else {
-                return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
             }
+            return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
         },
         slice(begin: number, end: number) {
             this[Self].__realFields;
@@ -244,7 +241,7 @@ class ListImpl<T extends Field> extends ObjectField {
             getOwnPropertyDescriptor: (target, prop) => {
                 if (prop in target[FieldTuples]) {
                     return {
-                        configurable: true, //TODO Should configurable be true?
+                        configurable: true, // TODO Should configurable be true?
                         enumerable: true,
                     };
                 }
@@ -255,11 +252,12 @@ class ListImpl<T extends Field> extends ObjectField {
                 throw new Error("Currently properties can't be defined on documents using Object.defineProperty");
             },
         });
-        this[SelfProxy] = list as any as List<Field>; // bcz: ugh .. don't know how to convince typesecript that list is a List
+        this[SelfProxy] = list as any as List<FieldType>; // bcz: ugh .. don't know how to convince typesecript that list is a List
         if (fields) {
             this[SelfProxy].push(...fields);
         }
-        return list;
+        // eslint-disable-next-line no-constructor-return
+        return list; // need to return the proxy here, otherwise we don't get any of our list handler functions
     }
 
     [key: number]: T | (T extends RefField ? Promise<T> : never);
@@ -271,7 +269,7 @@ class ListImpl<T extends Field> extends ObjectField {
         // if we find any ProxyFields that don't have a current value, then
         // start the server request for all of them
         if (unrequested.length) {
-            const batchPromise = DocServer.GetRefFields(unrequested.map(p => p.fieldId));
+            const batchPromise = ObjGetRefFields(unrequested.map(p => p.fieldId));
             // as soon as we get the fields from the server, set all the list values in one
             // action to generate one React dom update.
             const allSetPromise = batchPromise.then(action(pfields => unrequested.map(toReq => toReq.setValue(pfields[toReq.fieldId]))));
@@ -308,7 +306,7 @@ class ListImpl<T extends Field> extends ObjectField {
     @observable
     private [FieldTuples]: StoredType<T>[] = [];
     private [Self] = this;
-    private [SelfProxy]: List<Field>; // also used in utils.ts even though it won't be found using find all references
+    private [SelfProxy]: List<FieldType>; // also used in utils.ts even though it won't be found using find all references
 
     [ToJavascriptString]() {
         return `[${(this as any).map((field: any) => Field.toScriptString(field))}]`;
@@ -320,10 +318,11 @@ class ListImpl<T extends Field> extends ObjectField {
         return `[${(this as any).map((field: any) => Field.toString(field))}]`;
     }
 }
-export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
-export const List: { new <T extends Field>(fields?: T[]): List<T> } = ListImpl as any;
+export type List<T extends FieldType> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
+export const List: { new <T extends FieldType>(fields?: T[]): List<T> } = ListImpl as any;
 
 ScriptingGlobals.add('List', List);
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function compareLists(l1: any, l2: any) {
     const L1 = Cast(l1, listSpec('string'), []);
     const L2 = Cast(l2, listSpec('string'), []);
diff --git a/src/fields/ObjectField.ts b/src/fields/ObjectField.ts
index e1b5b036c..6c70adc1d 100644
--- a/src/fields/ObjectField.ts
+++ b/src/fields/ObjectField.ts
@@ -1,26 +1,35 @@
-import { RefField } from './RefField';
-import { FieldChanged, Parent, Copy, ToScriptString, ToString, ToJavascriptString } from './FieldSymbols';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { Field } from './Doc';
+import { Copy, FieldChanged, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
+import { RefField } from './RefField';
 
 export abstract class ObjectField {
     // prettier-ignore
     public [FieldChanged]?: (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; 
-                             items: Field[] | undefined; 
+                             items: FieldType[] | undefined; 
                              length: number | undefined; 
                              hint?: any }, serverOp?: any) => void;
+    // eslint-disable-next-line no-use-before-define
     public [Parent]?: RefField | ObjectField;
     abstract [Copy](): ObjectField;
 
     abstract [ToJavascriptString](): string;
     abstract [ToScriptString](): string;
     abstract [ToString](): string;
-}
-
-export namespace ObjectField {
-    export function MakeCopy<T extends ObjectField>(field: T) {
+    static MakeCopy<T extends ObjectField>(field: T) {
         return field?.[Copy]();
     }
 }
+export type FieldType = number | string | boolean | ObjectField | RefField; // bcz: hack for now .. must match the type definition in Doc.ts .. put here to avoid import cycles
 
+// eslint-disable-next-line import/no-mutable-exports
+export let ObjGetRefField: (id: string, force?: boolean) => Promise<RefField | undefined>;
+// eslint-disable-next-line import/no-mutable-exports
+export let ObjGetRefFields: (ids: string[]) => Promise<{ [id: string]: RefField | undefined }>;
+
+export function SetObjGetRefField(func: (id: string, force?: boolean) => Promise<RefField | undefined>) {
+    ObjGetRefField = func;
+}
+export function SetObjGetRefFields(func: (ids: string[]) => Promise<{ [id: string]: RefField | undefined }>) {
+    ObjGetRefFields = func;
+}
 ScriptingGlobals.add(ObjectField);
diff --git a/src/fields/RefField.ts b/src/fields/RefField.ts
index 01828dd14..1ce81368a 100644
--- a/src/fields/RefField.ts
+++ b/src/fields/RefField.ts
@@ -1,7 +1,7 @@
-import { serializable, primitive, alias } from 'serializr';
+import { alias, primitive, serializable } from 'serializr';
 import { Utils } from '../Utils';
-import { Id, HandleUpdate, ToScriptString, ToString, ToJavascriptString } from './FieldSymbols';
 import { afterDocDeserialize } from '../client/util/SerializationHelper';
+import { HandleUpdate, Id, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 
 export type FieldId = string;
 export abstract class RefField {
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index b84a91709..d75d66bf8 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -1,21 +1,22 @@
 import { AssertionError } from 'assert';
+import * as Color from 'color';
 import { docs_v1 } from 'googleapis';
 import { Fragment, Mark, Node } from 'prosemirror-model';
 import { sinkListItem } from 'prosemirror-schema-list';
 import { EditorState, TextSelection, Transaction } from 'prosemirror-state';
-import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils';
-import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils';
+import { ClientUtils, DashColor } from '../ClientUtils';
+import { Utils } from '../Utils';
 import { DocServer } from '../client/DocServer';
-import { Docs, DocUtils } from '../client/documents/Documents';
 import { Networking } from '../client/Network';
+import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils';
+import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils';
+import { DocUtils, Docs } from '../client/documents/Documents';
 import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox';
 import { schema } from '../client/views/nodes/formattedText/schema_rts';
-import { DashColor, Utils } from '../Utils';
 import { Doc, Opt } from './Doc';
 import { Id } from './FieldSymbols';
 import { RichTextField } from './RichTextField';
 import { Cast, StrCast } from './Types';
-import * as Color from 'color';
 
 export namespace RichTextUtils {
     const delimiter = '\n';
@@ -140,8 +141,8 @@ export namespace RichTextUtils {
                     inlineObjectMap.set(object.objectId!, {
                         title: embeddedObject.title || `Imported Image from ${document.title}`,
                         width,
-                        url: Utils.prepend(_m.client),
-                        agnostic: Utils.prepend(agnostic.client),
+                        url: ClientUtils.prepend(_m.client),
+                        agnostic: ClientUtils.prepend(agnostic.client),
                     });
                 }
             }
@@ -401,7 +402,7 @@ export namespace RichTextUtils {
                                         DocUtils.makeCustomViewClicked(exported, Docs.Create.FreeformDocument);
                                         linkDoc.link_anchor_2 = exported;
                                     }
-                                    url = Utils.shareUrl(exported[Id]);
+                                    url = ClientUtils.shareUrl(exported[Id]);
                                 }
                             }
                             value = { url };
diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts
index f5e64ae1f..364899dc7 100644
--- a/src/fields/Schema.ts
+++ b/src/fields/Schema.ts
@@ -1,5 +1,5 @@
 import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from './Types';
-import { Doc, Field } from './Doc';
+import { Doc, FieldType } from './Doc';
 import { ObjectField } from './ObjectField';
 import { RefField } from './RefField';
 import { SelfProxy } from './DocSymbols';
@@ -99,11 +99,11 @@ export function createSchema<T extends Interface>(schema: T): T & { proto: ToCon
     return schema as any;
 }
 
-export function listSpec<U extends ToConstructor<Field>>(type: U): ListSpec<ToType<U>> {
+export function listSpec<U extends ToConstructor<FieldType>>(type: U): ListSpec<ToType<U>> {
     return { List: type as any }; //TODO Types
 }
 
-export function defaultSpec<T extends ToConstructor<Field>>(type: T, defaultVal: ToType<T>): DefaultFieldConstructor<ToType<T>> {
+export function defaultSpec<T extends ToConstructor<FieldType>>(type: T, defaultVal: ToType<T>): DefaultFieldConstructor<ToType<T>> {
     return {
         type: type as any,
         defaultVal,
diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts
index fb4dc4e5b..0a8dd1d9e 100644
--- a/src/fields/SchemaHeaderField.ts
+++ b/src/fields/SchemaHeaderField.ts
@@ -1,9 +1,19 @@
+import { primitive, serializable } from 'serializr';
+import { ScriptingGlobals, scriptingGlobal } from '../client/util/ScriptingGlobals';
 import { Deserializable } from '../client/util/SerializationHelper';
-import { serializable, primitive } from 'serializr';
+import { Copy, FieldChanged, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 import { ObjectField } from './ObjectField';
-import { Copy, ToScriptString, ToString, FieldChanged, ToJavascriptString } from './FieldSymbols';
-import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { ColumnType } from '../client/views/collections/collectionSchema/CollectionSchemaView';
+
+export enum ColumnType {
+    Number,
+    String,
+    Boolean,
+    Date,
+    Image,
+    RTF,
+    Enumeration,
+    Any,
+}
 
 export const PastelSchemaPalette = new Map<string, string>([
     // ["pink1", "#FFB4E8"],
@@ -69,13 +79,14 @@ export class SchemaHeaderField extends ObjectField {
     @serializable(primitive())
     desc: boolean | undefined; // boolean determines sort order, undefined when no sort
 
+    // eslint-disable-next-line default-param-last
     constructor(heading: string = '', color: string = RandomPastel(), type?: ColumnType, width?: number, desc?: boolean, collapsed?: boolean) {
         super();
 
         this.heading = heading;
         this.color = color;
-        this.type = type ? type : 0;
-        this.width = width ? width : -1;
+        this.type = type ?? 0;
+        this.width = width ?? -1;
         this.desc = desc;
         this.collapsed = collapsed;
     }
@@ -124,6 +135,8 @@ export class SchemaHeaderField extends ObjectField {
         return `SchemaHeaderField`;
     }
 }
+
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function schemaHeaderField(heading: string, color: string, type: number, width: number, desc?: boolean, collapsed?: boolean) {
     return new SchemaHeaderField(heading, color, type, width, desc, collapsed);
 });
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 8b51088b2..8a3787768 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -1,17 +1,16 @@
 import { action, makeObservable, observable } from 'mobx';
 import { computedFn } from 'mobx-utils';
-import { createSimpleSchema, custom, map, object, primitive, PropSchema, serializable, SKIP } from 'serializr';
-import { DocServer } from '../client/DocServer';
-import { CompiledScript, CompileScript, ScriptOptions, Transformer } from '../client/util/Scripting';
-import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { autoObject, Deserializable } from '../client/util/SerializationHelper';
+import { PropSchema, SKIP, createSimpleSchema, custom, map, object, primitive, serializable } from 'serializr';
 import { numberRange } from '../Utils';
-import { Doc, Field, FieldResult, Opt } from './Doc';
+import { GPTCallType, gptAPICall } from '../client/apis/gpt/GPT';
+import { CompileScript, CompiledScript, ScriptOptions, Transformer } from '../client/util/Scripting';
+import { ScriptingGlobals, scriptingGlobal } from '../client/util/ScriptingGlobals';
+import { Deserializable, autoObject } from '../client/util/SerializationHelper';
+import { Doc, Field, FieldType, FieldResult, Opt } from './Doc';
 import { Copy, FieldChanged, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols';
 import { List } from './List';
-import { ObjectField } from './ObjectField';
+import { ObjGetRefField, ObjectField } from './ObjectField';
 import { Cast, StrCast } from './Types';
-import { GPTCallType, gptAPICall } from '../client/apis/gpt/GPT';
 
 function optional(propSchema: PropSchema) {
     return custom(
@@ -44,7 +43,9 @@ const scriptSchema = createSimpleSchema({
     originalScript: true,
 });
 
-function finalizeScript(script: ScriptField) {
+// eslint-disable-next-line no-use-before-define
+function finalizeScript(scriptIn: ScriptField) {
+    const script = scriptIn;
     const comp = CompileScript(script.script.originalScript, script.script.options);
     if (!comp.compiled) {
         throw new Error("Couldn't compile loaded script");
@@ -54,11 +55,13 @@ function finalizeScript(script: ScriptField) {
         if (!compset.compiled) {
             throw new Error("Couldn't compile setter script");
         }
-        (script as any).setterscript = compset;
+        script.setterscript = compset;
     }
     return comp;
 }
-async function deserializeScript(script: ScriptField) {
+// eslint-disable-next-line no-use-before-define
+async function deserializeScript(scriptIn: ScriptField) {
+    const script = scriptIn;
     if (script.captures) {
         const captured: any = {};
         (script.script.options as ScriptOptions).capturedVariables = captured;
@@ -68,13 +71,16 @@ async function deserializeScript(script: ScriptField) {
                 const val = capture.split(':')[1];
                 if (val === 'true') captured[key] = true;
                 else if (val === 'false') captured[key] = false;
-                else if (val.startsWith('ID->')) captured[key] = await DocServer.GetRefField(val.replace('ID->', ''));
+                else if (val.startsWith('ID->')) captured[key] = await ObjGetRefField(val.replace('ID->', ''));
                 else if (!isNaN(Number(val))) captured[key] = Number(val);
                 else captured[key] = val;
             })
-        ).then(() => ((script as any).script = finalizeScript(script)));
+        ).then(() => {
+            script.script = finalizeScript(script);
+        });
     } else {
-        (script as any).script = ScriptField.GetScriptFieldCache(script.script.originalScript) ?? finalizeScript(script);
+        // eslint-disable-next-line no-use-before-define
+        script.script = ScriptField.GetScriptFieldCache(script.script.originalScript) ?? finalizeScript(script);
     }
 }
 
@@ -84,9 +90,9 @@ export class ScriptField extends ObjectField {
     @serializable
     readonly rawscript: string | undefined;
     @serializable(object(scriptSchema))
-    readonly script: CompiledScript;
+    script: CompiledScript;
     @serializable(object(scriptSchema))
-    readonly setterscript: CompiledScript | undefined;
+    setterscript: CompiledScript | undefined;
     @serializable
     @observable
     _cachedResult: FieldResult = undefined;
@@ -131,6 +137,7 @@ export class ScriptField extends ObjectField {
     [ToString]() {
         return this.script.originalScript;
     }
+    // eslint-disable-next-line default-param-last
     public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }, transformer?: Transformer) {
         return CompileScript(script, {
             params: {
@@ -150,20 +157,23 @@ export class ScriptField extends ObjectField {
         });
     }
 
+    // eslint-disable-next-line default-param-last
     public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
         const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
         return compiled.compiled ? new ScriptField(compiled) : undefined;
     }
 
+    // eslint-disable-next-line default-param-last
     public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
         const compiled = ScriptField.CompileScript(script, params, false, capturedVariables);
         return compiled.compiled ? new ScriptField(compiled) : undefined;
     }
-    public static CallGpt(queryText: string, setVal: (val: FieldResult) => void, target: Doc) {
+    public static CallGpt(queryTextIn: string, setVal: (val: FieldResult) => void, target: Doc) {
+        let queryText = queryTextIn;
         if (typeof queryText === 'string' && setVal) {
             while (queryText.match(/\(this\.[a-zA-Z_]*\)/)?.length) {
                 const fieldRef = queryText.split('(this.')[1].replace(/\).*/, '');
-                queryText = queryText.replace(/\(this\.[a-zA-Z_]*\)/, Field.toString(target[fieldRef] as Field));
+                queryText = queryText.replace(/\(this\.[a-zA-Z_]*\)/, Field.toString(target[fieldRef] as FieldType));
             }
             setVal(`Chat Pending: ${queryText}`);
             gptAPICall(queryText, GPTCallType.COMPLETION).then(result => {
@@ -198,24 +208,29 @@ export class ComputedField extends ScriptField {
     }
 
     _lastComputedResult: FieldResult;
-    value = (doc:Doc) => (this._lastComputedResult = this._cachedResult ?? 
-        computedFn((doc: Doc) =>
-            this.script.compiled &&
-            this.script.run( {
-                    this: doc, 
-                    //value: '',
-                    _setCacheResult_: this.setCacheResult,
-                    _last_: this._lastComputedResult,
-                    _readOnly_: true,
-                },
-                console.log
-            ).result
-        )(doc)
-    ); // prettier-ignore
+    value = (doc: Doc) => {
+        this._lastComputedResult =
+            this._cachedResult ??
+            computedFn(() =>
+                this.script.compiled &&
+                this.script.run(
+                    {
+                        this: doc,
+                        // value: '',
+                        _setCacheResult_: this.setCacheResult,
+                        _last_: this._lastComputedResult,
+                        _readOnly_: true,
+                    },
+                    console.log
+                ).result
+            )(); // prettier-ignore
+        return this._lastComputedResult;
+    };
 
-    [ToValue](doc: Doc)   { if (ComputedField.useComputed) return { value: this.value(doc) };  } // prettier-ignore
+    [ToValue](doc: Doc)   { return ComputedField.useComputed ? { value: this.value(doc) } : undefined; } // prettier-ignore
     [Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore
 
+    // eslint-disable-next-line default-param-last
     public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) {
         const compiled = ScriptField.CompileScript(script, params, true, { value: '', ...capturedVariables });
         const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined;
@@ -225,7 +240,7 @@ export class ComputedField extends ScriptField {
 
     public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt<number>) {
         if (!doc[`${fieldKey}_indexed`]) {
-            const flist = new List<number>(numberRange(curTimecode + 1).map(i => undefined) as any as number[]);
+            const flist = new List<number>(numberRange(curTimecode + 1).map(() => undefined) as any as number[]);
             flist[curTimecode] = Cast(doc[fieldKey], 'number', null);
             doc[`${fieldKey}_indexed`] = flist;
         }
@@ -235,7 +250,7 @@ export class ComputedField extends ScriptField {
     }
     public static MakeInterpolatedString(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) {
         if (!doc[`${fieldKey}_`]) {
-            const flist = new List<string>(numberRange(curTimecode + 1).map(i => undefined) as any as string[]);
+            const flist = new List<string>(numberRange(curTimecode + 1).map(() => undefined) as any as string[]);
             flist[curTimecode] = StrCast(doc[fieldKey]);
             doc[`${fieldKey}_indexed`] = flist;
         }
@@ -244,9 +259,9 @@ export class ComputedField extends ScriptField {
         return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
     }
     public static MakeInterpolatedDataField(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) {
-        if (doc[`${fieldKey}`] instanceof List) return;
+        if (doc[`${fieldKey}`] instanceof List) return undefined;
         if (!doc[`${fieldKey}_indexed`]) {
-            const flist = new List<Field>(numberRange(curTimecode + 1).map(i => undefined) as any as Field[]);
+            const flist = new List<FieldType>(numberRange(curTimecode + 1).map(() => undefined) as any as FieldType[]);
             flist[curTimecode] = Field.Copy(doc[fieldKey]);
             doc[`${fieldKey}_indexed`] = flist;
         }
@@ -257,11 +272,13 @@ export class ComputedField extends ScriptField {
             false,
             {}
         );
-        return (doc[`${fieldKey}`] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined);
+        doc[fieldKey] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
+        return doc[fieldKey];
     }
 }
 
 ScriptingGlobals.add(
+    // eslint-disable-next-line prefer-arrow-callback
     function setIndexVal(list: any[], index: number, value: any) {
         while (list.length <= index) list.push(undefined);
         list[index] = value;
@@ -271,6 +288,7 @@ ScriptingGlobals.add(
 );
 
 ScriptingGlobals.add(
+    // eslint-disable-next-line prefer-arrow-callback
     function getIndexVal(list: any[], index: number, defaultVal: Opt<number> = undefined) {
         return list?.reduce((p, x, i) => ((i <= index && x !== undefined) || p === undefined ? x : p), defaultVal);
     },
@@ -279,12 +297,14 @@ ScriptingGlobals.add(
 );
 
 ScriptingGlobals.add(
+    // eslint-disable-next-line prefer-arrow-callback
     function makeScript(script: string) {
         return ScriptField.MakeScript(script);
     },
     'returns the value at a given index of a list',
     '(list: any[], index: number)'
 );
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function dashCallChat(setVal: (val: FieldResult) => void, target: Doc, queryText: string) {
     ScriptField.CallGpt(queryText, setVal, target);
 }, 'calls chat gpt for the query string and then calls setVal with the result');
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index 337e8ca21..6ed94d341 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -1,5 +1,5 @@
 import { DateField } from './DateField';
-import { Doc, Field, FieldResult, Opt } from './Doc';
+import { Doc, FieldType, FieldResult, Opt } from './Doc';
 import { List } from './List';
 import { ProxyField } from './Proxy';
 import { RefField } from './RefField';
@@ -7,54 +7,55 @@ import { RichTextField } from './RichTextField';
 import { ScriptField } from './ScriptField';
 import { CsvField, ImageField, WebField } from './URLField';
 
+export type ToConstructor<T extends FieldType> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T;
+
+export type DefaultFieldConstructor<T extends FieldType> = {
+    type: ToConstructor<T>;
+    defaultVal: T;
+};
+// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
+export type ListSpec<T extends FieldType> = { List: ToConstructor<T> };
+
+export type InterfaceValue = ToConstructor<FieldType> | ListSpec<FieldType> | DefaultFieldConstructor<FieldType> | ((doc?: Doc) => any);
+
 export type ToType<T extends InterfaceValue> = T extends 'string'
     ? string
     : T extends 'number'
-    ? number
-    : T extends 'boolean'
-    ? boolean
-    : T extends ListSpec<infer U>
-    ? List<U>
-    : // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
-    T extends DefaultFieldConstructor<infer _U>
-    ? never
-    : T extends { new (...args: any[]): List<Field> }
-    ? never
-    : T extends { new (...args: any[]): infer R }
-    ? R
-    : T extends (doc?: Doc) => infer R
-    ? R
-    : never;
-
-export type ToConstructor<T extends Field> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T;
+      ? number
+      : T extends 'boolean'
+        ? boolean
+        : T extends ListSpec<infer U>
+          ? List<U>
+          : // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+            T extends DefaultFieldConstructor<infer _U>
+            ? never
+            : T extends { new (...args: any[]): List<FieldType> }
+              ? never
+              : T extends { new (...args: any[]): infer R }
+                ? R
+                : T extends (doc?: Doc) => infer R
+                  ? R
+                  : never;
 
 export type ToInterface<T extends Interface> = {
     [P in Exclude<keyof T, 'proto'>]: T[P] extends DefaultFieldConstructor<infer F> ? Exclude<FieldResult<F>, undefined> : FieldResult<ToType<T[P]>>;
 };
 
-// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
-export type ListSpec<T extends Field> = { List: ToConstructor<T> };
-
-export type DefaultFieldConstructor<T extends Field> = {
-    type: ToConstructor<T>;
-    defaultVal: T;
-};
-
 // type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
 
 export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
 export type Tail<T extends any[]> = ((...t: T) => any) extends (_: any, ...tail: infer TT) => any ? TT : [];
 export type HasTail<T extends any[]> = T extends [] | [any] ? false : true;
-
-export type InterfaceValue = ToConstructor<Field> | ListSpec<Field> | DefaultFieldConstructor<Field> | ((doc?: Doc) => any);
-//TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
+// TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
 export interface Interface {
     [key: string]: InterfaceValue;
     // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
 }
-export type WithoutRefField<T extends Field> = T extends RefField ? never : T;
+export type WithoutRefField<T extends FieldType> = T extends RefField ? never : T;
 
-export type CastCtor = ToConstructor<Field> | ListSpec<Field>;
+export type CastCtor = ToConstructor<FieldType> | ListSpec<FieldType>;
+
+type WithoutList<T extends FieldType> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
 
 export function Cast<T extends CastCtor>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
 export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
@@ -116,18 +117,16 @@ export function ImageCast(field: FieldResult, defaultVal: ImageField | null = nu
     return Cast(field, ImageField, defaultVal);
 }
 
-type WithoutList<T extends Field> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
-
-export function FieldValue<T extends Field, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
-export function FieldValue<T extends Field>(field: FieldResult<T>): Opt<T>;
-export function FieldValue<T extends Field>(field: FieldResult<T>, defaultValue?: T): Opt<T> {
+export function FieldValue<T extends FieldType, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
+export function FieldValue<T extends FieldType>(field: FieldResult<T>): Opt<T>;
+export function FieldValue<T extends FieldType>(field: FieldResult<T>, defaultValue?: T): Opt<T> {
     return field instanceof Promise || field === undefined ? defaultValue : field;
 }
 
 export interface PromiseLike<T> {
     then(callback: (field: Opt<T>) => void): void;
 }
-export function PromiseValue<T extends Field>(field: FieldResult<T>): PromiseLike<Opt<T>> {
+export function PromiseValue<T extends FieldType>(field: FieldResult<T>): PromiseLike<Opt<T>> {
     if (field instanceof Promise) return field as Promise<Opt<T>>;
     return {
         then(cb: (field: Opt<T>) => void) {
diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts
index 87334ad16..c6c51957d 100644
--- a/src/fields/URLField.ts
+++ b/src/fields/URLField.ts
@@ -1,18 +1,14 @@
+import { custom, serializable } from 'serializr';
+import { ClientUtils } from '../ClientUtils';
+import { scriptingGlobal } from '../client/util/ScriptingGlobals';
 import { Deserializable } from '../client/util/SerializationHelper';
-import { serializable, custom } from 'serializr';
+import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 import { ObjectField } from './ObjectField';
-import { ToScriptString, ToString, Copy, ToJavascriptString } from './FieldSymbols';
-import { scriptingGlobal } from '../client/util/ScriptingGlobals';
-import { Utils } from '../Utils';
 
 function url() {
     return custom(
-        function (value: URL) {
-            return value?.origin === window.location.origin ? value.pathname : value?.href;
-        },
-        function (jsonValue: string) {
-            return new URL(jsonValue, window.location.origin);
-        }
+        (value: URL) => (value?.origin === window.location.origin ? value.pathname : value?.href),
+        (jsonValue: string) => new URL(jsonValue, window.location.origin)
     );
 }
 
@@ -24,26 +20,28 @@ export abstract class URLField extends ObjectField {
     constructor(url: URL);
     constructor(url: URL | string) {
         super();
-        if (typeof url === 'string') {
-            url = url.startsWith('http') ? new URL(url) : new URL(url, window.location.origin);
-        }
-        this.url = url;
+        this.url =
+            typeof url !== 'string'
+                ? url // it's an URL
+                : url.startsWith('http')
+                  ? new URL(url)
+                  : new URL(url, window.location.origin);
     }
 
     [ToScriptString]() {
-        if (Utils.prepend(this.url?.pathname) === this.url?.href) {
+        if (ClientUtils.prepend(this.url?.pathname) === this.url?.href) {
             return `new ${this.constructor.name}("${this.url.pathname}")`;
         }
         return `new ${this.constructor.name}("${this.url?.href}")`;
     }
     [ToJavascriptString]() {
-        if (Utils.prepend(this.url?.pathname) === this.url?.href) {
+        if (ClientUtils.prepend(this.url?.pathname) === this.url?.href) {
             return `new ${this.constructor.name}("${this.url.pathname}")`;
         }
         return `new ${this.constructor.name}("${this.url?.href}")`;
     }
     [ToString]() {
-        if (Utils.prepend(this.url?.pathname) === this.url?.href) {
+        if (ClientUtils.prepend(this.url?.pathname) === this.url?.href) {
             return this.url.pathname;
         }
         return this.url?.href;
diff --git a/src/fields/util.ts b/src/fields/util.ts
index ad592391e..f87c200c1 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,12 +1,11 @@
-import { $mobx, action, observable, runInAction, trace, values } from 'mobx';
+import { $mobx, action, observable, runInAction, trace } from 'mobx';
 import { computedFn } from 'mobx-utils';
-import { returnZero } from '../Utils';
+import { ClientUtils, returnZero } from '../ClientUtils';
 import { DocServer } from '../client/DocServer';
-import { LinkManager } from '../client/util/LinkManager';
 import { SerializationHelper } from '../client/util/SerializationHelper';
 import { UndoManager } from '../client/util/UndoManager';
-import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, aclLevel, updateCachedAcls } from './Doc';
-import { AclAdmin, AclAugment, AclEdit, AclPrivate, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, UpdatingFromServer, Width } from './DocSymbols';
+import { Doc, DocListCast, FieldType, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, aclLevel, updateCachedAcls } from './Doc';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, DirectLinks, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, UpdatingFromServer, Width } from './DocSymbols';
 import { FieldChanged, Id, Parent, ToValue } from './FieldSymbols';
 import { List } from './List';
 import { ObjectField } from './ObjectField';
@@ -16,24 +15,44 @@ import { RichTextField } from './RichTextField';
 import { SchemaHeaderField } from './SchemaHeaderField';
 import { ComputedField } from './ScriptField';
 import { DocCast, ScriptCast, StrCast } from './Types';
-import { BaseException } from 'pdfjs-dist/types/src/shared/util';
+
+/**
+ * These are the various levels of access a user can have to a document.
+ *
+ * Admin: a user with admin access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), as well as change others' access rights to that document.
+ * Edit: a user with edit access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), but not change any access rights to that document.
+ * Add: a user with add access to a document can augment documents/annotations to that document but cannot edit or delete anything.
+ * View: a user with view access to a document can only view it - they cannot add/remove/edit anything.
+ * None: the document is not shared with that user.
+ * Unset: Remove a sharing permission (eg., used )
+ */
+export enum SharingPermissions {
+    Admin = 'Admin',
+    Edit = 'Edit',
+    Augment = 'Augment',
+    View = 'View',
+    None = 'Not-Shared',
+}
 
 function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-var tracing = false;
+// eslint-disable-next-line prefer-const
+let tracing = false;
 export function TraceMobx() {
     tracing && trace();
 }
 
-const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+export const _propSetterCB = new Map<string, ((target: any, value: any) => void) | undefined>();
+
+const _setterImpl = action((target: any, prop: string | symbol | number, valueIn: any, receiver: any): boolean => {
     if (SerializationHelper.IsSerializing() || typeof prop === 'symbol') {
-        target[prop] = value;
+        target[prop] = valueIn;
         return true;
     }
 
-    value = value?.[SelfProxy] ?? value; // convert any Doc type values to Proxy's
+    let value = valueIn?.[SelfProxy] ?? valueIn; // convert any Doc type values to Proxy's
 
     const curValue = target.__fieldTuples[prop];
     if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
@@ -50,6 +69,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
             throw new Error("Can't put the same object in multiple documents at the same time");
         }
         value[Parent] = receiver;
+        // eslint-disable-next-line no-use-before-define
         value[FieldChanged] = containedFieldChangedHandler(receiver, prop, value);
     }
     if (curValue instanceof ObjectField) {
@@ -59,11 +79,12 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
 
     if (typeof prop === 'string' && _propSetterCB.has(prop)) _propSetterCB.get(prop)!(target[SelfProxy], value);
 
+    // eslint-disable-next-line no-use-before-define
     const effectiveAcl = GetEffectiveAcl(target);
 
     const writeMode = DocServer.getFieldWriteMode(prop as string);
     const fromServer = target[UpdatingFromServer];
-    const sameAuthor = fromServer || receiver.author === Doc.CurrentUserEmail;
+    const sameAuthor = fromServer || receiver.author === ClientUtils.CurrentUserEmail;
     const writeToDoc =
         sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Playground || writeMode === DocServer.WriteMode.LivePlayground || (effectiveAcl === AclAugment && value instanceof RichTextField);
     const writeToServer =
@@ -95,7 +116,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
             (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
             UndoManager.AddEvent(
                 {
-                    redo: () => (receiver[prop] = value),
+                    redo: () => {
+                        receiver[prop] = value;
+                    },
                     undo: () => {
                         const wasUpdate = receiver[UpdatingFromServer];
                         const wasForce = receiver[ForceServerWrite];
@@ -131,57 +154,16 @@ export function denormalizeEmail(email: string) {
     return email.replace(/__/g, '.');
 }
 
-/**
- * Copies parent's acl fields to the child
- */
-export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean) {
-    [...Object.keys(parent), ...(Doc.CurrentUserEmail !== parent.author ? ['acl-Owner'] : [])]
-        .filter(key => key.startsWith('acl'))
-        .forEach(key => {
-            // if the default acl mode is private, then don't inherit the acl-guest permission, but set it to private.
-            // const permission: string = key === 'acl-guest' && Doc.defaultAclPrivate ? AclPrivate : parent[key];
-            const parAcl = ReverseHierarchyMap.get(StrCast(key === 'acl-Owner' ? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Edit) : parent[key]))?.acl;
-            if (parAcl) {
-                const sharePermission = HierarchyMapping.get(parAcl)?.name;
-                sharePermission && distributeAcls(key === 'acl-Owner' ? `acl-${normalizeEmail(StrCast(parent.author))}` : key, sharePermission, child, undefined, false, layoutOnly);
-            }
-        });
-}
-
-/**
- * These are the various levels of access a user can have to a document.
- *
- * Admin: a user with admin access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), as well as change others' access rights to that document.
- *
- * Edit: a user with edit access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), but not change any access rights to that document.
- *
- * Add: a user with add access to a document can augment documents/annotations to that document but cannot edit or delete anything.
- *
- * View: a user with view access to a document can only view it - they cannot add/remove/edit anything.
- *
- * None: the document is not shared with that user.
- *
- * Unset: Remove a sharing permission (eg., used )
- */
-export enum SharingPermissions {
-    Admin = 'Admin',
-    Edit = 'Edit',
-    Augment = 'Augment',
-    View = 'View',
-    None = 'Not-Shared',
-}
-
 // return acl from cache or cache the acl and return.
-const getEffectiveAclCache = computedFn(function (target: any, user?: string) {
-    return getEffectiveAcl(target, user);
-}, true);
+// eslint-disable-next-line no-use-before-define
+const getEffectiveAclCache = computedFn((target: any, user?: string) => getEffectiveAcl(target, user), true);
 
 /**
  * Calculates the effective access right to a document for the current user.
  */
 export function GetEffectiveAcl(target: any, user?: string): symbol {
     if (!target) return AclPrivate;
-    if (target[UpdatingFromServer] || Doc.CurrentUserEmail === 'guest') return AclAdmin;
+    if (target[UpdatingFromServer] || ClientUtils.CurrentUserEmail === 'guest') return AclAdmin;
     return getEffectiveAclCache(target, user); // all changes received from the server must be processed as Admin.  return this directly so that the acls aren't cached (UpdatingFromServer is not observable)
 }
 
@@ -191,10 +173,9 @@ export function GetPropAcl(target: any, prop: string | symbol | number) {
     return GetEffectiveAcl(target);
 }
 
-let cachedGroups = observable([] as string[]);
-const getCachedGroupByNameCache = computedFn(function (name: string) {
-    return cachedGroups.includes(name);
-}, true);
+const cachedGroups = observable([] as string[]);
+const getCachedGroupByNameCache = computedFn((name: string) => cachedGroups.includes(name), true);
+
 export function GetCachedGroupByName(name: string) {
     return getCachedGroupByNameCache(name);
 }
@@ -205,10 +186,10 @@ function getEffectiveAcl(target: any, user?: string): symbol {
     const targetAcls = target[DocAcl];
     if (targetAcls?.['acl-Me'] === AclAdmin || GetCachedGroupByName('Admin')) return AclAdmin;
 
-    const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group
+    const userChecked = user || ClientUtils.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group
     if (targetAcls && Object.keys(targetAcls).length) {
         let effectiveAcl = AclPrivate;
-        for (const [key, value] of Object.entries(targetAcls)) {
+        Object.entries(targetAcls).forEach(([key, value]) => {
             // there are issues with storing fields with . in the name, so they are replaced with _ during creation
             // as a result we need to restore them again during this comparison.
             const entity = denormalizeEmail(key.substring(4)); // an individual or a group
@@ -217,7 +198,7 @@ function getEffectiveAcl(target: any, user?: string): symbol {
                     effectiveAcl = value as symbol;
                 }
             }
-        }
+        });
 
         return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl;
     }
@@ -236,9 +217,9 @@ function getEffectiveAcl(target: any, user?: string): symbol {
  * @param allowUpgrade whether permissions can be made less restrictive
  * @param layoutOnly just sets the layout doc's ACL (unless the data doc has no entry for the ACL, in which case it will be set as well)
  */
-export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean, layoutOnly = false) {
-    const selfKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
-    if (!visited) visited = [] as Doc[];
+// eslint-disable-next-line default-param-last
+export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited: Doc[] = [], allowUpgrade?: boolean, layoutOnly = false) {
+    const selfKey = `acl-${normalizeEmail(ClientUtils.CurrentUserEmail)}`;
     if (!target || visited.includes(target) || key === selfKey) return;
     visited.push(target);
 
@@ -249,23 +230,21 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
     if (!layoutOnly && dataDoc && (allowUpgrade !== false || !dataDoc[key] || curVal > aclVal)) {
         // propagate ACLs to links, children, and annotations
 
-        LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade ? true : false));
+        dataDoc[DirectLinks].forEach(link => distributeAcls(key, acl, link, visited, !!allowUpgrade));
 
         DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => {
-            distributeAcls(key, acl, d, visited, allowUpgrade ? true : false);
-            d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false);
+            distributeAcls(key, acl, d, visited, !!allowUpgrade);
+            d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, !!allowUpgrade);
         });
 
         DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => {
-            distributeAcls(key, acl, d, visited, allowUpgrade ? true : false);
-            d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false);
+            distributeAcls(key, acl, d, visited, !!allowUpgrade);
+            d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, !!allowUpgrade);
         });
 
         Object.keys(target) // share expanded layout templates (eg, for presElementBox'es )
             .filter(lkey => lkey.includes('layout[') && DocCast(target[lkey]))
-            .map(lkey => {
-                distributeAcls(key, acl, DocCast(target[lkey]), visited, allowUpgrade ? true : false);
-            });
+            .forEach(lkey => distributeAcls(key, acl, DocCast(target[lkey]), visited, !!allowUpgrade));
 
         if (GetEffectiveAcl(dataDoc) === AclAdmin) {
             dataDoc[key] = acl;
@@ -285,7 +264,23 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
     dataDocChanged && updateCachedAcls(dataDoc);
 }
 
-export var _propSetterCB = new Map<string, ((target: any, value: any) => void) | undefined>();
+/**
+ * Copies parent's acl fields to the child
+ */
+export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean) {
+    [...Object.keys(parent), ...(ClientUtils.CurrentUserEmail !== parent.author ? ['acl-Owner'] : [])]
+        .filter(key => key.startsWith('acl'))
+        .forEach(key => {
+            // if the default acl mode is private, then don't inherit the acl-guest permission, but set it to private.
+            // const permission: string = key === 'acl-guest' && Doc.defaultAclPrivate ? AclPrivate : parent[key];
+            const parAcl = ReverseHierarchyMap.get(StrCast(key === 'acl-Owner' ? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Edit) : parent[key]))?.acl;
+            if (parAcl) {
+                const sharePermission = HierarchyMapping.get(parAcl)?.name;
+                sharePermission && distributeAcls(key === 'acl-Owner' ? `acl-${normalizeEmail(StrCast(parent.author))}` : key, sharePermission, child, undefined, false, layoutOnly);
+            }
+        });
+}
+
 /**
  * sets a callback function to be called whenever a value is assigned to the specified field key.
  * For example, this is used to "publish" documents with titles that start with '@'
@@ -299,13 +294,13 @@ export function SetPropSetterCb(prop: string, setter: ((target: any, value: any)
 //
 // target should be either a Doc or ListImpl.  receiver should be a Proxy<Doc> Or List.
 //
-export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
-    if (!in_prop) {
+export function setter(target: any, inProp: string | symbol | number, value: any, receiver: any): boolean {
+    if (!inProp) {
         console.log('WARNING: trying to set an empty property.  This should be fixed. ');
         return false;
     }
-    let prop = in_prop;
-    const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
+    let prop = inProp;
+    const effectiveAcl = inProp === 'constructor' || typeof inProp === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
     if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin) return true;
     // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
     if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
@@ -319,12 +314,24 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
     }
     if (target.__fieldTuples[prop] instanceof ComputedField) {
         if (target.__fieldTuples[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) {
-            return ScriptCast(target.__fieldTuples[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false;
+            return !!ScriptCast(target.__fieldTuples[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success;
         }
     }
     return _setter(target, prop, value, receiver);
 }
 
+function getFieldImpl(target: any, prop: string | number, proxy: any, ignoreProto: boolean = false): any {
+    const field = target.__fieldTuples[prop];
+    const value = field?.[ToValue]?.(proxy); // converts ComputedFields to values, or unpacks ProxyFields into Proxys
+    if (value) return value.value;
+    if (field === undefined && !ignoreProto && prop !== 'proto') {
+        const proto = getFieldImpl(target, 'proto', proxy, true); // TODO tfs: instead of proxy we could use target[SelfProxy]... I don't which semantics we want or if it really matters
+        if (proto instanceof Doc && GetEffectiveAcl(proto) !== AclPrivate) {
+            return getFieldImpl(proto, prop, proxy, ignoreProto);
+        }
+    }
+    return field;
+}
 export function getter(target: any, prop: string | symbol, proxy: any): any {
     // prettier-ignore
     switch (prop) {
@@ -336,6 +343,7 @@ export function getter(target: any, prop: string | symbol, proxy: any): any {
         case $mobx:     return target.__fieldTuples[prop];
         case DocLayout: return target.__LAYOUT__;
         case Height: case Width: if (GetEffectiveAcl(target) === AclPrivate) return returnZero;
+        // eslint-disable-next-line no-fallthrough
         default :
             if (typeof prop === 'symbol')  return target[prop];
             if (prop.startsWith('isMobX')) return target[prop];
@@ -343,23 +351,11 @@ export function getter(target: any, prop: string | symbol, proxy: any): any {
             if (GetEffectiveAcl(target) === AclPrivate && prop !== 'author') return undefined;
     }
 
-    const layout_prop = prop.startsWith('_') ? prop.substring(1) : undefined;
-    if (layout_prop && target.__LAYOUT__) return target.__LAYOUT__[layout_prop];
-    return getFieldImpl(target, layout_prop ?? prop, proxy);
+    const layoutProp = prop.startsWith('_') ? prop.substring(1) : undefined;
+    if (layoutProp && target.__LAYOUT__) return target.__LAYOUT__[layoutProp];
+    return getFieldImpl(target, layoutProp ?? prop, proxy);
 }
 
-function getFieldImpl(target: any, prop: string | number, proxy: any, ignoreProto: boolean = false): any {
-    const field = target.__fieldTuples[prop];
-    const value = field?.[ToValue]?.(proxy); // converts ComputedFields to values, or unpacks ProxyFields into Proxys
-    if (value) return value.value;
-    if (field === undefined && !ignoreProto && prop !== 'proto') {
-        const proto = getFieldImpl(target, 'proto', proxy, true); //TODO tfs: instead of proxy we could use target[SelfProxy]... I don't which semantics we want or if it really matters
-        if (proto instanceof Doc && GetEffectiveAcl(proto) !== AclPrivate) {
-            return getFieldImpl(proto, prop, proxy, ignoreProto);
-        }
-    }
-    return field;
-}
 export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any {
     return getFieldImpl(target, prop, target[SelfProxy], ignoreProto);
 }
@@ -382,10 +378,10 @@ export function deleteProperty(target: any, prop: string | number | symbol) {
 // were replaced.  Based on this specification, an Undo event is setup that will save enough information about the ObjectField to be
 // able to undo and redo the partial change.
 //
-export function containedFieldChangedHandler(container: List<Field> | Doc, prop: string | number, liveContainedField: ObjectField) {
+export function containedFieldChangedHandler(container: List<FieldType> | Doc, prop: string | number, liveContainedField: ObjectField) {
     let lastValue: FieldResult = liveContainedField instanceof ObjectField ? ObjectField.MakeCopy(liveContainedField) : liveContainedField;
-    return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, dummyServerOp?: any) => {
-        const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: Field) => SerializationHelper.Serialize(item)) });
+    return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: FieldType[] | undefined; length: number | undefined; hint?: any }, dummyServerOp?: any) => {
+        const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: FieldType) => SerializationHelper.Serialize(item)) });
         // prettier-ignore
         const serverOp = diff?.op === '$addToSet'
             ? { $addToSet:   { ['fields.' + prop]: serializeItems() }, length: diff.length }
@@ -401,8 +397,8 @@ export function containedFieldChangedHandler(container: List<Field> | Doc, prop:
                 UndoManager.AddEvent(
                     {
                         redo: () => {
-                            //console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo
-                            (container as any)[prop as any]?.push(...(diff.items || [])?.map((item: any) => item.value ?? item));
+                            // console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo
+                            (container as any)[prop as any]?.push(...((diff.items || [])?.map((item: any) => item.value ?? item) ?? []));
                             lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
                         },
                         undo: action(() => {
@@ -416,7 +412,7 @@ export function containedFieldChangedHandler(container: List<Field> | Doc, prop:
                             });
                             lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
                         }),
-                        prop: 'add ' + diff.items?.length + ' items to list',
+                        prop: 'add ' + (diff.items?.length ?? 0) + ' items to list',
                     },
                     diff?.items
                 );
@@ -447,12 +443,14 @@ export function containedFieldChangedHandler(container: List<Field> | Doc, prop:
                             });
                             lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
                         },
-                        prop: 'remove ' + diff.items?.length + ' items from list',
+                        prop: 'remove ' + (diff.items?.length ?? 0) + ' items from list',
                     },
                     diff?.items
                 );
             } else {
-                const setFieldVal = (val: Field | undefined) => (container instanceof Doc ? (container[prop as string] = val) : (container[prop as number] = val as Field));
+                const setFieldVal = (val: FieldType | undefined) => {
+                    container instanceof Doc ? (container[prop as string] = val) : (container[prop as number] = val as FieldType);
+                };
                 UndoManager.AddEvent(
                     {
                         redo: () => {
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index da01e099c..4e9d85b6d 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -1,9 +1,11 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as rp from 'request-promise';
-import { Utils } from '../Utils';
+import { ClientUtils } from '../ClientUtils';
 import { DocServer } from '../client/DocServer';
 import { Networking } from '../client/Network';
 import { Docs } from '../client/documents/Documents';
@@ -13,8 +15,9 @@ import { List } from '../fields/List';
 import { listSpec } from '../fields/Schema';
 import { Cast } from '../fields/Types';
 import './ImageUpload.scss';
-import { MobileInterface } from './MobileInterface';
+
 const { DFLT_IMAGE_NATIVE_DIM } = require('../client/views/global/globalCssVariables.module.scss'); // prettier-ignore
+
 export interface ImageUploadProps {
     Document: Doc; // Target document for upload (upload location)
 }
@@ -27,9 +30,12 @@ export class Uploader extends React.Component<ImageUploadProps> {
     @observable error: string = '';
     @observable nm: string = 'Choose files'; // Text of 'Choose Files' button
     @observable process: string = ''; // Current status of upload
+    @observable private dialogueBoxOpacity = 1;
+    @observable private overlayOpacity = 0.4;
 
     onClick = async () => {
         try {
+            // eslint-disable-next-line react/destructuring-assignment
             const col = this.props.Document;
             await Docs.Prototypes.initialize();
             const imgPrev = document.getElementById('img_preview');
@@ -41,11 +47,12 @@ export class Uploader extends React.Component<ImageUploadProps> {
                     this.process = 'Uploading Files';
                     for (let index = 0; index < files.length; ++index) {
                         const file = files[index];
+                        // eslint-disable-next-line no-await-in-loop
                         const res = await Networking.UploadFilesToServer({ file });
                         this.setOpacity(3, '1'); // Slab 3
                         // For each item that the user has selected
                         res.map(async ({ result }) => {
-                            const name = file.name;
+                            const { name } = file;
                             if (result instanceof Error) {
                                 return;
                             }
@@ -62,7 +69,7 @@ export class Uploader extends React.Component<ImageUploadProps> {
                                 doc = Docs.Create.ImageDocument(path, { _nativeWidth: defaultNativeImageDim, _width: 400, title: name });
                             }
                             this.setOpacity(4, '1'); // Slab 4
-                            const res = await rp.get(Utils.prepend('/getUserDocumentIds'));
+                            const res = await rp.get(ClientUtils.prepend('/getUserDocumentIds'));
                             if (!res) {
                                 throw new Error('No user id returned');
                             }
@@ -72,7 +79,7 @@ export class Uploader extends React.Component<ImageUploadProps> {
                                 pending = col;
                             }
                             if (pending) {
-                                const data = await Cast(pending.data, listSpec(Doc));
+                                const data = Cast(pending.data, listSpec(Doc));
                                 if (data) data.push(doc);
                                 else pending.data = new List([doc]);
                                 this.setOpacity(5, '1'); // Slab 5
@@ -99,8 +106,7 @@ export class Uploader extends React.Component<ImageUploadProps> {
 
     // Updates label after a files is selected (so user knows a file is uploaded)
     inputLabel = async () => {
-        const files: FileList | null = inputRef.current!.files;
-        await files;
+        const files: FileList | null = await inputRef.current!.files;
         if (files && files.length === 1) {
             this.nm = files[0].name;
         } else if (files && files.length > 1) {
@@ -125,7 +131,6 @@ export class Uploader extends React.Component<ImageUploadProps> {
     // Clears the upload and closes the upload menu
     closeUpload = () => {
         this.clearUpload();
-        MobileInterface.Instance.toggleUpload();
     };
 
     // Handles the setting of the loading bar
@@ -139,17 +144,17 @@ export class Uploader extends React.Component<ImageUploadProps> {
         return (
             <div className="imgupload_cont">
                 <div className="closeUpload" onClick={() => this.closeUpload()}>
-                    <FontAwesomeIcon icon="window-close" size={'lg'} />
+                    <FontAwesomeIcon icon="window-close" size="lg" />
                 </div>
                 <FontAwesomeIcon icon="upload" size="lg" style={{ fontSize: '130' }} />
-                <input type="file" accept="application/pdf, video/*,image/*" className={`inputFile ${this.nm !== 'Choose files' ? 'active' : ''}`} id="input_image_file" ref={inputRef} onChange={this.inputLabel} multiple></input>
+                <input type="file" accept="application/pdf, video/*,image/*" className={`inputFile ${this.nm !== 'Choose files' ? 'active' : ''}`} id="input_image_file" ref={inputRef} onChange={this.inputLabel} multiple />
                 <label className="file" id="label" htmlFor="input_image_file">
                     {this.nm}
                 </label>
                 <div className="upload_label" onClick={this.onClick}>
                     Upload
                 </div>
-                <img id="img_preview" src=""></img>
+                <img id="img_preview" src="" alt="" />
                 <div className="loadingImage">
                     <div className="loadingSlab" id="slab1" />
                     <div className="loadingSlab" id="slab2" />
@@ -164,10 +169,7 @@ export class Uploader extends React.Component<ImageUploadProps> {
         );
     }
 
-    @observable private dialogueBoxOpacity = 1;
-    @observable private overlayOpacity = 0.4;
-
     render() {
-        return <MainViewModal contents={this.uploadInterface} isDisplayed={true} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.closeUpload} />;
+        return <MainViewModal contents={this.uploadInterface} isDisplayed interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.closeUpload} />;
     }
 }
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index 23e19585a..6babd2f39 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { DocServer } from '../client/DocServer';
 import { DragManager } from '../client/util/DragManager';
 import { Doc } from '../fields/Doc';
-import { GestureUtils } from '../pen-gestures/GestureUtils';
+import { Gestures } from '../pen-gestures/GestureTypes';
 import { GestureContent, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from '../server/Message';
 import './MobileInkOverlay.scss';
 
@@ -76,11 +76,11 @@ export default class MobileInkOverlay extends React.Component {
 
         const target = document.elementFromPoint(this._x + 10, this._y + 10);
         target?.dispatchEvent(
-            new CustomEvent<GestureUtils.GestureEvent>('dashOnGesture', {
+            new CustomEvent<GestureEvent>('dashOnGesture', {
                 bubbles: true,
                 detail: {
                     points: points,
-                    gesture: GestureUtils.Gestures.Stroke,
+                    gesture: Gestures.Stroke,
                     bounds: B,
                 },
             })
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index dc4af8303..1d4c0adce 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -104,7 +104,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../ClientUtils';
 import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
 import { Docs, DocumentOptions } from '../client/documents/Documents';
 import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
diff --git a/src/pen-gestures/GestureTypes.ts b/src/pen-gestures/GestureTypes.ts
new file mode 100644
index 000000000..d86562580
--- /dev/null
+++ b/src/pen-gestures/GestureTypes.ts
@@ -0,0 +1,16 @@
+export enum Gestures {
+    Line = 'line',
+    Stroke = 'stroke',
+    Scribble = 'scribble',
+    Text = 'text',
+    Triangle = 'triangle',
+    Circle = 'circle',
+    Rectangle = 'rectangle',
+    Arrow = 'arrow',
+}
+
+// Defines a point in an ink as a pair of x- and y-coordinates.
+export interface PointData {
+    X: number;
+    Y: number;
+}
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 41917aac9..28323d62f 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -1,32 +1,32 @@
 import { Rect } from 'react-measure';
-import { PointData } from '../fields/InkField';
+import { Gestures, PointData } from './GestureTypes';
 import { NDollarRecognizer } from './ndollar';
 
 export namespace GestureUtils {
     export class GestureEvent {
-        constructor(readonly gesture: Gestures, readonly points: PointData[], readonly bounds: Rect, readonly text?: any) {}
+        readonly gesture: Gestures;
+        readonly points: PointData[];
+        readonly bounds: Rect;
+        readonly text?: any;
+
+        constructor(gesture: Gestures, points: PointData[], bounds: Rect, text?: any) {
+            this.gesture = gesture;
+            this.points = points;
+            this.bounds = bounds;
+            this.text = text;
+        }
     }
 
     export interface GestureEventDisposer {
         (): void;
     }
 
+    // eslint-disable-next-line no-undef
     export function MakeGestureTarget(element: HTMLElement, func: (e: Event, ge: GestureEvent) => void): GestureEventDisposer {
         const handler = (e: Event) => func(e, (e as CustomEvent<GestureEvent>).detail);
         element.addEventListener('dashOnGesture', handler);
         return () => element.removeEventListener('dashOnGesture', handler);
     }
 
-    export enum Gestures {
-        Line = 'line',
-        Stroke = 'stroke',
-        Scribble = 'scribble',
-        Text = 'text',
-        Triangle = 'triangle',
-        Circle = 'circle',
-        Rectangle = 'rectangle',
-        Arrow = 'arrow',
-    }
-
     export const GestureRecognizer = new NDollarRecognizer(false);
 }
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 3ee9506cb..d28d303b8 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -1,4 +1,4 @@
-import { GestureUtils } from './GestureUtils';
+import { Gestures } from './GestureTypes';
 
 /**
  * The $N Multistroke Recognizer (JavaScript version)
@@ -172,7 +172,7 @@ export class NDollarRecognizer {
         //
         this.Multistrokes.push(
             new Multistroke(
-                GestureUtils.Gestures.Rectangle,
+                Gestures.Rectangle,
                 useBoundedRotationInvariance,
                 new Array(
                     new Array(
@@ -185,17 +185,17 @@ export class NDollarRecognizer {
                 )
             )
         );
-        this.Multistrokes.push(new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347)))));
+        this.Multistrokes.push(new Multistroke(Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347)))));
         this.Multistrokes.push(
             new Multistroke(
-                GestureUtils.Gestures.Triangle, // equilateral
+                Gestures.Triangle, // equilateral
                 useBoundedRotationInvariance,
                 new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100)))
             )
         );
         this.Multistrokes.push(
             new Multistroke(
-                GestureUtils.Gestures.Circle,
+                Gestures.Circle,
                 useBoundedRotationInvariance,
                 new Array(
                     new Array(
diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts
index 55b50cc12..6f5b9272a 100644
--- a/src/server/ActionUtilities.ts
+++ b/src/server/ActionUtilities.ts
@@ -1,14 +1,14 @@
 import { exec } from 'child_process';
 import { Color, yellow } from 'colors';
 import { createWriteStream, exists, mkdir, readFile, unlink, writeFile } from 'fs';
-import * as nodemailer from "nodemailer";
-import { MailOptions } from "nodemailer/lib/json-transport";
+import * as nodemailer from 'nodemailer';
+import { MailOptions } from 'nodemailer/lib/json-transport';
 import * as path from 'path';
-import { rimraf } from "rimraf";
+import { rimraf } from 'rimraf';
 import { ExecOptions } from 'shelljs';
 import * as Mail from 'nodemailer/lib/mailer';
 
-const projectRoot = path.resolve(__dirname, "../../");
+const projectRoot = path.resolve(__dirname, '../../');
 export function pathFromRoot(relative?: string) {
     if (!relative) {
         return projectRoot;
@@ -18,7 +18,7 @@ export function pathFromRoot(relative?: string) {
 
 export async function fileDescriptorFromStream(path: string) {
     const logStream = createWriteStream(path);
-    return new Promise<number>(resolve => logStream.on("open", resolve));
+    return new Promise<number>(resolve => logStream.on('open', resolve));
 }
 
 export const command_line = (command: string, fromDirectory?: string) => {
@@ -27,25 +27,25 @@ export const command_line = (command: string, fromDirectory?: string) => {
         if (fromDirectory) {
             options.cwd = fromDirectory ? path.resolve(projectRoot, fromDirectory) : projectRoot;
         }
-        exec(command, options, (err, stdout) => err ? reject(err) : resolve(stdout));
+        exec(command, options, (err, stdout) => (err ? reject(err) : resolve(stdout)));
     });
 };
 
 export const read_text_file = (relativePath: string) => {
     const target = path.resolve(__dirname, relativePath);
     return new Promise<string>((resolve, reject) => {
-        readFile(target, (err, data) => err ? reject(err) : resolve(data.toString()));
+        readFile(target, (err, data) => (err ? reject(err) : resolve(data.toString())));
     });
 };
 
 export const write_text_file = (relativePath: string, contents: any) => {
     const target = path.resolve(__dirname, relativePath);
     return new Promise<void>((resolve, reject) => {
-        writeFile(target, contents, (err) => err ? reject(err) : resolve());
+        writeFile(target, contents, err => (err ? reject(err) : resolve()));
     });
 };
 
-export type Messager<T> = (outcome: { result: T | undefined, error: Error | null }) => string;
+export type Messager<T> = (outcome: { result: T | undefined; error: Error | null }) => string;
 
 export interface LogData<T> {
     startMessage: string;
@@ -56,22 +56,23 @@ export interface LogData<T> {
 }
 
 let current = Math.ceil(Math.random() * 20);
-export async function log_execution<T>({ startMessage, endMessage, action, color }: LogData<T>): Promise<T | undefined> {
-    let result: T | undefined = undefined, error: Error | null = null;
-    const resolvedColor = color || `\x1b[${31 + ++current % 6}m%s\x1b[0m`;
+export async function logExecution<T>({ startMessage, endMessage, action, color }: LogData<T>): Promise<T | undefined> {
+    let result: T | undefined = undefined,
+        error: Error | null = null;
+    const resolvedColor = color || `\x1b[${31 + (++current % 6)}m%s\x1b[0m`;
     log_helper(`${startMessage}...`, resolvedColor);
     try {
         result = await action();
     } catch (e: any) {
         error = e;
     } finally {
-        log_helper(typeof endMessage === "string" ? endMessage : endMessage({ result, error }), resolvedColor);
+        log_helper(typeof endMessage === 'string' ? endMessage : endMessage({ result, error }), resolvedColor);
     }
     return result;
 }
 
 function log_helper(content: string, color: Color | string) {
-    if (typeof color === "string") {
+    if (typeof color === 'string') {
         console.log(color, content);
     } else {
         console.log(color(content));
@@ -88,11 +89,11 @@ export function msToTime(duration: number) {
         minutes = Math.floor((duration / (1000 * 60)) % 60),
         hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
 
-    const hoursS = (hours < 10) ? "0" + hours : hours;
-    const minutesS = (minutes < 10) ? "0" + minutes : minutes;
-    const secondsS = (seconds < 10) ? "0" + seconds : seconds;
+    const hoursS = hours < 10 ? '0' + hours : hours;
+    const minutesS = minutes < 10 ? '0' + minutes : minutes;
+    const secondsS = seconds < 10 ? '0' + seconds : seconds;
 
-    return hoursS + ":" + minutesS + ":" + secondsS + "." + milliseconds;
+    return hoursS + ':' + minutesS + ':' + secondsS + '.' + milliseconds;
 }
 
 export const createIfNotExists = async (path: string) => {
@@ -112,13 +113,12 @@ export async function Prune(rootDirectory: string): Promise<boolean> {
 export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => unlink(mediaPath, error => resolve(error === null)));
 
 export namespace Email {
-
     const smtpTransport = nodemailer.createTransport({
         service: 'Gmail',
         auth: {
             user: 'browndashptc@gmail.com',
-            pass: 'TsarNicholas#2'
-        }
+            pass: 'TsarNicholas#2',
+        },
     });
 
     export interface DispatchOptions<T extends string | string[]> {
@@ -135,16 +135,18 @@ export namespace Email {
 
     export async function dispatchAll({ to, subject, content, attachments }: DispatchOptions<string[]>) {
         const failures: DispatchFailure[] = [];
-        await Promise.all(to.map(async recipient => {
-            let error: Error | null;
-            const resolved = attachments ? "length" in attachments ? attachments : [attachments] : undefined;
-            if ((error = await Email.dispatch({ to: recipient, subject, content, attachments: resolved })) !== null) {
-                failures.push({
-                    recipient,
-                    error
-                });
-            }
-        }));
+        await Promise.all(
+            to.map(async recipient => {
+                let error: Error | null;
+                const resolved = attachments ? ('length' in attachments ? attachments : [attachments]) : undefined;
+                if ((error = await Email.dispatch({ to: recipient, subject, content, attachments: resolved })) !== null) {
+                    failures.push({
+                        recipient,
+                        error,
+                    });
+                }
+            })
+        );
         return failures.length ? failures : undefined;
     }
 
@@ -153,10 +155,9 @@ export namespace Email {
             to,
             from: 'browndashptc@gmail.com',
             subject,
-            text: `Hello ${to.split("@")[0]},\n\n${content}`,
-            attachments
+            text: `Hello ${to.split('@')[0]},\n\n${content}`,
+            attachments,
         } as MailOptions;
         return new Promise<Error | null>(resolve => smtpTransport.sendMail(mailOptions, resolve));
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/server/ApiManagers/DataVizManager.ts b/src/server/ApiManagers/DataVizManager.ts
index 0d43130d1..88f22992d 100644
--- a/src/server/ApiManagers/DataVizManager.ts
+++ b/src/server/ApiManagers/DataVizManager.ts
@@ -1,14 +1,14 @@
-import { csvParser, csvToString } from "../DataVizUtils";
-import { Method, _success } from "../RouteManager";
-import ApiManager, { Registration } from "./ApiManager";
-import { Directory, serverPathToFile } from "./UploadManager";
 import * as path from 'path';
+import { csvParser, csvToString } from '../DataVizUtils';
+import { Method, _success } from '../RouteManager';
+import { Directory, serverPathToFile } from '../SocketData';
+import ApiManager, { Registration } from './ApiManager';
 
 export default class DataVizManager extends ApiManager {
     protected initialize(register: Registration): void {
         register({
             method: Method.GET,
-            subscription: "/csvData",
+            subscription: '/csvData',
             secureHandler: async ({ req, res }) => {
                 const uri = req.query.uri as string;
 
@@ -19,8 +19,7 @@ export default class DataVizManager extends ApiManager {
                     _success(res, parsedCsv);
                     resolve();
                 });
-            }
+            },
         });
     }
-    
-}
\ No newline at end of file
+}
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index c6c4ca464..9a9b807ae 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -1,5 +1,5 @@
 import ApiManager, { Registration } from './ApiManager';
-import { Method, _permission_denied } from '../RouteManager';
+import { Method, _permissionDenied } from '../RouteManager';
 import { WebSocket } from '../websocket';
 import { Database } from '../database';
 import { rimraf } from 'rimraf';
diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts
index 2175b6db6..b105c825c 100644
--- a/src/server/ApiManagers/DownloadManager.ts
+++ b/src/server/ApiManagers/DownloadManager.ts
@@ -1,13 +1,13 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
-import RouteSubscriber from "../RouteSubscriber";
 import * as Archiver from 'archiver';
 import * as express from 'express';
-import { Database } from "../database";
-import * as path from "path";
-import { DashUploadUtils, SizeSuffix } from "../DashUploadUtils";
-import { publicDirectory } from "..";
-import { serverPathToFile, Directory } from "./UploadManager";
+import * as path from 'path';
+import { URL } from 'url';
+import { DashUploadUtils, SizeSuffix } from '../DashUploadUtils';
+import { Method } from '../RouteManager';
+import RouteSubscriber from '../RouteSubscriber';
+import { Directory, publicDirectory, serverPathToFile } from '../SocketData';
+import { Database } from '../database';
+import ApiManager, { Registration } from './ApiManager';
 
 export type Hierarchy = { [id: string]: string | Hierarchy };
 export type ZipMutator = (file: Archiver.Archiver) => void | Promise<void>;
@@ -16,147 +16,45 @@ export interface DocumentElements {
     title: string;
 }
 
-export default class DownloadManager extends ApiManager {
-
-    protected initialize(register: Registration): void {
-
-        /**
-         * Let's say someone's using Dash to organize images in collections.
-         * This lets them export the hierarchy they've built to their
-         * own file system in a useful format.
-         * 
-         * This handler starts with a single document id (interesting only
-         * if it's that of a collection). It traverses the database, captures
-         * the nesting of only nested images or collections, writes
-         * that to a zip file and returns it to the client for download.
-         */
-        register({
-            method: Method.GET,
-            subscription: new RouteSubscriber("imageHierarchyExport").add('docId'),
-            secureHandler: async ({ req, res }) => {
-                const id = req.params.docId;
-                const hierarchy: Hierarchy = {};
-                await buildHierarchyRecursive(id, hierarchy);
-                return BuildAndDispatchZip(res, zip => writeHierarchyRecursive(zip, hierarchy));
-            }
-        });
-
-        register({
-            method: Method.GET,
-            subscription: new RouteSubscriber("downloadId").add("docId"),
-            secureHandler: async ({ req, res }) => {
-                return BuildAndDispatchZip(res, async zip => {
-                    const { id, docs, files } = await getDocs(req.params.docId);
-                    const docString = JSON.stringify({ id, docs });
-                    zip.append(docString, { name: "doc.json" });
-                    files.forEach(val => {
-                        zip.file(publicDirectory + val, { name: val.substring(1) });
-                    });
-                });
-            }
-        });
-
-        register({
-            method: Method.GET,
-            subscription: new RouteSubscriber("serializeDoc").add("docId"),
-            secureHandler: async ({ req, res }) => {
-                const { docs, files } = await getDocs(req.params.docId);
-                res.send({ docs, files: Array.from(files) });
-            }
-        });
-
-    }
-
-}
-
-async function getDocs(id: string) {
-    const files = new Set<string>();
-    const docs: { [id: string]: any } = {};
-    const fn = (doc: any): string[] => {
-        const id = doc.id;
-        if (typeof id === "string" && id.endsWith("Proto")) {
-            //Skip protos
-            return [];
-        }
-        const ids: string[] = [];
-        for (const key in doc.fields) {
-            if (!doc.fields.hasOwnProperty(key)) { continue; }
-            const field = doc.fields[key];
-            if (field === undefined || field === null) { continue; }
-
-            if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
-                ids.push(field.fieldId);
-            } else if (field.__type === "script" || field.__type === "computed") {
-                field.captures && ids.push(field.captures.fieldId);
-            } else if (field.__type === "list") {
-                ids.push(...fn(field));
-            } else if (typeof field === "string") {
-                const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w\-]*)"/g;
-                let match: string[] | null;
-                while ((match = re.exec(field)) !== null) {
-                    ids.push(match[1]);
-                }
-            } else if (field.__type === "RichTextField") {
-                const re = /"href"\s*:\s*"(.*?)"/g;
-                let match: string[] | null;
-                while ((match = re.exec(field.Data)) !== null) {
-                    const urlString = match[1];
-                    const split = new URL(urlString).pathname.split("doc/");
-                    if (split.length > 1) {
-                        ids.push(split[split.length - 1]);
-                    }
-                }
-                const re2 = /"src"\s*:\s*"(.*?)"/g;
-                while ((match = re2.exec(field.Data)) !== null) {
-                    const urlString = match[1];
-                    const pathname = new URL(urlString).pathname;
-                    files.add(pathname);
-                }
-            } else if (["audio", "image", "video", "pdf", "web", "map"].includes(field.__type)) {
-                const url = new URL(field.url);
-                const pathname = url.pathname;
-                files.add(pathname);
-            }
-        }
-
-        if (doc.id) {
-            docs[doc.id] = doc;
-        }
-        return ids;
-    };
-    await Database.Instance.visit([id], fn);
-    return { id, docs, files };
-}
-
 /**
- * This utility function factors out the process
- * of creating a zip file and sending it back to the client
- * by piping it into a response.
- * 
- * Learn more about piping and readable / writable streams here!
- * https://www.freecodecamp.org/news/node-js-streams-everything-you-need-to-know-c9141306be93/ 
- * 
- * @param res the writable stream response object that will transfer the generated zip file
- * @param mutator the callback function used to actually modify and insert information into the zip instance
+ * This is a very specific utility method to help traverse the database
+ * to parse data and titles out of images and collections alone.
+ *
+ * We don't know if the document id given to is corresponds to a view document or a data
+ * document. If it's a data document, the response from the database will have
+ * a data field. If not, call recursively on the proto, and resolve with *its* data
+ *
+ * @param targetId the id of the Dash document whose data is being requests
+ * @returns the data of the document, as well as its title
  */
-export async function BuildAndDispatchZip(res: express.Response, mutator: ZipMutator): Promise<void> {
-    res.set('Content-disposition', `attachment;`);
-    res.set('Content-Type', "application/zip");
-    const zip = Archiver('zip');
-    zip.pipe(res);
-    await mutator(zip);
-    return zip.finalize();
+async function getData(targetId: string): Promise<DocumentElements> {
+    return new Promise<DocumentElements>((resolve, reject) => {
+        Database.Instance.getDocument(targetId, async (result: any) => {
+            const { data, proto, title } = result.fields;
+            if (data) {
+                if (data.url) {
+                    resolve({ data: data.url, title });
+                } else if (data.fields) {
+                    resolve({ data: data.fields, title });
+                } else {
+                    reject();
+                }
+            } else if (proto) {
+                getData(proto.fieldId).then(resolve, reject);
+            } else {
+                reject();
+            }
+        });
+    });
 }
 
 /**
  * This function starts with a single document id as a seed,
  * typically that of a collection, and then descends the entire tree
- * of image or collection documents that are reachable from that seed. 
+ * of image or collection documents that are reachable from that seed.
  * @param seedId the id of the root of the subtree we're trying to capture, interesting only if it's a collection
  * @param hierarchy the data structure we're going to use to record the nesting of the collections and images as we descend
- */
-
-/*
+ 
 Below is an example of the JSON hierarchy built from two images contained inside a collection titled 'a nested collection',
 following the general recursive structure shown immediately below
 {
@@ -190,74 +88,175 @@ async function buildHierarchyRecursive(seedId: string, hierarchy: Hierarchy): Pr
 }
 
 /**
- * This is a very specific utility method to help traverse the database
- * to parse data and titles out of images and collections alone.
- * 
- * We don't know if the document id given to is corresponds to a view document or a data
- * document. If it's a data document, the response from the database will have
- * a data field. If not, call recursively on the proto, and resolve with *its* data
- * 
- * @param targetId the id of the Dash document whose data is being requests
- * @returns the data of the document, as well as its title
+ * This utility function factors out the process
+ * of creating a zip file and sending it back to the client
+ * by piping it into a response.
+ *
+ * Learn more about piping and readable / writable streams here!
+ * https://www.freecodecamp.org/news/node-js-streams-everything-you-need-to-know-c9141306be93/
+ *
+ * @param res the writable stream response object that will transfer the generated zip file
+ * @param mutator the callback function used to actually modify and insert information into the zip instance
  */
-async function getData(targetId: string): Promise<DocumentElements> {
-    return new Promise<DocumentElements>((resolve, reject) => {
-        Database.Instance.getDocument(targetId, async (result: any) => {
-            const { data, proto, title } = result.fields;
-            if (data) {
-                if (data.url) {
-                    resolve({ data: data.url, title });
-                } else if (data.fields) {
-                    resolve({ data: data.fields, title });
-                } else {
-                    reject();
-                }
-            } else if (proto) {
-                getData(proto.fieldId).then(resolve, reject);
-            } else {
-                reject();
-            }
-        });
-    });
+export async function BuildAndDispatchZip(res: express.Response, mutator: ZipMutator): Promise<void> {
+    res.set('Content-disposition', `attachment;`);
+    res.set('Content-Type', 'application/zip');
+    const zip = Archiver('zip');
+    zip.pipe(res);
+    await mutator(zip);
+    return zip.finalize();
 }
 
 /**
- * 
+ *
  * @param file the zip file to which we write the files
  * @param hierarchy the data structure from which we read, defining the nesting of the documents in the zip
  * @param prefix lets us create nested folders in the zip file by continually appending to the end
  * of the prefix with each layer of recursion.
- * 
+ *
  * Function Call #1 => "Dash Export"
  * Function Call #2 => "Dash Export/a nested collection"
  * Function Call #3 => "Dash Export/a nested collection/lowest level collection"
  * ...
  */
-async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hierarchy, prefix = "Dash Export"): Promise<void> {
-    for (const documentTitle of Object.keys(hierarchy)) {
-        const result = hierarchy[documentTitle];
-        // base case or leaf node, we've hit a url (image)
-        if (typeof result === "string") {
-            let path: string;
-            let matches: RegExpExecArray | null;
-            if ((matches = /\:\d+\/files\/images\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) {
-                // image already exists on our server
-                path = serverPathToFile(Directory.images, matches[1]);
+async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hierarchy, prefix = 'Dash Export'): Promise<void> {
+    // eslint-disable-next-line no-restricted-syntax
+    for (const documentTitle in hierarchy) {
+        if (Object.prototype.hasOwnProperty.call(hierarchy, documentTitle)) {
+            const result = hierarchy[documentTitle];
+            // base case or leaf node, we've hit a url (image)
+            if (typeof result === 'string') {
+                let fPath: string;
+                const matches = /:\d+\/files\/images\/(upload_[\da-z]{32}.*)/g.exec(result);
+                if (matches !== null) {
+                    // image already exists on our server
+                    fPath = serverPathToFile(Directory.images, matches[1]);
+                } else {
+                    // the image doesn't already exist on our server (may have been dragged
+                    // and dropped in the browser and thus hosted remotely) so we upload it
+                    // to our server and point the zip file to it, so it can bundle up the bytes
+                    // eslint-disable-next-line no-await-in-loop
+                    const information = await DashUploadUtils.UploadImage(result);
+                    fPath = information instanceof Error ? '' : information.accessPaths[SizeSuffix.Original].server;
+                }
+                // write the file specified by the path to the directory in the
+                // zip file given by the prefix.
+                if (fPath) {
+                    file.file(fPath, { name: documentTitle, prefix });
+                }
             } else {
-                // the image doesn't already exist on our server (may have been dragged
-                // and dropped in the browser and thus hosted remotely) so we upload it
-                // to our server and point the zip file to it, so it can bundle up the bytes
-                const information = await DashUploadUtils.UploadImage(result);
-                path = information instanceof Error ? "" : information.accessPaths[SizeSuffix.Original].server;
+                // we've hit a collection, so we have to recurse
+                // eslint-disable-next-line no-await-in-loop
+                await writeHierarchyRecursive(file, result, `${prefix}/${documentTitle}`);
             }
-            // write the file specified by the path to the directory in the
-            // zip file given by the prefix.
-            if (path) {
-                file.file(path, { name: documentTitle, prefix });
+        }
+    }
+}
+
+async function getDocs(id: string) {
+    const files = new Set<string>();
+    const docs: { [id: string]: any } = {};
+    const fn = (doc: any): string[] => {
+        const { id } = doc;
+        if (typeof id === 'string' && id.endsWith('Proto')) {
+            // Skip protos
+            return [];
+        }
+        const ids: string[] = [];
+        // eslint-disable-next-line no-restricted-syntax
+        for (const key in doc.fields) {
+            // eslint-disable-next-line no-continue
+            if (!Object.prototype.hasOwnProperty.call(doc.fields, key)) continue;
+
+            const field = doc.fields[key];
+            // eslint-disable-next-line no-continue
+            if (field === undefined || field === null) continue;
+
+            if (field.__type === 'proxy' || field.__type === 'prefetch_proxy') {
+                ids.push(field.fieldId);
+            } else if (field.__type === 'script' || field.__type === 'computed') {
+                field.captures && ids.push(field.captures.fieldId);
+            } else if (field.__type === 'list') {
+                ids.push(...fn(field));
+            } else if (typeof field === 'string') {
+                const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w-]*)"/g;
+                for (let match = re.exec(field); match !== null; match = re.exec(field)) {
+                    ids.push(match[1]);
+                }
+            } else if (field.__type === 'RichTextField') {
+                const re = /"href"\s*:\s*"(.*?)"/g;
+                for (let match = re.exec(field.data); match !== null; match = re.exec(field.Data)) {
+                    const urlString = match[1];
+                    const split = new URL(urlString).pathname.split('doc/');
+                    if (split.length > 1) {
+                        ids.push(split[split.length - 1]);
+                    }
+                }
+                const re2 = /"src"\s*:\s*"(.*?)"/g;
+                for (let match = re2.exec(field.Data); match !== null; match = re2.exec(field.Data)) {
+                    const urlString = match[1];
+                    const { pathname } = new URL(urlString);
+                    files.add(pathname);
+                }
+            } else if (['audio', 'image', 'video', 'pdf', 'web', 'map'].includes(field.__type)) {
+                const { pathname } = new URL(field.url);
+                files.add(pathname);
             }
-        } else {
-            // we've hit a collection, so we have to recurse
-            await writeHierarchyRecursive(file, result, `${prefix}/${documentTitle}`);
         }
+
+        if (doc.id) {
+            docs[doc.id] = doc;
+        }
+        return ids;
+    };
+    await Database.Instance.visit([id], fn);
+    return { id, docs, files };
+}
+
+export default class DownloadManager extends ApiManager {
+    protected initialize(register: Registration): void {
+        /**
+         * Let's say someone's using Dash to organize images in collections.
+         * This lets them export the hierarchy they've built to their
+         * own file system in a useful format.
+         *
+         * This handler starts with a single document id (interesting only
+         * if it's that of a collection). It traverses the database, captures
+         * the nesting of only nested images or collections, writes
+         * that to a zip file and returns it to the client for download.
+         */
+        register({
+            method: Method.GET,
+            subscription: new RouteSubscriber('imageHierarchyExport').add('docId'),
+            secureHandler: async ({ req, res }) => {
+                const id = req.params.docId;
+                const hierarchy: Hierarchy = {};
+                await buildHierarchyRecursive(id, hierarchy);
+                return BuildAndDispatchZip(res, zip => writeHierarchyRecursive(zip, hierarchy));
+            },
+        });
+
+        register({
+            method: Method.GET,
+            subscription: new RouteSubscriber('downloadId').add('docId'),
+            secureHandler: async ({ req, res }) =>
+                BuildAndDispatchZip(res, async zip => {
+                    const { id, docs, files } = await getDocs(req.params.docId);
+                    const docString = JSON.stringify({ id, docs });
+                    zip.append(docString, { name: 'doc.json' });
+                    files.forEach(val => {
+                        zip.file(publicDirectory + val, { name: val.substring(1) });
+                    });
+                }),
+        });
+
+        register({
+            method: Method.GET,
+            subscription: new RouteSubscriber('serializeDoc').add('docId'),
+            secureHandler: async ({ req, res }) => {
+                const { docs, files } = await getDocs(req.params.docId);
+                res.send({ docs, files: Array.from(files) });
+            },
+        });
     }
-}
\ No newline at end of file
+}
diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts
index f94b77cac..12913b1ef 100644
--- a/src/server/ApiManagers/GeneralGoogleManager.ts
+++ b/src/server/ApiManagers/GeneralGoogleManager.ts
@@ -1,51 +1,49 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method, _permission_denied } from "../RouteManager";
-import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils";
-import RouteSubscriber from "../RouteSubscriber";
-import { Database } from "../database";
+import ApiManager, { Registration } from './ApiManager';
+import { Method } from '../RouteManager';
+import { GoogleApiServerUtils } from '../apis/google/GoogleApiServerUtils';
+import RouteSubscriber from '../RouteSubscriber';
+import { Database } from '../database';
 
 const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerUtils.ApiRouter>([
-    ["create", (api, params) => api.create(params)],
-    ["retrieve", (api, params) => api.get(params)],
-    ["update", (api, params) => api.batchUpdate(params)],
+    ['create', (api, params) => api.create(params)],
+    ['retrieve', (api, params) => api.get(params)],
+    ['update', (api, params) => api.batchUpdate(params)],
 ]);
 
 export default class GeneralGoogleManager extends ApiManager {
-
     protected initialize(register: Registration): void {
-
         register({
             method: Method.GET,
-            subscription: "/readGoogleAccessToken",
+            subscription: '/readGoogleAccessToken',
             secureHandler: async ({ user, res }) => {
-                const { credentials } = (await GoogleApiServerUtils.retrieveCredentials(user.id));
+                const { credentials } = await GoogleApiServerUtils.retrieveCredentials(user.id);
                 if (!credentials?.access_token) {
                     return res.send(GoogleApiServerUtils.generateAuthenticationUrl());
                 }
                 return res.send(credentials);
-            }
+            },
         });
 
         register({
             method: Method.POST,
-            subscription: "/writeGoogleAccessToken",
+            subscription: '/writeGoogleAccessToken',
             secureHandler: async ({ user, req, res }) => {
                 res.send(await GoogleApiServerUtils.processNewUser(user.id, req.body.authenticationCode));
-            }
+            },
         });
 
         register({
             method: Method.GET,
-            subscription: "/revokeGoogleAccessToken",
+            subscription: '/revokeGoogleAccessToken',
             secureHandler: async ({ user, res }) => {
                 await Database.Auxiliary.GoogleAccessToken.Revoke(user.id);
                 res.send();
-            }
+            },
         });
 
         register({
             method: Method.POST,
-            subscription: new RouteSubscriber("googleDocs").add("sector", "action"),
+            subscription: new RouteSubscriber('googleDocs').add('sector', 'action'),
             secureHandler: async ({ req, res, user }) => {
                 const sector: GoogleApiServerUtils.Service = req.params.sector as GoogleApiServerUtils.Service;
                 const action: GoogleApiServerUtils.Action = req.params.action as GoogleApiServerUtils.Action;
@@ -61,8 +59,7 @@ export default class GeneralGoogleManager extends ApiManager {
                     return;
                 }
                 res.send(undefined);
-            }
+            },
         });
-
     }
-}
\ No newline at end of file
+}
diff --git a/src/server/ApiManagers/MongoStore.js b/src/server/ApiManagers/MongoStore.js
new file mode 100644
index 000000000..28515fee4
--- /dev/null
+++ b/src/server/ApiManagers/MongoStore.js
@@ -0,0 +1,414 @@
+'use strict';
+var __createBinding =
+    (this && this.__createBinding) ||
+    (Object.create
+        ? function (o, m, k, k2) {
+              if (k2 === undefined) k2 = k;
+              var desc = Object.getOwnPropertyDescriptor(m, k);
+              if (!desc || ('get' in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+                  desc = {
+                      enumerable: true,
+                      get: function () {
+                          return m[k];
+                      },
+                  };
+              }
+              Object.defineProperty(o, k2, desc);
+          }
+        : function (o, m, k, k2) {
+              if (k2 === undefined) k2 = k;
+              o[k2] = m[k];
+          });
+var __setModuleDefault =
+    (this && this.__setModuleDefault) ||
+    (Object.create
+        ? function (o, v) {
+              Object.defineProperty(o, 'default', { enumerable: true, value: v });
+          }
+        : function (o, v) {
+              o['default'] = v;
+          });
+var __importStar =
+    (this && this.__importStar) ||
+    function (mod) {
+        if (mod && mod.__esModule) return mod;
+        var result = {};
+        if (mod != null) for (var k in mod) if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+        __setModuleDefault(result, mod);
+        return result;
+    };
+var __importDefault =
+    (this && this.__importDefault) ||
+    function (mod) {
+        return mod && mod.__esModule ? mod : { default: mod };
+    };
+Object.defineProperty(exports, '__esModule', { value: true });
+const console_1 = require('console');
+const util_1 = __importDefault(require('util'));
+const session = __importStar(require('express-session'));
+const mongodb_1 = require('mongodb');
+const debug_1 = __importDefault(require('debug'));
+const debug = (0, debug_1.default)('connect-mongo');
+// eslint-disable-next-line @typescript-eslint/no-empty-function
+const noop = () => {};
+const unit = a => a;
+function defaultSerializeFunction(session) {
+    // Copy each property of the session to a new object
+    const obj = {};
+    let prop;
+    for (prop in session) {
+        if (prop === 'cookie') {
+            // Convert the cookie instance to an object, if possible
+            // This gets rid of the duplicate object under session.cookie.data property
+            // @ts-ignore FIXME:
+            obj.cookie = session.cookie.toJSON
+                ? // @ts-ignore FIXME:
+                  session.cookie.toJSON()
+                : session.cookie;
+        } else {
+            // @ts-ignore FIXME:
+            obj[prop] = session[prop];
+        }
+    }
+    return obj;
+}
+function computeTransformFunctions(options) {
+    if (options.serialize || options.unserialize) {
+        return {
+            serialize: options.serialize || defaultSerializeFunction,
+            unserialize: options.unserialize || unit,
+        };
+    }
+    if (options.stringify === false) {
+        return {
+            serialize: defaultSerializeFunction,
+            unserialize: unit,
+        };
+    }
+    // Default case
+    return {
+        serialize: JSON.stringify,
+        unserialize: JSON.parse,
+    };
+}
+class MongoStore extends session.Store {
+    constructor({ collectionName = 'sessions', ttl = 1209600, mongoOptions = {}, autoRemove = 'native', autoRemoveInterval = 10, touchAfter = 0, stringify = true, crypto, ...required }) {
+        super();
+        this.crypto = null;
+        debug('create MongoStore instance');
+        const options = {
+            collectionName,
+            ttl,
+            mongoOptions,
+            autoRemove,
+            autoRemoveInterval,
+            touchAfter,
+            stringify,
+            crypto: {
+                ...{
+                    secret: false,
+                    algorithm: 'aes-256-gcm',
+                    hashing: 'sha512',
+                    encodeas: 'base64',
+                    key_size: 32,
+                    iv_size: 16,
+                    at_size: 16,
+                },
+                ...crypto,
+            },
+            ...required,
+        };
+        // Check params
+        (0, console_1.assert)(options.mongoUrl || options.clientPromise || options.client, 'You must provide either mongoUrl|clientPromise|client in options');
+        (0, console_1.assert)(options.createAutoRemoveIdx === null || options.createAutoRemoveIdx === undefined, 'options.createAutoRemoveIdx has been reverted to autoRemove and autoRemoveInterval');
+        (0, console_1.assert)(!options.autoRemoveInterval || options.autoRemoveInterval <= 71582, /* (Math.pow(2, 32) - 1) / (1000 * 60) */ 'autoRemoveInterval is too large. options.autoRemoveInterval is in minutes but not seconds nor mills');
+        this.transformFunctions = computeTransformFunctions(options);
+        let _clientP;
+        if (options.mongoUrl) {
+            _clientP = mongodb_1.MongoClient.connect(options.mongoUrl, options.mongoOptions);
+        } else if (options.clientPromise) {
+            _clientP = options.clientPromise;
+        } else if (options.client) {
+            _clientP = Promise.resolve(options.client);
+        } else {
+            throw new Error('Cannot init client. Please provide correct options');
+        }
+        (0, console_1.assert)(!!_clientP, 'Client is null|undefined');
+        this.clientP = _clientP;
+        this.options = options;
+        this.collectionP = _clientP.then(async con => {
+            const collection = con.db(options.dbName).collection(options.collectionName);
+            await this.setAutoRemove(collection);
+            return collection;
+        });
+        if (options.crypto.secret) {
+            this.crypto = require('kruptein')(options.crypto);
+        }
+    }
+    static create(options) {
+        return new MongoStore(options);
+    }
+    setAutoRemove(collection) {
+        const removeQuery = () => ({
+            expires: {
+                $lt: new Date(),
+            },
+        });
+        switch (this.options.autoRemove) {
+            case 'native':
+                debug('Creating MongoDB TTL index');
+                return collection.createIndex(
+                    { expires: 1 },
+                    {
+                        background: true,
+                        expireAfterSeconds: 0,
+                    }
+                );
+            case 'interval':
+                debug('create Timer to remove expired sessions');
+                this.timer = setInterval(
+                    () =>
+                        collection.deleteMany(removeQuery(), {
+                            writeConcern: {
+                                w: 0,
+                                j: false,
+                            },
+                        }),
+                    this.options.autoRemoveInterval * 1000 * 60
+                );
+                this.timer.unref();
+                return Promise.resolve();
+            case 'disabled':
+            default:
+                return Promise.resolve();
+        }
+    }
+    computeStorageId(sessionId) {
+        if (this.options.transformId && typeof this.options.transformId === 'function') {
+            return this.options.transformId(sessionId);
+        }
+        return sessionId;
+    }
+    /**
+     * promisify and bind the `this.crypto.get` function.
+     * Please check !!this.crypto === true before using this getter!
+     */
+    get cryptoGet() {
+        if (!this.crypto) {
+            throw new Error('Check this.crypto before calling this.cryptoGet!');
+        }
+        return util_1.default.promisify(this.crypto.get).bind(this.crypto);
+    }
+    /**
+     * Decrypt given session data
+     * @param session session data to be decrypt. Mutate the input session.
+     */
+    async decryptSession(session) {
+        if (this.crypto && session) {
+            const plaintext = await this.cryptoGet(this.options.crypto.secret, session.session).catch(err => {
+                throw new Error(err);
+            });
+            // @ts-ignore
+            session.session = JSON.parse(plaintext);
+        }
+    }
+    /**
+     * Get a session from the store given a session ID (sid)
+     * @param sid session ID
+     */
+    get(sid, callback) {
+        (async () => {
+            try {
+                debug(`MongoStore#get=${sid}`);
+                const collection = await this.collectionP;
+                const session = await collection.findOne({
+                    _id: this.computeStorageId(sid),
+                    $or: [{ expires: { $exists: false } }, { expires: { $gt: new Date() } }],
+                });
+                if (this.crypto && session) {
+                    await this.decryptSession(session).catch(err => callback(err));
+                }
+                const s = session && this.transformFunctions.unserialize(session.session);
+                if (this.options.touchAfter > 0 && (session === null || session === void 0 ? void 0 : session.lastModified)) {
+                    s.lastModified = session.lastModified;
+                }
+                this.emit('get', sid);
+                callback(null, s === undefined ? null : s);
+            } catch (error) {
+                callback(error);
+            }
+        })();
+    }
+    /**
+     * Upsert a session into the store given a session ID (sid) and session (session) object.
+     * @param sid session ID
+     * @param session session object
+     */
+    set(sid, session, callback = noop) {
+        (async () => {
+            var _a;
+            try {
+                debug(`MongoStore#set=${sid}`);
+                // Removing the lastModified prop from the session object before update
+                // @ts-ignore
+                if (this.options.touchAfter > 0 && (session === null || session === void 0 ? void 0 : session.lastModified)) {
+                    // @ts-ignore
+                    delete session.lastModified;
+                }
+                const s = {
+                    _id: this.computeStorageId(sid),
+                    session: this.transformFunctions.serialize(session),
+                };
+                // Expire handling
+                if ((_a = session === null || session === void 0 ? void 0 : session.cookie) === null || _a === void 0 ? void 0 : _a.expires) {
+                    s.expires = new Date(session.cookie.expires);
+                } else {
+                    // If there's no expiration date specified, it is
+                    // browser-session cookie or there is no cookie at all,
+                    // as per the connect docs.
+                    //
+                    // So we set the expiration to two-weeks from now
+                    // - as is common practice in the industry (e.g Django) -
+                    // or the default specified in the options.
+                    s.expires = new Date(Date.now() + this.options.ttl * 1000);
+                }
+                // Last modify handling
+                if (this.options.touchAfter > 0) {
+                    s.lastModified = new Date();
+                }
+                if (this.crypto) {
+                    const cryptoSet = util_1.default.promisify(this.crypto.set).bind(this.crypto);
+                    const data = await cryptoSet(this.options.crypto.secret, s.session).catch(err => {
+                        throw new Error(err);
+                    });
+                    s.session = data;
+                }
+                const collection = await this.collectionP;
+                const rawResp = await collection.updateOne(
+                    { _id: s._id },
+                    { $set: s },
+                    {
+                        upsert: true,
+                        writeConcern: this.options.writeOperationOptions,
+                    }
+                );
+                if (rawResp.upsertedCount > 0) {
+                    this.emit('create', sid);
+                } else {
+                    this.emit('update', sid);
+                }
+                this.emit('set', sid);
+            } catch (error) {
+                return callback(error);
+            }
+            return callback(null);
+        })();
+    }
+    touch(sid, session, callback = noop) {
+        (async () => {
+            var _a;
+            try {
+                debug(`MongoStore#touch=${sid}`);
+                const updateFields = {};
+                const touchAfter = this.options.touchAfter * 1000;
+                const lastModified = session.lastModified ? session.lastModified.getTime() : 0;
+                const currentDate = new Date();
+                // If the given options has a touchAfter property, check if the
+                // current timestamp - lastModified timestamp is bigger than
+                // the specified, if it's not, don't touch the session
+                if (touchAfter > 0 && lastModified > 0) {
+                    const timeElapsed = currentDate.getTime() - lastModified;
+                    if (timeElapsed < touchAfter) {
+                        debug(`Skip touching session=${sid}`);
+                        return callback(null);
+                    }
+                    updateFields.lastModified = currentDate;
+                }
+                if ((_a = session === null || session === void 0 ? void 0 : session.cookie) === null || _a === void 0 ? void 0 : _a.expires) {
+                    updateFields.expires = new Date(session.cookie.expires);
+                } else {
+                    updateFields.expires = new Date(Date.now() + this.options.ttl * 1000);
+                }
+                const collection = await this.collectionP;
+                const rawResp = await collection.updateOne({ _id: this.computeStorageId(sid) }, { $set: updateFields }, { writeConcern: this.options.writeOperationOptions });
+                if (rawResp.matchedCount === 0) {
+                    return callback(new Error('Unable to find the session to touch'));
+                } else {
+                    this.emit('touch', sid, session);
+                    return callback(null);
+                }
+            } catch (error) {
+                return callback(error);
+            }
+        })();
+    }
+    /**
+     * Get all sessions in the store as an array
+     */
+    all(callback) {
+        (async () => {
+            try {
+                debug('MongoStore#all()');
+                const collection = await this.collectionP;
+                const sessions = collection.find({
+                    $or: [{ expires: { $exists: false } }, { expires: { $gt: new Date() } }],
+                });
+                const results = [];
+                for await (const session of sessions) {
+                    if (this.crypto && session) {
+                        await this.decryptSession(session);
+                    }
+                    results.push(this.transformFunctions.unserialize(session.session));
+                }
+                this.emit('all', results);
+                callback(null, results);
+            } catch (error) {
+                callback(error);
+            }
+        })();
+    }
+    /**
+     * Destroy/delete a session from the store given a session ID (sid)
+     * @param sid session ID
+     */
+    destroy(sid, callback = noop) {
+        debug(`MongoStore#destroy=${sid}`);
+        this.collectionP
+            .then(colleciton => colleciton.deleteOne({ _id: this.computeStorageId(sid) }, { writeConcern: this.options.writeOperationOptions }))
+            .then(() => {
+                this.emit('destroy', sid);
+                callback(null);
+            })
+            .catch(err => callback(err));
+    }
+    /**
+     * Get the count of all sessions in the store
+     */
+    length(callback) {
+        debug('MongoStore#length()');
+        this.collectionP
+            .then(collection => collection.countDocuments())
+            .then(c => callback(null, c))
+            // @ts-ignore
+            .catch(err => callback(err));
+    }
+    /**
+     * Delete all sessions from the store.
+     */
+    clear(callback = noop) {
+        debug('MongoStore#clear()');
+        this.collectionP
+            .then(collection => collection.drop())
+            .then(() => callback(null))
+            .catch(err => callback(err));
+    }
+    /**
+     * Close database connection
+     */
+    close() {
+        debug('MongoStore#close()');
+        return this.clientP.then(c => c.close());
+    }
+}
+exports.default = MongoStore;
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTW9uZ29TdG9yZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvTW9uZ29TdG9yZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEscUNBQWdDO0FBQ2hDLGdEQUF1QjtBQUN2Qix5REFBMEM7QUFDMUMscUNBS2dCO0FBQ2hCLGtEQUF5QjtBQUd6QixNQUFNLEtBQUssR0FBRyxJQUFBLGVBQUssRUFBQyxlQUFlLENBQUMsQ0FBQTtBQWdFcEMsZ0VBQWdFO0FBQ2hFLE1BQU0sSUFBSSxHQUFHLEdBQUcsRUFBRSxHQUFFLENBQUMsQ0FBQTtBQUNyQixNQUFNLElBQUksR0FBbUIsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQTtBQUVyQyxTQUFTLHdCQUF3QixDQUMvQixPQUE0QjtJQUU1QixvREFBb0Q7SUFDcEQsTUFBTSxHQUFHLEdBQUcsRUFBRSxDQUFBO0lBQ2QsSUFBSSxJQUFJLENBQUE7SUFDUixLQUFLLElBQUksSUFBSSxPQUFPLEVBQUU7UUFDcEIsSUFBSSxJQUFJLEtBQUssUUFBUSxFQUFFO1lBQ3JCLHdEQUF3RDtZQUN4RCwyRUFBMkU7WUFDM0Usb0JBQW9CO1lBQ3BCLEdBQUcsQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxNQUFNO2dCQUNoQyxDQUFDLENBQUMsb0JBQW9CO29CQUNwQixPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRTtnQkFDekIsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUE7U0FDbkI7YUFBTTtZQUNMLG9CQUFvQjtZQUNwQixHQUFHLENBQUMsSUFBSSxDQUFDLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFBO1NBQzFCO0tBQ0Y7SUFFRCxPQUFPLEdBQTBCLENBQUE7QUFDbkMsQ0FBQztBQUVELFNBQVMseUJBQXlCLENBQUMsT0FBbUM7SUFDcEUsSUFBSSxPQUFPLENBQUMsU0FBUyxJQUFJLE9BQU8sQ0FBQyxXQUFXLEVBQUU7UUFDNUMsT0FBTztZQUNMLFNBQVMsRUFBRSxPQUFPLENBQUMsU0FBUyxJQUFJLHdCQUF3QjtZQUN4RCxXQUFXLEVBQUUsT0FBTyxDQUFDLFdBQVcsSUFBSSxJQUFJO1NBQ3pDLENBQUE7S0FDRjtJQUVELElBQUksT0FBTyxDQUFDLFNBQVMsS0FBSyxLQUFLLEVBQUU7UUFDL0IsT0FBTztZQUNMLFNBQVMsRUFBRSx3QkFBd0I7WUFDbkMsV0FBVyxFQUFFLElBQUk7U0FDbEIsQ0FBQTtLQUNGO0lBQ0QsZUFBZTtJQUNmLE9BQU87UUFDTCxTQUFTLEVBQUUsSUFBSSxDQUFDLFNBQVM7UUFDekIsV0FBVyxFQUFFLElBQUksQ0FBQyxLQUFLO0tBQ3hCLENBQUE7QUFDSCxDQUFDO0FBRUQsTUFBcUIsVUFBVyxTQUFRLE9BQU8sQ0FBQyxLQUFLO0lBWW5ELFlBQVksRUFDVixjQUFjLEdBQUcsVUFBVSxFQUMzQixHQUFHLEdBQUcsT0FBTyxFQUNiLFlBQVksR0FBRyxFQUFFLEVBQ2pCLFVBQVUsR0FBRyxRQUFRLEVBQ3JCLGtCQUFrQixHQUFHLEVBQUUsRUFDdkIsVUFBVSxHQUFHLENBQUMsRUFDZCxTQUFTLEdBQUcsSUFBSSxFQUNoQixNQUFNLEVBQ04sR0FBRyxRQUFRLEVBQ1M7UUFDcEIsS0FBSyxFQUFFLENBQUE7UUFyQkQsV0FBTSxHQUFvQixJQUFJLENBQUE7UUFzQnBDLEtBQUssQ0FBQyw0QkFBNEIsQ0FBQyxDQUFBO1FBQ25DLE1BQU0sT0FBTyxHQUErQjtZQUMxQyxjQUFjO1lBQ2QsR0FBRztZQUNILFlBQVk7WUFDWixVQUFVO1lBQ1Ysa0JBQWtCO1lBQ2xCLFVBQVU7WUFDVixTQUFTO1lBQ1QsTUFBTSxFQUFFO2dCQUNOLEdBQUc7b0JBQ0QsTUFBTSxFQUFFLEtBQUs7b0JBQ2IsU0FBUyxFQUFFLGFBQWE7b0JBQ3hCLE9BQU8sRUFBRSxRQUFRO29CQUNqQixRQUFRLEVBQUUsUUFBUTtvQkFDbEIsUUFBUSxFQUFFLEVBQUU7b0JBQ1osT0FBTyxFQUFFLEVBQUU7b0JBQ1gsT0FBTyxFQUFFLEVBQUU7aUJBQ1o7Z0JBQ0QsR0FBRyxNQUFNO2FBQ1Y7WUFDRCxHQUFHLFFBQVE7U0FDWixDQUFBO1FBQ0QsZUFBZTtRQUNmLElBQUEsZ0JBQU0sRUFDSixPQUFPLENBQUMsUUFBUSxJQUFJLE9BQU8sQ0FBQyxhQUFhLElBQUksT0FBTyxDQUFDLE1BQU0sRUFDM0Qsa0VBQWtFLENBQ25FLENBQUE7UUFDRCxJQUFBLGdCQUFNLEVBQ0osT0FBTyxDQUFDLG1CQUFtQixLQUFLLElBQUk7WUFDbEMsT0FBTyxDQUFDLG1CQUFtQixLQUFLLFNBQVMsRUFDM0Msb0ZBQW9GLENBQ3JGLENBQUE7UUFDRCxJQUFBLGdCQUFNLEVBQ0osQ0FBQyxPQUFPLENBQUMsa0JBQWtCLElBQUksT0FBTyxDQUFDLGtCQUFrQixJQUFJLEtBQUs7UUFDbEUseUNBQXlDLENBQUMscUdBQXFHLENBQ2hKLENBQUE7UUFDRCxJQUFJLENBQUMsa0JBQWtCLEdBQUcseUJBQXlCLENBQUMsT0FBTyxDQUFDLENBQUE7UUFDNUQsSUFBSSxRQUE4QixDQUFBO1FBQ2xDLElBQUksT0FBTyxDQUFDLFFBQVEsRUFBRTtZQUNwQixRQUFRLEdBQUcscUJBQVcsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLFFBQVEsRUFBRSxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUE7U0FDdkU7YUFBTSxJQUFJLE9BQU8sQ0FBQyxhQUFhLEVBQUU7WUFDaEMsUUFBUSxHQUFHLE9BQU8sQ0FBQyxhQUFhLENBQUE7U0FDakM7YUFBTSxJQUFJLE9BQU8sQ0FBQyxNQUFNLEVBQUU7WUFDekIsUUFBUSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFBO1NBQzNDO2FBQU07WUFDTCxNQUFNLElBQUksS0FBSyxDQUFDLG9EQUFvRCxDQUFDLENBQUE7U0FDdEU7UUFDRCxJQUFBLGdCQUFNLEVBQUMsQ0FBQyxDQUFDLFFBQVEsRUFBRSwwQkFBMEIsQ0FBQyxDQUFBO1FBQzlDLElBQUksQ0FBQyxPQUFPLEdBQUcsUUFBUSxDQUFBO1FBQ3ZCLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFBO1FBQ3RCLElBQUksQ0FBQyxXQUFXLEdBQUcsUUFBUSxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLEVBQUU7WUFDN0MsTUFBTSxVQUFVLEdBQUcsR0FBRztpQkFDbkIsRUFBRSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7aUJBQ2xCLFVBQVUsQ0FBc0IsT0FBTyxDQUFDLGNBQWMsQ0FBQyxDQUFBO1lBQzFELE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FBQyxVQUFVLENBQUMsQ0FBQTtZQUNwQyxPQUFPLFVBQVUsQ0FBQTtRQUNuQixDQUFDLENBQUMsQ0FBQTtRQUNGLElBQUksT0FBTyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUU7WUFDekIsSUFBSSxDQUFDLE1BQU0sR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFBO1NBQ2xEO0lBQ0gsQ0FBQztJQUVELE1BQU0sQ0FBQyxNQUFNLENBQUMsT0FBNEI7UUFDeEMsT0FBTyxJQUFJLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQTtJQUNoQyxDQUFDO0lBRU8sYUFBYSxDQUNuQixVQUEyQztRQUUzQyxNQUFNLFdBQVcsR0FBRyxHQUFHLEVBQUUsQ0FBQyxDQUFDO1lBQ3pCLE9BQU8sRUFBRTtnQkFDUCxHQUFHLEVBQUUsSUFBSSxJQUFJLEVBQUU7YUFDaEI7U0FDRixDQUFDLENBQUE7UUFDRixRQUFRLElBQUksQ0FBQyxPQUFPLENBQUMsVUFBVSxFQUFFO1lBQy9CLEtBQUssUUFBUTtnQkFDWCxLQUFLLENBQUMsNEJBQTRCLENBQUMsQ0FBQTtnQkFDbkMsT0FBTyxVQUFVLENBQUMsV0FBVyxDQUMzQixFQUFFLE9BQU8sRUFBRSxDQUFDLEVBQUUsRUFDZDtvQkFDRSxVQUFVLEVBQUUsSUFBSTtvQkFDaEIsa0JBQWtCLEVBQUUsQ0FBQztpQkFDdEIsQ0FDRixDQUFBO1lBQ0gsS0FBSyxVQUFVO2dCQUNiLEtBQUssQ0FBQyx5Q0FBeUMsQ0FBQyxDQUFBO2dCQUNoRCxJQUFJLENBQUMsS0FBSyxHQUFHLFdBQVcsQ0FDdEIsR0FBRyxFQUFFLENBQ0gsVUFBVSxDQUFDLFVBQVUsQ0FBQyxXQUFXLEVBQUUsRUFBRTtvQkFDbkMsWUFBWSxFQUFFO3dCQUNaLENBQUMsRUFBRSxDQUFDO3dCQUNKLENBQUMsRUFBRSxLQUFLO3FCQUNUO2lCQUNGLENBQUMsRUFDSixJQUFJLENBQUMsT0FBTyxDQUFDLGtCQUFrQixHQUFHLElBQUksR0FBRyxFQUFFLENBQzVDLENBQUE7Z0JBQ0QsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsQ0FBQTtnQkFDbEIsT0FBTyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUE7WUFDMUIsS0FBSyxVQUFVLENBQUM7WUFDaEI7Z0JBQ0UsT0FBTyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUE7U0FDM0I7SUFDSCxDQUFDO0lBRU8sZ0JBQWdCLENBQUMsU0FBaUI7UUFDeEMsSUFDRSxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVc7WUFDeEIsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsS0FBSyxVQUFVLEVBQzlDO1lBQ0EsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUMsQ0FBQTtTQUMzQztRQUNELE9BQU8sU0FBUyxDQUFBO0lBQ2xCLENBQUM7SUFFRDs7O09BR0c7SUFDSCxJQUFZLFNBQVM7UUFDbkIsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUU7WUFDaEIsTUFBTSxJQUFJLEtBQUssQ0FBQyxrREFBa0QsQ0FBQyxDQUFBO1NBQ3BFO1FBQ0QsT0FBTyxjQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQTtJQUMxRCxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ssS0FBSyxDQUFDLGNBQWMsQ0FDMUIsT0FBK0M7UUFFL0MsSUFBSSxJQUFJLENBQUMsTUFBTSxJQUFJLE9BQU8sRUFBRTtZQUMxQixNQUFNLFNBQVMsR0FBRyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQ3BDLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLE1BQWdCLEVBQ3BDLE9BQU8sQ0FBQyxPQUFPLENBQ2hCLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUU7Z0JBQ2QsTUFBTSxJQUFJLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQTtZQUN0QixDQUFDLENBQUMsQ0FBQTtZQUNGLGFBQWE7WUFDYixPQUFPLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUE7U0FDeEM7SUFDSCxDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsR0FBRyxDQUNELEdBQVcsRUFDWCxRQUFrRTtRQUVsRSxDQUFDO1FBQUEsQ0FBQyxLQUFLLElBQUksRUFBRTtZQUNYLElBQUk7Z0JBQ0YsS0FBSyxDQUFDLGtCQUFrQixHQUFHLEVBQUUsQ0FBQyxDQUFBO2dCQUM5QixNQUFNLFVBQVUsR0FBRyxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUE7Z0JBQ3pDLE1BQU0sT0FBTyxHQUFHLE1BQU0sVUFBVSxDQUFDLE9BQU8sQ0FBQztvQkFDdkMsR0FBRyxFQUFFLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUM7b0JBQy9CLEdBQUcsRUFBRTt3QkFDSCxFQUFFLE9BQU8sRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsRUFBRTt3QkFDL0IsRUFBRSxPQUFPLEVBQUUsRUFBRSxHQUFHLEVBQUUsSUFBSSxJQUFJLEVBQUUsRUFBRSxFQUFFO3FCQUNqQztpQkFDRixDQUFDLENBQUE7Z0JBQ0YsSUFBSSxJQUFJLENBQUMsTUFBTSxJQUFJLE9BQU8sRUFBRTtvQkFDMUIsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUN2QixPQUF5QyxDQUMxQyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUE7aUJBQ2hDO2dCQUNELE1BQU0sQ0FBQyxHQUNMLE9BQU8sSUFBSSxJQUFJLENBQUMsa0JBQWtCLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQTtnQkFDakUsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLFVBQVUsR0FBRyxDQUFDLEtBQUksT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFFLFlBQVksQ0FBQSxFQUFFO29CQUN4RCxDQUFDLENBQUMsWUFBWSxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUE7aUJBQ3RDO2dCQUNELElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFBO2dCQUNyQixRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7YUFDM0M7WUFBQyxPQUFPLEtBQUssRUFBRTtnQkFDZCxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUE7YUFDaEI7UUFDSCxDQUFDLENBQUMsRUFBRSxDQUFBO0lBQ04sQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxHQUFHLENBQ0QsR0FBVyxFQUNYLE9BQTRCLEVBQzVCLFdBQStCLElBQUk7UUFFbkMsQ0FBQztRQUFBLENBQUMsS0FBSyxJQUFJLEVBQUU7O1lBQ1gsSUFBSTtnQkFDRixLQUFLLENBQUMsa0JBQWtCLEdBQUcsRUFBRSxDQUFDLENBQUE7Z0JBQzlCLHVFQUF1RTtnQkFDdkUsYUFBYTtnQkFDYixJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsVUFBVSxHQUFHLENBQUMsS0FBSSxPQUFPLGFBQVAsT0FBTyx1QkFBUCxPQUFPLENBQUUsWUFBWSxDQUFBLEVBQUU7b0JBQ3hELGFBQWE7b0JBQ2IsT0FBTyxPQUFPLENBQUMsWUFBWSxDQUFBO2lCQUM1QjtnQkFDRCxNQUFNLENBQUMsR0FBd0I7b0JBQzdCLEdBQUcsRUFBRSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDO29CQUMvQixPQUFPLEVBQUUsSUFBSSxDQUFDLGtCQUFrQixDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUM7aUJBQ3BELENBQUE7Z0JBQ0Qsa0JBQWtCO2dCQUNsQixJQUFJLE1BQUEsT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFFLE1BQU0sMENBQUUsT0FBTyxFQUFFO29CQUM1QixDQUFDLENBQUMsT0FBTyxHQUFHLElBQUksSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUE7aUJBQzdDO3FCQUFNO29CQUNMLGlEQUFpRDtvQkFDakQsdURBQXVEO29CQUN2RCwyQkFBMkI7b0JBQzNCLEVBQUU7b0JBQ0YsaURBQWlEO29CQUNqRCx5REFBeUQ7b0JBQ3pELDJDQUEyQztvQkFDM0MsQ0FBQyxDQUFDLE9BQU8sR0FBRyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEdBQUcsSUFBSSxDQUFDLENBQUE7aUJBQzNEO2dCQUNELHVCQUF1QjtnQkFDdkIsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLFVBQVUsR0FBRyxDQUFDLEVBQUU7b0JBQy9CLENBQUMsQ0FBQyxZQUFZLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQTtpQkFDNUI7Z0JBQ0QsSUFBSSxJQUFJLENBQUMsTUFBTSxFQUFFO29CQUNmLE1BQU0sU0FBUyxHQUFHLGNBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFBO29CQUNuRSxNQUFNLElBQUksR0FBRyxNQUFNLFNBQVMsQ0FDMUIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsTUFBZ0IsRUFDcEMsQ0FBQyxDQUFDLE9BQU8sQ0FDVixDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFO3dCQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUE7b0JBQ3RCLENBQUMsQ0FBQyxDQUFBO29CQUNGLENBQUMsQ0FBQyxPQUFPLEdBQUcsSUFBc0MsQ0FBQTtpQkFDbkQ7Z0JBQ0QsTUFBTSxVQUFVLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFBO2dCQUN6QyxNQUFNLE9BQU8sR0FBRyxNQUFNLFVBQVUsQ0FBQyxTQUFTLENBQ3hDLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFDZCxFQUFFLElBQUksRUFBRSxDQUFDLEVBQUUsRUFDWDtvQkFDRSxNQUFNLEVBQUUsSUFBSTtvQkFDWixZQUFZLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUI7aUJBQ2pELENBQ0YsQ0FBQTtnQkFDRCxJQUFJLE9BQU8sQ0FBQyxhQUFhLEdBQUcsQ0FBQyxFQUFFO29CQUM3QixJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxHQUFHLENBQUMsQ0FBQTtpQkFDekI7cUJBQU07b0JBQ0wsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsR0FBRyxDQUFDLENBQUE7aUJBQ3pCO2dCQUNELElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFBO2FBQ3RCO1lBQUMsT0FBTyxLQUFLLEVBQUU7Z0JBQ2QsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUE7YUFDdkI7WUFDRCxPQUFPLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQTtRQUN2QixDQUFDLENBQUMsRUFBRSxDQUFBO0lBQ04sQ0FBQztJQUVELEtBQUssQ0FDSCxHQUFXLEVBQ1gsT0FBc0QsRUFDdEQsV0FBK0IsSUFBSTtRQUVuQyxDQUFDO1FBQUEsQ0FBQyxLQUFLLElBQUksRUFBRTs7WUFDWCxJQUFJO2dCQUNGLEtBQUssQ0FBQyxvQkFBb0IsR0FBRyxFQUFFLENBQUMsQ0FBQTtnQkFDaEMsTUFBTSxZQUFZLEdBSWQsRUFBRSxDQUFBO2dCQUNOLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQTtnQkFDakQsTUFBTSxZQUFZLEdBQUcsT0FBTyxDQUFDLFlBQVk7b0JBQ3ZDLENBQUMsQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLE9BQU8sRUFBRTtvQkFDaEMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtnQkFDTCxNQUFNLFdBQVcsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFBO2dCQUU5QiwrREFBK0Q7Z0JBQy9ELDREQUE0RDtnQkFDNUQsc0RBQXNEO2dCQUN0RCxJQUFJLFVBQVUsR0FBRyxDQUFDLElBQUksWUFBWSxHQUFHLENBQUMsRUFBRTtvQkFDdEMsTUFBTSxXQUFXLEdBQUcsV0FBVyxDQUFDLE9BQU8sRUFBRSxHQUFHLFlBQVksQ0FBQTtvQkFDeEQsSUFBSSxXQUFXLEdBQUcsVUFBVSxFQUFFO3dCQUM1QixLQUFLLENBQUMseUJBQXlCLEdBQUcsRUFBRSxDQUFDLENBQUE7d0JBQ3JDLE9BQU8sUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFBO3FCQUN0QjtvQkFDRCxZQUFZLENBQUMsWUFBWSxHQUFHLFdBQVcsQ0FBQTtpQkFDeEM7Z0JBRUQsSUFBSSxNQUFBLE9BQU8sYUFBUCxPQUFPLHVCQUFQLE9BQU8sQ0FBRSxNQUFNLDBDQUFFLE9BQU8sRUFBRTtvQkFDNUIsWUFBWSxDQUFDLE9BQU8sR0FBRyxJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFBO2lCQUN4RDtxQkFBTTtvQkFDTCxZQUFZLENBQUMsT0FBTyxHQUFHLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsR0FBRyxJQUFJLENBQUMsQ0FBQTtpQkFDdEU7Z0JBQ0QsTUFBTSxVQUFVLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFBO2dCQUN6QyxNQUFNLE9BQU8sR0FBRyxNQUFNLFVBQVUsQ0FBQyxTQUFTLENBQ3hDLEVBQUUsR0FBRyxFQUFFLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsRUFBRSxFQUNuQyxFQUFFLElBQUksRUFBRSxZQUFZLEVBQUUsRUFDdEIsRUFBRSxZQUFZLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUNyRCxDQUFBO2dCQUNELElBQUksT0FBTyxDQUFDLFlBQVksS0FBSyxDQUFDLEVBQUU7b0JBQzlCLE9BQU8sUUFBUSxDQUFDLElBQUksS0FBSyxDQUFDLHFDQUFxQyxDQUFDLENBQUMsQ0FBQTtpQkFDbEU7cUJBQU07b0JBQ0wsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsR0FBRyxFQUFFLE9BQU8sQ0FBQyxDQUFBO29CQUNoQyxPQUFPLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQTtpQkFDdEI7YUFDRjtZQUFDLE9BQU8sS0FBSyxFQUFFO2dCQUNkLE9BQU8sUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFBO2FBQ3ZCO1FBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtJQUNOLENBQUM7SUFFRDs7T0FFRztJQUNILEdBQUcsQ0FDRCxRQU1TO1FBRVQsQ0FBQztRQUFBLENBQUMsS0FBSyxJQUFJLEVBQUU7WUFDWCxJQUFJO2dCQUNGLEtBQUssQ0FBQyxrQkFBa0IsQ0FBQyxDQUFBO2dCQUN6QixNQUFNLFVBQVUsR0FBRyxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUE7Z0JBQ3pDLE1BQU0sUUFBUSxHQUFHLFVBQVUsQ0FBQyxJQUFJLENBQUM7b0JBQy9CLEdBQUcsRUFBRTt3QkFDSCxFQUFFLE9BQU8sRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsRUFBRTt3QkFDL0IsRUFBRSxPQUFPLEVBQUUsRUFBRSxHQUFHLEVBQUUsSUFBSSxJQUFJLEVBQUUsRUFBRSxFQUFFO3FCQUNqQztpQkFDRixDQUFDLENBQUE7Z0JBQ0YsTUFBTSxPQUFPLEdBQTBCLEVBQUUsQ0FBQTtnQkFDekMsSUFBSSxLQUFLLEVBQUUsTUFBTSxPQUFPLElBQUksUUFBUSxFQUFFO29CQUNwQyxJQUFJLElBQUksQ0FBQyxNQUFNLElBQUksT0FBTyxFQUFFO3dCQUMxQixNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsT0FBeUMsQ0FBQyxDQUFBO3FCQUNyRTtvQkFDRCxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxXQUFXLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUE7aUJBQ25FO2dCQUNELElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFBO2dCQUN6QixRQUFRLENBQUMsSUFBSSxFQUFFLE9BQU8sQ0FBQyxDQUFBO2FBQ3hCO1lBQUMsT0FBTyxLQUFLLEVBQUU7Z0JBQ2QsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFBO2FBQ2hCO1FBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtJQUNOLENBQUM7SUFFRDs7O09BR0c7SUFDSCxPQUFPLENBQUMsR0FBVyxFQUFFLFdBQStCLElBQUk7UUFDdEQsS0FBSyxDQUFDLHNCQUFzQixHQUFHLEVBQUUsQ0FBQyxDQUFBO1FBQ2xDLElBQUksQ0FBQyxXQUFXO2FBQ2IsSUFBSSxDQUFDLENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FDbkIsVUFBVSxDQUFDLFNBQVMsQ0FDbEIsRUFBRSxHQUFHLEVBQUUsSUFBSSxDQUFDLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxFQUFFLEVBQ25DLEVBQUUsWUFBWSxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMscUJBQXFCLEVBQUUsQ0FDckQsQ0FDRjthQUNBLElBQUksQ0FBQyxHQUFHLEVBQUU7WUFDVCxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxHQUFHLENBQUMsQ0FBQTtZQUN6QixRQUFRLENBQUMsSUFBSSxDQUFDLENBQUE7UUFDaEIsQ0FBQyxDQUFDO2FBQ0QsS0FBSyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQTtJQUNsQyxDQUFDO0lBRUQ7O09BRUc7SUFDSCxNQUFNLENBQUMsUUFBNEM7UUFDakQsS0FBSyxDQUFDLHFCQUFxQixDQUFDLENBQUE7UUFDNUIsSUFBSSxDQUFDLFdBQVc7YUFDYixJQUFJLENBQUMsQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLFVBQVUsQ0FBQyxjQUFjLEVBQUUsQ0FBQzthQUNqRCxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUM7WUFDL0IsYUFBYTthQUNaLEtBQUssQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUE7SUFDbEMsQ0FBQztJQUVEOztPQUVHO0lBQ0gsS0FBSyxDQUFDLFdBQStCLElBQUk7UUFDdkMsS0FBSyxDQUFDLG9CQUFvQixDQUFDLENBQUE7UUFDM0IsSUFBSSxDQUFDLFdBQVc7YUFDYixJQUFJLENBQUMsQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsQ0FBQzthQUN2QyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO2FBQzFCLEtBQUssQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUE7SUFDbEMsQ0FBQztJQUVEOztPQUVHO0lBQ0gsS0FBSztRQUNILEtBQUssQ0FBQyxvQkFBb0IsQ0FBQyxDQUFBO1FBQzNCLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFBO0lBQzVDLENBQUM7Q0FDRjtBQW5hRCw2QkFtYUMifQ==
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 92c10975f..1b1db5809 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -1,6 +1,6 @@
 import { exec } from 'child_process';
 import { cyan, green, red, yellow } from 'colors';
-import { log_execution } from '../ActionUtilities';
+import { logExecution } from '../ActionUtilities';
 import { Method } from '../RouteManager';
 import RouteSubscriber from '../RouteSubscriber';
 import { Search } from '../Search';
@@ -66,13 +66,13 @@ export namespace SolrManager {
 
     export async function update() {
         console.log(green('Beginning update...'));
-        await log_execution<void>({
+        await logExecution<void>({
             startMessage: 'Clearing existing Solr information...',
             endMessage: 'Solr information successfully cleared',
             action: Search.clear,
             color: cyan,
         });
-        const cursor = await log_execution({
+        const cursor = await logExecution({
             startMessage: 'Connecting to and querying for all documents from database...',
             endMessage: ({ result, error }) => {
                 const success = error === null && result !== undefined;
@@ -118,7 +118,7 @@ export namespace SolrManager {
             }
         }
         await cursor?.forEach(updateDoc);
-        const result = await log_execution({
+        const result = await logExecution({
             startMessage: `Dispatching updates for ${updates.length} documents`,
             endMessage: 'Dispatched updates complete',
             action: () => Search.updateDocuments(updates),
diff --git a/src/server/ApiManagers/SessionManager.ts b/src/server/ApiManagers/SessionManager.ts
index e37f8c6db..c3139896f 100644
--- a/src/server/ApiManagers/SessionManager.ts
+++ b/src/server/ApiManagers/SessionManager.ts
@@ -1,67 +1,66 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method, _permission_denied, AuthorizedCore, SecureHandler } from "../RouteManager";
-import RouteSubscriber from "../RouteSubscriber";
-import { sessionAgent } from "..";
-import { DashSessionAgent } from "../DashSession/DashSessionAgent";
+import ApiManager, { Registration } from './ApiManager';
+import { Method, _permissionDenied, AuthorizedCore, SecureHandler } from '../RouteManager';
+import RouteSubscriber from '../RouteSubscriber';
+import { sessionAgent } from '..';
+import { DashSessionAgent } from '../DashSession/DashSessionAgent';
 
-const permissionError = "You are not authorized!";
+const permissionError = 'You are not authorized!';
 
 export default class SessionManager extends ApiManager {
-
-    private secureSubscriber = (root: string, ...params: string[]) => new RouteSubscriber(root).add("session_key", ...params);
+    private secureSubscriber = (root: string, ...params: string[]) => new RouteSubscriber(root).add('session_key', ...params);
 
     private authorizedAction = (handler: SecureHandler) => {
         return (core: AuthorizedCore) => {
-            const { req: { params }, res } = core;
+            const {
+                req: { params },
+                res,
+            } = core;
             if (!process.env.MONITORED) {
-                return res.send("This command only makes sense in the context of a monitored session.");
+                return res.send('This command only makes sense in the context of a monitored session.');
             }
             if (params.session_key !== process.env.session_key) {
-                return _permission_denied(res, permissionError);
+                return _permissionDenied(res, permissionError);
             }
             return handler(core);
         };
-    }
+    };
 
     protected initialize(register: Registration): void {
-
         register({
             method: Method.GET,
-            subscription: this.secureSubscriber("debug", "to?"),
+            subscription: this.secureSubscriber('debug', 'to?'),
             secureHandler: this.authorizedAction(async ({ req: { params }, res }) => {
                 const to = params.to || DashSessionAgent.notificationRecipient;
-                const { error } = await sessionAgent.serverWorker.emit("debug", { to });
+                const { error } = await sessionAgent.serverWorker.emit('debug', { to });
                 res.send(error ? error.message : `Your request was successful: the server captured and compressed (but did not save) a new back up. It was sent to ${to}.`);
-            })
+            }),
         });
 
         register({
             method: Method.GET,
-            subscription: this.secureSubscriber("backup"),
+            subscription: this.secureSubscriber('backup'),
             secureHandler: this.authorizedAction(async ({ res }) => {
-                const { error } = await sessionAgent.serverWorker.emit("backup");
-                res.send(error ? error.message : "Your request was successful: the server successfully created a new back up.");
-            })
+                const { error } = await sessionAgent.serverWorker.emit('backup');
+                res.send(error ? error.message : 'Your request was successful: the server successfully created a new back up.');
+            }),
         });
 
         register({
             method: Method.GET,
-            subscription: this.secureSubscriber("kill"),
+            subscription: this.secureSubscriber('kill'),
             secureHandler: this.authorizedAction(({ res }) => {
-                res.send("Your request was successful: the server and its session have been killed.");
-                sessionAgent.killSession("an authorized user has manually ended the server session via the /kill route");
-            })
+                res.send('Your request was successful: the server and its session have been killed.');
+                sessionAgent.killSession('an authorized user has manually ended the server session via the /kill route');
+            }),
         });
 
         register({
             method: Method.GET,
-            subscription: this.secureSubscriber("deleteSession"),
+            subscription: this.secureSubscriber('deleteSession'),
             secureHandler: this.authorizedAction(async ({ res }) => {
-                const { error } = await sessionAgent.serverWorker.emit("delete");
-                res.send(error ? error.message : "Your request was successful: the server successfully deleted the database. Return to /home.");
-            })
+                const { error } = await sessionAgent.serverWorker.emit('delete');
+                res.send(error ? error.message : 'Your request was successful: the server successfully deleted the database. Return to /home.');
+            }),
         });
-
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 2306b6589..1a759f04d 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -1,50 +1,27 @@
+import * as AdmZip from 'adm-zip';
 import * as formidable from 'formidable';
-import { createReadStream, createWriteStream, unlink, writeFile } from 'fs';
-import * as path from 'path';
+import * as fs from 'fs';
+import { createReadStream, createWriteStream, unlink } from 'fs';
+import * as imageDataUri from 'image-data-uri';
 import Jimp from 'jimp';
-import { filesDirectory, publicDirectory } from '..';
+import * as path from 'path';
+import * as uuid from 'uuid';
 import { retrocycle } from '../../decycler/decycler';
+import { DashVersion } from '../../fields/DocSymbols';
 import { DashUploadUtils, InjectSize, SizeSuffix } from '../DashUploadUtils';
-import { Database } from '../database';
 import { Method, _success } from '../RouteManager';
-import RouteSubscriber from '../RouteSubscriber';
 import { AcceptableMedia, Upload } from '../SharedMediaTypes';
+import { clientPathToFile, Directory, pathToDirectory, publicDirectory, serverPathToFile } from '../SocketData';
+import { Database } from '../database';
 import ApiManager, { Registration } from './ApiManager';
 import { SolrManager } from './SearchManager';
-import * as uuid from 'uuid';
-import { DashVersion } from '../../fields/DocSymbols';
-import * as AdmZip from 'adm-zip';
-import * as imageDataUri from 'image-data-uri';
-import * as fs from 'fs';
-
-export enum Directory {
-    parsed_files = 'parsed_files',
-    images = 'images',
-    videos = 'videos',
-    pdfs = 'pdfs',
-    text = 'text',
-    audio = 'audio',
-    csv = 'csv',
-}
-
-export function serverPathToFile(directory: Directory, filename: string) {
-    return path.normalize(`${filesDirectory}/${directory}/${filename}`);
-}
-
-export function pathToDirectory(directory: Directory) {
-    return path.normalize(`${filesDirectory}/${directory}`);
-}
-
-export function clientPathToFile(directory: Directory, filename: string) {
-    return `/files/${directory}/${filename}`;
-}
 
 export default class UploadManager extends ApiManager {
     protected initialize(register: Registration): void {
         register({
             method: Method.POST,
             subscription: '/ping',
-            secureHandler: async ({ req, res }) => {
+            secureHandler: async ({ /* req, */ res }) => {
                 _success(res, { message: DashVersion, date: new Date() });
             },
         });
@@ -78,31 +55,33 @@ export default class UploadManager extends ApiManager {
                 form.on('progress', e => fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `read:(${Math.round((100 * +e) / +filesize)}%) ${e} of ${filesize}`)));
                 return new Promise<void>(resolve => {
                     form.parse(req, async (_err, _fields, files) => {
-                        const results: Upload.FileResponse[] = [];
                         if (_err?.message) {
-                            results.push({
-                                source: {
-                                    filepath: '',
-                                    originalFilename: 'none',
-                                    newFilename: 'none',
-                                    mimetype: 'text',
-                                    size: 0,
-                                    hashAlgorithm: 'md5',
-                                    toJSON: () => ({ name: 'none', size: 0, length: 0, mtime: new Date(), filepath: '', originalFilename: 'none', newFilename: 'none', mimetype: 'text' }),
+                            _success(res, [
+                                {
+                                    source: {
+                                        filepath: '',
+                                        originalFilename: 'none',
+                                        newFilename: 'none',
+                                        mimetype: 'text',
+                                        size: 0,
+                                        hashAlgorithm: 'md5',
+                                        toJSON: () => ({ name: 'none', size: 0, length: 0, mtime: new Date(), filepath: '', originalFilename: 'none', newFilename: 'none', mimetype: 'text' }),
+                                    },
+                                    result: { name: 'failed upload', message: `${_err.message}` },
                                 },
-                                result: { name: 'failed upload', message: `${_err.message}` },
-                            });
-                        }
-                        fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `resampling images`));
+                            ]);
+                        } else {
+                            fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `resampling images`));
+                            const results = (
+                                await Promise.all(
+                                    Array.from(Object.keys(files)).map(
+                                        async key => (!files[key] ? undefined : DashUploadUtils.upload(files[key]![0] /* , key */)) // key is the guid used by the client to track upload progress.
+                                    )
+                                )
+                            ).filter(result => result && !(result.result instanceof Error));
 
-                        for (const key in files) {
-                            const f = files[key];
-                            if (f) {
-                                const result = await DashUploadUtils.upload(f[0], key); // key is the guid used by the client to track upload progress.
-                                result && !(result.result instanceof Error) && results.push(result);
-                            }
+                            _success(res, results);
                         }
-                        _success(res, results);
                         resolve();
                     });
                 });
@@ -113,17 +92,14 @@ export default class UploadManager extends ApiManager {
             method: Method.POST,
             subscription: '/uploadYoutubeVideo',
             secureHandler: async ({ req, res }) => {
-                //req.readableBuffer.head.data
-                return new Promise<void>(async resolve => {
-                    req.addListener('data', async args => {
-                        const payload = String.fromCharCode.apply(String, args);
-                        const { videoId, overwriteId } = JSON.parse(payload);
-                        const results: Upload.FileResponse[] = [];
-                        const result = await DashUploadUtils.uploadYoutube(videoId, overwriteId ?? videoId);
-                        result && results.push(result);
-                        _success(res, results);
-                        resolve();
-                    });
+                // req.readableBuffer.head.data
+                req.addListener('data', async args => {
+                    const payload = String.fromCharCode(...args); // .apply(String, args);
+                    const { videoId, overwriteId } = JSON.parse(payload);
+                    const results: Upload.FileResponse[] = [];
+                    const result = await DashUploadUtils.uploadYoutube(videoId, overwriteId ?? videoId);
+                    result && results.push(result);
+                    _success(res, results);
                 });
             },
         });
@@ -132,49 +108,10 @@ export default class UploadManager extends ApiManager {
             method: Method.POST,
             subscription: '/queryYoutubeProgress',
             secureHandler: async ({ req, res }) => {
-                return new Promise<void>(async resolve => {
-                    req.addListener('data', args => {
-                        const payload = String.fromCharCode.apply(String, args);
-                        const videoId = JSON.parse(payload).videoId;
-                        _success(res, { progress: DashUploadUtils.QueryYoutubeProgress(videoId, req.user) });
-                        resolve();
-                    });
-                });
-            },
-        });
-
-        register({
-            method: Method.POST,
-            subscription: new RouteSubscriber('youtubeScreenshot'),
-            secureHandler: async ({ req, res }) => {
-                const { id, timecode } = req.body;
-                const convert = (raw: string) => {
-                    const number = Math.floor(Number(raw));
-                    const seconds = number % 60;
-                    const minutes = (number - seconds) / 60;
-                    return `${minutes}m${seconds}s`;
-                };
-                const suffix = timecode ? `&t=${convert(timecode)}` : ``;
-                const targetUrl = `https://www.youtube.com/watch?v=${id}${suffix}`;
-                const buffer = await captureYoutubeScreenshot(targetUrl);
-                if (!buffer) {
-                    return res.send();
-                }
-                const resolvedName = `youtube_capture_${id}_${suffix}.png`;
-                const resolvedPath = serverPathToFile(Directory.images, resolvedName);
-                return new Promise<void>(resolve => {
-                    writeFile(resolvedPath, buffer, async error => {
-                        if (error) {
-                            return res.send();
-                        }
-                        await DashUploadUtils.outputResizedImages(resolvedPath, resolvedName, pathToDirectory(Directory.images));
-                        res.send({
-                            accessPaths: {
-                                agnostic: DashUploadUtils.getAccessPaths(Directory.images, resolvedName),
-                            },
-                        } as Upload.FileInformation);
-                        resolve();
-                    });
+                req.addListener('data', args => {
+                    const payload = String.fromCharCode(...args); // .apply(String, args);
+                    const { videoId } = JSON.parse(payload);
+                    _success(res, { progress: DashUploadUtils.QueryYoutubeProgress(videoId) });
                 });
             },
         });
@@ -186,7 +123,8 @@ export default class UploadManager extends ApiManager {
                 const { sources } = req.body;
                 if (Array.isArray(sources)) {
                     const results = await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source)));
-                    return res.send(results);
+                    res.send(results);
+                    return;
                 }
                 res.send();
             },
@@ -203,20 +141,22 @@ export default class UploadManager extends ApiManager {
                 const getId = (id: string): string => {
                     if (!remap || id.endsWith('Proto')) return id;
                     if (id in ids) return ids[id];
-                    return (ids[id] = uuid.v4());
+                    ids[id] = uuid.v4();
+                    return ids[id];
                 };
-                const mapFn = (doc: any) => {
+                const mapFn = (docIn: any) => {
+                    const doc = docIn;
                     if (doc.id) {
                         doc.id = getId(doc.id);
                     }
+                    // eslint-disable-next-line no-restricted-syntax
                     for (const key in doc.fields) {
-                        if (!doc.fields.hasOwnProperty(key)) {
-                            continue;
-                        }
+                        // eslint-disable-next-line no-continue
+                        if (!Object.prototype.hasOwnProperty.call(doc.fields, key)) continue;
+
                         const field = doc.fields[key];
-                        if (field === undefined || field === null) {
-                            continue;
-                        }
+                        // eslint-disable-next-line no-continue
+                        if (field === undefined || field === null) continue;
 
                         if (field.__type === 'Doc') {
                             mapFn(field);
@@ -229,78 +169,80 @@ export default class UploadManager extends ApiManager {
                         } else if (field.__type === 'list') {
                             mapFn(field);
                         } else if (typeof field === 'string') {
-                            const re = /("(?:dataD|d)ocumentId"\s*:\s*")([\w\-]*)"/g;
-                            doc.fields[key] = (field as any).replace(re, (match: any, p1: string, p2: string) => {
-                                return `${p1}${getId(p2)}"`;
-                            });
+                            const re = /("(?:dataD|d)ocumentId"\s*:\s*")([\w-]*)"/g;
+                            doc.fields[key] = (field as any).replace(re, (match: any, p1: string, p2: string) => `${p1}${getId(p2)}"`);
                         } else if (field.__type === 'RichTextField') {
                             const re = /("href"\s*:\s*")(.*?)"/g;
-                            field.Data = field.Data.replace(re, (match: any, p1: string, p2: string) => {
-                                return `${p1}${getId(p2)}"`;
-                            });
+                            field.Data = field.Data.replace(re, (match: any, p1: string, p2: string) => `${p1}${getId(p2)}"`);
                         }
                     }
                 };
                 return new Promise<void>(resolve => {
                     form.parse(req, async (_err, fields, files) => {
-                        remap = Object.keys(fields).some(key => key === 'remap' && !fields.remap?.includes('false')); //.remap !== 'false'; // bcz: looking to see if the field 'remap' is set to 'false'
+                        remap = Object.keys(fields).some(key => key === 'remap' && !fields.remap?.includes('false')); // .remap !== 'false'; // bcz: looking to see if the field 'remap' is set to 'false'
                         let id: string = '';
                         let docids: string[] = [];
                         let linkids: string[] = [];
                         try {
-                            for (const name in files) {
-                                const f = files[name];
-                                if (!f) continue;
-                                const path_2 = f[0]; // what about the rest of the array? are we guaranteed only one value is set?
-                                const zip = new AdmZip(path_2.filepath);
-                                zip.getEntries().forEach((entry: any) => {
-                                    let entryName = entry.entryName.replace(/%%%/g, '/');
-                                    if (!entryName.startsWith('files/')) {
-                                        return;
-                                    }
-                                    const extension = path.extname(entryName);
-                                    const pathname = publicDirectory + '/' + entry.entryName;
-                                    const targetname = publicDirectory + '/' + entryName;
-                                    try {
-                                        zip.extractEntryTo(entry.entryName, publicDirectory, true, false);
-                                        createReadStream(pathname).pipe(createWriteStream(targetname));
-                                        Jimp.read(pathname).then(img => {
-                                            DashUploadUtils.imageResampleSizes(extension).forEach(({ width, suffix }) => {
-                                                const outputPath = InjectSize(targetname, suffix);
-                                                if (!width) createReadStream(pathname).pipe(createWriteStream(outputPath));
-                                                else img = img.resize(width, Jimp.AUTO).write(outputPath);
+                            // eslint-disable-next-line no-restricted-syntax
+                            for (const name in Object.keys(files)) {
+                                if (Object.prototype.hasOwnProperty.call(files, name)) {
+                                    const f = files[name];
+                                    // eslint-disable-next-line no-continue
+                                    if (!f) continue;
+                                    const path2 = f[0]; // what about the rest of the array? are we guaranteed only one value is set?
+                                    const zip = new AdmZip(path2.filepath);
+                                    zip.getEntries().forEach((entry: any) => {
+                                        const entryName = entry.entryName.replace(/%%%/g, '/');
+                                        if (!entryName.startsWith('files/')) {
+                                            return;
+                                        }
+                                        const extension = path.extname(entryName);
+                                        const pathname = publicDirectory + '/' + entry.entryName;
+                                        const targetname = publicDirectory + '/' + entryName;
+                                        try {
+                                            zip.extractEntryTo(entry.entryName, publicDirectory, true, false);
+                                            createReadStream(pathname).pipe(createWriteStream(targetname));
+                                            Jimp.read(pathname).then(imgIn => {
+                                                let img = imgIn;
+                                                DashUploadUtils.imageResampleSizes(extension).forEach(({ width, suffix }) => {
+                                                    const outputPath = InjectSize(targetname, suffix);
+                                                    if (!width) createReadStream(pathname).pipe(createWriteStream(outputPath));
+                                                    else img = img.resize(width, Jimp.AUTO).write(outputPath);
+                                                });
+                                                unlink(pathname, () => {});
                                             });
-                                            unlink(pathname, () => {});
-                                        });
-                                    } catch (e) {
-                                        console.log(e);
-                                    }
-                                });
-                                const json = zip.getEntry('docs.json');
-                                if (json) {
-                                    try {
-                                        const data = JSON.parse(json.getData().toString('utf8'), retrocycle());
-                                        const { docs, links } = data;
-                                        id = getId(data.id);
-                                        const rdocs = Object.keys(docs).map(key => docs[key]);
-                                        const ldocs = Object.keys(links).map(key => links[key]);
-                                        [...rdocs, ...ldocs].forEach(mapFn);
-                                        docids = rdocs.map(doc => doc.id);
-                                        linkids = ldocs.map(link => link.id);
-                                        await Promise.all(
-                                            [...rdocs, ...ldocs].map(
-                                                doc =>
-                                                    new Promise<void>(res => {
-                                                        // overwrite mongo doc with json doc contents
-                                                        Database.Instance.replace(doc.id, doc, (err, r) => res(err && console.log(err)), true);
-                                                    })
-                                            )
-                                        );
-                                    } catch (e) {
-                                        console.log(e);
+                                        } catch (e) {
+                                            console.log(e);
+                                        }
+                                    });
+                                    const json = zip.getEntry('docs.json');
+                                    if (json) {
+                                        try {
+                                            const data = JSON.parse(json.getData().toString('utf8'), retrocycle());
+                                            const { docs, links } = data;
+                                            id = getId(data.id);
+                                            const rdocs = Object.keys(docs).map(key => docs[key]);
+                                            const ldocs = Object.keys(links).map(key => links[key]);
+                                            [...rdocs, ...ldocs].forEach(mapFn);
+                                            docids = rdocs.map(doc => doc.id);
+                                            linkids = ldocs.map(link => link.id);
+                                            // eslint-disable-next-line no-await-in-loop
+                                            await Promise.all(
+                                                [...rdocs, ...ldocs].map(
+                                                    doc =>
+                                                        new Promise<void>(res => {
+                                                            // overwrite mongo doc with json doc contents
+                                                            Database.Instance.replace(doc.id, doc, err => res(err && console.log(err)), true);
+                                                        })
+                                                )
+                                            );
+                                        } catch (e) {
+                                            console.log(e);
+                                        }
                                     }
+                                    unlink(path2.filepath, () => {});
                                 }
-                                unlink(path_2.filepath, () => {});
                             }
                             SolrManager.update();
                             res.send(JSON.stringify({ id, docids, linkids } || 'error'));
@@ -319,9 +261,8 @@ export default class UploadManager extends ApiManager {
             secureHandler: async ({ req, res }) => {
                 const { source } = req.body;
                 if (typeof source === 'string') {
-                    return res.send(await DashUploadUtils.InspectImage(source));
-                }
-                res.send({});
+                    res.send(await DashUploadUtils.InspectImage(source));
+                } else res.send({});
             },
         });
 
@@ -329,7 +270,7 @@ export default class UploadManager extends ApiManager {
             method: Method.POST,
             subscription: '/uploadURI',
             secureHandler: ({ req, res }) => {
-                const uri: any = req.body.uri;
+                const { uri } = req.body;
                 const filename = req.body.name;
                 const origSuffix = req.body.nosuffix ? SizeSuffix.None : SizeSuffix.Original;
                 const deleteFiles = req.body.replaceRootFilename;
@@ -338,23 +279,24 @@ export default class UploadManager extends ApiManager {
                     return;
                 }
                 if (deleteFiles) {
-                    const path = serverPathToFile(Directory.images, '');
+                    const serverPath = serverPathToFile(Directory.images, '');
                     const regex = new RegExp(`${deleteFiles}.*`);
-                    fs.readdirSync(path)
+                    fs.readdirSync(serverPath)
                         .filter((f: any) => regex.test(f))
-                        .map((f: any) => fs.unlinkSync(path + f));
+                        .map((f: any) => fs.unlinkSync(serverPath + f));
                 }
-                return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
+                imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
                     const ext = path.extname(savedName).toLowerCase();
                     if (AcceptableMedia.imageFormats.includes(ext)) {
-                        Jimp.read(savedName).then(img =>
+                        Jimp.read(savedName).then(imgIn => {
+                            let img = imgIn;
                             (!origSuffix ? [{ width: 400, suffix: SizeSuffix.Medium }] : Object.values(DashUploadUtils.Sizes)) //
                                 .forEach(({ width, suffix }) => {
                                     const outputPath = serverPathToFile(Directory.images, InjectSize(filename, suffix) + ext);
                                     if (!width) createReadStream(savedName).pipe(createWriteStream(outputPath));
                                     else img = img.resize(width, Jimp.AUTO).write(outputPath);
-                                })
-                        );
+                                });
+                        });
                     }
                     res.send(clientPathToFile(Directory.images, filename + ext));
                 });
@@ -362,35 +304,3 @@ export default class UploadManager extends ApiManager {
         });
     }
 }
-function delay(ms: number) {
-    return new Promise(resolve => setTimeout(resolve, ms));
-}
-/**
- * On success, returns a buffer containing the bytes of a screenshot
- * of the video (optionally, at a timecode) specified by @param targetUrl.
- *
- * On failure, returns undefined.
- */
-async function captureYoutubeScreenshot(targetUrl: string) {
-    // const browser = await launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
-    // const page = await browser.newPage();
-    // // await page.setViewport({ width: 1920, height: 1080 });
-
-    // // await page.goto(targetUrl, { waitUntil: 'domcontentloaded' as any });
-
-    // const videoPlayer = await page.$('.html5-video-player');
-    // videoPlayer && await page.focus("video");
-    // await delay(7000);
-    // const ad = await page.$('.ytp-ad-skip-button-text');
-    // await ad?.click();
-    // await videoPlayer?.click();
-    // await delay(1000);
-    // // hide youtube player controls.
-    // await page.evaluate(() => (document.querySelector('.ytp-chrome-bottom') as HTMLElement).style.display = 'none');
-
-    // const buffer = await videoPlayer?.screenshot({ encoding: "binary" });
-    // await browser.close();
-
-    // return buffer;
-    return null;
-}
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index 0431b9bcf..b587340e2 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -1,16 +1,14 @@
-import ApiManager, { Registration } from './ApiManager';
-import { Method } from '../RouteManager';
-import { Database } from '../database';
-import { msToTime } from '../ActionUtilities';
 import * as bcrypt from 'bcrypt-nodejs';
+import { check, validationResult } from 'express-validator';
+import { Utils } from '../../Utils';
 import { Opt } from '../../fields/Doc';
-import { WebSocket } from '../websocket';
-import { resolvedPorts } from '../server_Initialization';
 import { DashVersion } from '../../fields/DocSymbols';
-import { Utils } from '../../Utils';
-import { check, validationResult } from 'express-validator';
+import { msToTime } from '../ActionUtilities';
+import { Method } from '../RouteManager';
+import { resolvedPorts, socketMap, timeMap } from '../SocketData';
+import { Database } from '../database';
+import ApiManager, { Registration } from './ApiManager';
 
-export const timeMap: { [id: string]: number } = {};
 interface ActivityUnit {
     user: string;
     duration: number;
@@ -32,9 +30,10 @@ export default class UserManager extends ApiManager {
             method: Method.POST,
             subscription: '/setCacheDocumentIds',
             secureHandler: async ({ user, req, res }) => {
+                const userModel = user;
                 const result: any = {};
-                user.cacheDocumentIds = req.body.cacheDocumentIds;
-                user.save().then(undefined, err => {
+                userModel.cacheDocumentIds = req.body.cacheDocumentIds;
+                userModel.save().then(undefined, (err: any) => {
                     if (err) {
                         result.error = [{ msg: 'Error while caching documents' }];
                     }
@@ -90,17 +89,19 @@ export default class UserManager extends ApiManager {
             method: Method.POST,
             subscription: '/internalResetPassword',
             secureHandler: async ({ user, req, res }) => {
+                const userModel = user;
                 const result: any = {};
-                const { curr_pass, new_pass, new_confirm } = req.body;
+                // eslint-disable-next-line camelcase
+                const { curr_pass, new_pass } = req.body;
                 // perhaps should assert whether curr password is entered correctly
                 const validated = await new Promise<Opt<boolean>>(resolve => {
-                    bcrypt.compare(curr_pass, user.password, (err, passwords_match) => {
-                        if (err || !passwords_match) {
+                    bcrypt.compare(curr_pass, userModel.password, (err, passwordsMatch) => {
+                        if (err || !passwordsMatch) {
                             result.error = [{ msg: 'Incorrect current password' }];
                             res.send(result);
                             resolve(undefined);
                         } else {
-                            resolve(passwords_match);
+                            resolve(passwordsMatch);
                         }
                     });
                 });
@@ -111,10 +112,11 @@ export default class UserManager extends ApiManager {
 
                 check('new_pass', 'Password must be at least 4 characters long')
                     .run(req)
-                    .then(chcekcres => console.log(chcekcres)); //.len({ min: 4 });
+                    .then(chcekcres => console.log(chcekcres)); // .len({ min: 4 });
                 check('new_confirm', 'Passwords do not match')
                     .run(req)
-                    .then(theres => console.log(theres)); //.equals(new_pass);
+                    .then(theres => console.log(theres)); // .equals(new_pass);
+                // eslint-disable-next-line camelcase
                 if (curr_pass === new_pass) {
                     result.error = [{ msg: 'Current and new password are the same' }];
                 }
@@ -125,12 +127,13 @@ export default class UserManager extends ApiManager {
 
                 // will only change password if there are no errors.
                 if (!result.error) {
-                    user.password = new_pass;
-                    user.passwordResetToken = undefined;
-                    user.passwordResetExpires = undefined;
+                    // eslint-disable-next-line camelcase
+                    userModel.password = new_pass;
+                    userModel.passwordResetToken = undefined;
+                    userModel.passwordResetExpires = undefined;
                 }
 
-                user.save().then(undefined, err => {
+                userModel.save().then(undefined, err => {
                     if (err) {
                         result.error = [{ msg: 'Error while saving new password' }];
                     }
@@ -149,13 +152,16 @@ export default class UserManager extends ApiManager {
                 const activeTimes: ActivityUnit[] = [];
                 const inactiveTimes: ActivityUnit[] = [];
 
+                // eslint-disable-next-line no-restricted-syntax
                 for (const user in timeMap) {
-                    const time = timeMap[user];
-                    const socketPair = Array.from(WebSocket.socketMap).find(pair => pair[1] === user);
-                    if (socketPair && !socketPair[0].disconnected) {
-                        const duration = now - time;
-                        const target = duration / 1000 < 60 * 5 ? activeTimes : inactiveTimes;
-                        target.push({ user, duration });
+                    if (Object.prototype.hasOwnProperty.call(timeMap, user)) {
+                        const time = timeMap[user];
+                        const socketPair = Array.from(socketMap).find(pair => pair[1] === user);
+                        if (socketPair && !socketPair[0].disconnected) {
+                            const duration = now - time;
+                            const target = duration / 1000 < 60 * 5 ? activeTimes : inactiveTimes;
+                            target.push({ user, duration });
+                        }
                     }
                 }
 
diff --git a/src/server/Client.ts b/src/server/Client.ts
index e6f953712..f67999c5b 100644
--- a/src/server/Client.ts
+++ b/src/server/Client.ts
@@ -1,4 +1,4 @@
-import { computed } from "mobx";
+import { computed } from 'mobx';
 
 export class Client {
     private _guid: string;
@@ -7,5 +7,7 @@ export class Client {
         this._guid = guid;
     }
 
-    @computed public get GUID(): string { return this._guid; }
-}
\ No newline at end of file
+    @computed public get GUID(): string {
+        return this._guid;
+    }
+}
diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts
index 1ef7a131d..f937c17ad 100644
--- a/src/server/DashSession/DashSessionAgent.ts
+++ b/src/server/DashSession/DashSessionAgent.ts
@@ -1,18 +1,19 @@
-import { Email, pathFromRoot } from '../ActionUtilities';
-import { red, yellow, green, cyan } from 'colors';
-import { get } from 'request-promise';
-import { Utils } from '../../Utils';
-import { WebSocket } from '../websocket';
-import { MessageStore } from '../Message';
-import { launchServer, onWindows } from '..';
-import { readdirSync, statSync, createWriteStream, readFileSync, unlinkSync } from 'fs';
 import * as Archiver from 'archiver';
+import { cyan, green, red, yellow } from 'colors';
+import { createWriteStream, readFileSync, readdirSync, statSync, unlinkSync } from 'fs';
 import { resolve } from 'path';
+import { get } from 'request-promise';
 import { rimraf } from 'rimraf';
+import { launchServer, onWindows } from '..';
+import { Utils } from '../../Utils';
+import { ServerUtils } from '../../ServerUtils';
+import { Email, pathFromRoot } from '../ActionUtilities';
+import { MessageStore } from '../Message';
+import { WebSocket } from '../websocket';
 import { AppliedSessionAgent, ExitHandler } from './Session/agents/applied_session_agent';
-import { ServerWorker } from './Session/agents/server_worker';
 import { Monitor } from './Session/agents/monitor';
-import { MessageHandler, ErrorLike } from './Session/agents/promisified_ipc_manager';
+import { ErrorLike, MessageHandler } from './Session/agents/promisified_ipc_manager';
+import { ServerWorker } from './Session/agents/server_worker';
 
 /**
  * If we're the monitor (master) thread, we should launch the monitor logic for the session.
@@ -22,6 +23,7 @@ import { MessageHandler, ErrorLike } from './Session/agents/promisified_ipc_mana
 export class DashSessionAgent extends AppliedSessionAgent {
     private readonly signature = '-Dash Server Session Manager';
     private readonly releaseDesktop = pathFromRoot('../../Desktop');
+    public static notificationRecipient = 'browndashptc@gmail.com';
 
     /**
      * The core method invoked when the single master thread is initialized.
@@ -149,7 +151,7 @@ export class DashSessionAgent extends AppliedSessionAgent {
         const { _socket } = WebSocket;
         if (_socket) {
             const message = typeof reason === 'boolean' ? (reason ? 'exit' : 'temporary') : 'crash';
-            Utils.Emit(_socket, MessageStore.ConnectionTerminated, message);
+            ServerUtils.Emit(_socket, MessageStore.ConnectionTerminated, message);
         }
     };
 
@@ -217,7 +219,3 @@ export class DashSessionAgent extends AppliedSessionAgent {
         }
     }
 }
-
-export namespace DashSessionAgent {
-    export const notificationRecipient = 'browndashptc@gmail.com';
-}
diff --git a/src/server/DashStats.ts b/src/server/DashStats.ts
index a9e6af67c..485ab9f99 100644
--- a/src/server/DashStats.ts
+++ b/src/server/DashStats.ts
@@ -1,9 +1,8 @@
 import { cyan, magenta } from 'colors';
 import { Response } from 'express';
-import SocketIO from 'socket.io';
-import { timeMap } from './ApiManagers/UserManager';
-import { WebSocket } from './websocket';
 import * as fs from 'fs';
+import SocketIO from 'socket.io';
+import { socketMap, timeMap, userOperations } from './SocketData';
 
 /**
  * DashStats focuses on tracking user data for each session.
@@ -17,7 +16,6 @@ export namespace DashStats {
 
     const statsCSVDirectory = './src/server/stats/';
     const statsCSVFilename = statsCSVDirectory + 'userLoginStats.csv';
-    const columns = ['USERNAME', 'ACTION', 'TIME'];
 
     /**
      * UserStats holds the stats associated with a particular user.
@@ -77,6 +75,111 @@ export namespace DashStats {
     // structure
     export const lastUserOperations = new Map<string, UserLastOperations>();
 
+    /**
+     * convertToCSV() is a helper method that stringifies a CSVStore object
+     * that can be written to the CSV file later.
+     * @param dataObject the object to stringify
+     * @returns the object as a string.
+     */
+    function convertToCSV(dataObject: CSVStore): string {
+        return `${dataObject.USERNAME},${dataObject.ACTION},${dataObject.TIME}\n`;
+    }
+    /**
+     * getLastOperationsOrDefault() is a helper method that will attempt
+     * to query the lastUserOperations map for a specified username. If the
+     * username is not in the map, an empty UserLastOperations object is returned.
+     * @param username
+     * @returns the user's UserLastOperations structure or an empty
+     * UserLastOperations object (All values set to 0) if the username is not found.
+     */
+    function getLastOperationsOrDefault(username: string): UserLastOperations {
+        if (lastUserOperations.get(username) === undefined) {
+            const initializeOperationsQueue = [];
+            for (let i = 0; i < RATE_INTERVAL; i++) {
+                initializeOperationsQueue.push(0);
+            }
+            return {
+                sampleOperations: 0,
+                lastSampleOperations: 0,
+                previousOperationsQueue: initializeOperationsQueue,
+            };
+        }
+        return lastUserOperations.get(username)!;
+    }
+
+    /**
+     * updateLastOperations updates a specific user's UserLastOperations information
+     * for the current sampling cycle. The method removes old/outdated counts for
+     * operations from the queue and adds new data for the current sampling
+     * cycle to the queue, updating the total count as it goes.
+     * @param lastOperationData the old UserLastOperations data that must be updated
+     * @param currentOperations the total number of operations measured for this sampling cycle.
+     * @returns the udpated UserLastOperations structure.
+     */
+    function updateLastOperations(lastOperationData: UserLastOperations, currentOperations: number): UserLastOperations {
+        // create a copy of the UserLastOperations to modify
+        const newLastOperationData: UserLastOperations = {
+            sampleOperations: lastOperationData.sampleOperations,
+            lastSampleOperations: lastOperationData.lastSampleOperations,
+            previousOperationsQueue: lastOperationData.previousOperationsQueue.slice(),
+        };
+
+        let newSampleOperations = newLastOperationData.sampleOperations;
+        newSampleOperations -= newLastOperationData.previousOperationsQueue.shift()!; // removes and returns the first element of the queue
+        const operationsThisCycle = currentOperations - lastOperationData.lastSampleOperations;
+        newSampleOperations += operationsThisCycle; // add the operations this cycle to find out what our count for the interval should be (e.g operations in the last 10 seconds)
+
+        // update values for the copy object
+        newLastOperationData.sampleOperations = newSampleOperations;
+
+        newLastOperationData.previousOperationsQueue.push(operationsThisCycle);
+        newLastOperationData.lastSampleOperations = currentOperations;
+
+        return newLastOperationData;
+    }
+
+    /**
+     * getUserOperationsOrDefault() is a helper method to get the user's total
+     * operations for the CURRENT sampling interval. The method will return 0
+     * if the username is not in the userOperations map.
+     * @param username the username to search the map for
+     * @returns the total number of operations recorded up to this sampling cycle.
+     */
+    function getUserOperationsOrDefault(username: string): number {
+        return userOperations.get(username) === undefined ? 0 : userOperations.get(username)!;
+    }
+
+    /**
+     * getCurrentStats() calculates the total stats for this cycle. In this case,
+     * getCurrentStats() returns an Array of UserStats[] objects describing
+     * the stats for each user
+     * @returns an array of UserStats storing data for each user at the current moment.
+     */
+    function getCurrentStats(): UserStats[] {
+        const socketPairs: UserStats[] = [];
+        Array.from(socketMap.entries()).forEach(([key, value]) => {
+            const username = value.split(' ')[0];
+            const connectionTime = new Date(timeMap[username]);
+
+            const connectionTimeString = connectionTime.toLocaleDateString() + ' ' + connectionTime.toLocaleTimeString();
+
+            if (!key.disconnected) {
+                const lastRecordedOperations = getLastOperationsOrDefault(username);
+                const currentUserOperationCount = getUserOperationsOrDefault(username);
+
+                socketPairs.push({
+                    socketId: key.id,
+                    username: username,
+                    time: connectionTimeString.includes('Invalid Date') ? '' : connectionTimeString,
+                    operations: userOperations.get(username) ? userOperations.get(username)! : 0,
+                    rate: lastRecordedOperations.sampleOperations,
+                });
+                lastUserOperations.set(username, updateLastOperations(lastRecordedOperations, currentUserOperationCount));
+            }
+        });
+        return socketPairs;
+    }
+
     /**
      * handleStats is called when the /stats route is called, providing a JSON
      * object with relevant stats. In this case, we return the number of
@@ -84,8 +187,7 @@ export namespace DashStats {
      * @param res Response object from Express
      */
     export function handleStats(res: Response) {
-        let current = getCurrentStats();
-        const results: CSVStore[] = [];
+        const current = getCurrentStats();
         res.json({
             currentConnections: current.length,
             socketMap: current,
@@ -99,7 +201,7 @@ export namespace DashStats {
      * @returns a StatsDataBundle that is sent to the frontend view on each websocket update
      */
     export function getUpdatedStatsBundle(): StatsDataBundle {
-        let current = getCurrentStats();
+        const current = getCurrentStats();
 
         return {
             connectedUsers: current,
@@ -113,11 +215,8 @@ export namespace DashStats {
      * @param res
      */
     export function handleStatsView(res: Response) {
-        let current = getCurrentStats();
-
-        let connectedUsers = current.map(socketPair => {
-            return socketPair.time + ' - ' + socketPair.username + ' Operations: ' + socketPair.operations;
-        });
+        const current = getCurrentStats();
+        const connectedUsers = current.map(({ time, username, operations }) => time + ' - ' + username + ' Operations: ' + operations);
 
         let serverTraffic = ServerTraffic.NOT_BUSY;
         if (current.length < BUSY_SERVER_BOUND) {
@@ -145,17 +244,17 @@ export namespace DashStats {
      */
     export function logUserLogin(username: string | undefined, socket: SocketIO.Socket) {
         if (!(username === undefined)) {
-            let currentDate = new Date();
+            const currentDate = new Date();
             console.log(magenta(`User ${username.split(' ')[0]} logged in at: ${currentDate.toISOString()}`));
 
-            let toWrite: CSVStore = {
+            const toWrite: CSVStore = {
                 USERNAME: username,
                 ACTION: 'loggedIn',
                 TIME: currentDate.toISOString(),
             };
 
             if (!fs.existsSync(statsCSVDirectory)) fs.mkdirSync(statsCSVDirectory);
-            let statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' });
+            const statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' });
             statsFile.write(convertToCSV(toWrite));
             statsFile.end();
             console.log(cyan(convertToCSV(toWrite)));
@@ -170,10 +269,10 @@ export namespace DashStats {
      */
     export function logUserLogout(username: string | undefined, socket: SocketIO.Socket) {
         if (!(username === undefined)) {
-            let currentDate = new Date();
+            const currentDate = new Date();
 
-            let statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' });
-            let toWrite: CSVStore = {
+            const statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' });
+            const toWrite: CSVStore = {
                 USERNAME: username,
                 ACTION: 'loggedOut',
                 TIME: currentDate.toISOString(),
@@ -182,110 +281,4 @@ export namespace DashStats {
             statsFile.end();
         }
     }
-
-    /**
-     * getLastOperationsOrDefault() is a helper method that will attempt
-     * to query the lastUserOperations map for a specified username. If the
-     * username is not in the map, an empty UserLastOperations object is returned.
-     * @param username
-     * @returns the user's UserLastOperations structure or an empty
-     * UserLastOperations object (All values set to 0) if the username is not found.
-     */
-    function getLastOperationsOrDefault(username: string): UserLastOperations {
-        if (lastUserOperations.get(username) === undefined) {
-            let initializeOperationsQueue = [];
-            for (let i = 0; i < RATE_INTERVAL; i++) {
-                initializeOperationsQueue.push(0);
-            }
-            return {
-                sampleOperations: 0,
-                lastSampleOperations: 0,
-                previousOperationsQueue: initializeOperationsQueue,
-            };
-        }
-        return lastUserOperations.get(username)!;
-    }
-
-    /**
-     * updateLastOperations updates a specific user's UserLastOperations information
-     * for the current sampling cycle. The method removes old/outdated counts for
-     * operations from the queue and adds new data for the current sampling
-     * cycle to the queue, updating the total count as it goes.
-     * @param lastOperationData the old UserLastOperations data that must be updated
-     * @param currentOperations the total number of operations measured for this sampling cycle.
-     * @returns the udpated UserLastOperations structure.
-     */
-    function updateLastOperations(lastOperationData: UserLastOperations, currentOperations: number): UserLastOperations {
-        // create a copy of the UserLastOperations to modify
-        let newLastOperationData: UserLastOperations = {
-            sampleOperations: lastOperationData.sampleOperations,
-            lastSampleOperations: lastOperationData.lastSampleOperations,
-            previousOperationsQueue: lastOperationData.previousOperationsQueue.slice(),
-        };
-
-        let newSampleOperations = newLastOperationData.sampleOperations;
-        newSampleOperations -= newLastOperationData.previousOperationsQueue.shift()!; // removes and returns the first element of the queue
-        let operationsThisCycle = currentOperations - lastOperationData.lastSampleOperations;
-        newSampleOperations += operationsThisCycle; // add the operations this cycle to find out what our count for the interval should be (e.g operations in the last 10 seconds)
-
-        // update values for the copy object
-        newLastOperationData.sampleOperations = newSampleOperations;
-
-        newLastOperationData.previousOperationsQueue.push(operationsThisCycle);
-        newLastOperationData.lastSampleOperations = currentOperations;
-
-        return newLastOperationData;
-    }
-
-    /**
-     * getUserOperationsOrDefault() is a helper method to get the user's total
-     * operations for the CURRENT sampling interval. The method will return 0
-     * if the username is not in the userOperations map.
-     * @param username the username to search the map for
-     * @returns the total number of operations recorded up to this sampling cycle.
-     */
-    function getUserOperationsOrDefault(username: string): number {
-        return WebSocket.userOperations.get(username) === undefined ? 0 : WebSocket.userOperations.get(username)!;
-    }
-
-    /**
-     * getCurrentStats() calculates the total stats for this cycle. In this case,
-     * getCurrentStats() returns an Array of UserStats[] objects describing
-     * the stats for each user
-     * @returns an array of UserStats storing data for each user at the current moment.
-     */
-    function getCurrentStats(): UserStats[] {
-        let socketPairs: UserStats[] = [];
-        for (let [key, value] of WebSocket.socketMap) {
-            let username = value.split(' ')[0];
-            let connectionTime = new Date(timeMap[username]);
-
-            let connectionTimeString = connectionTime.toLocaleDateString() + ' ' + connectionTime.toLocaleTimeString();
-
-            if (!key.disconnected) {
-                let lastRecordedOperations = getLastOperationsOrDefault(username);
-                let currentUserOperationCount = getUserOperationsOrDefault(username);
-
-                socketPairs.push({
-                    socketId: key.id,
-                    username: username,
-                    time: connectionTimeString.includes('Invalid Date') ? '' : connectionTimeString,
-                    operations: WebSocket.userOperations.get(username) ? WebSocket.userOperations.get(username)! : 0,
-                    rate: lastRecordedOperations.sampleOperations,
-                });
-                lastUserOperations.set(username, updateLastOperations(lastRecordedOperations, currentUserOperationCount));
-            }
-        }
-        return socketPairs;
-    }
-
-    /**
-     * convertToCSV() is a helper method that stringifies a CSVStore object
-     * that can be written to the CSV file later.
-     * @param dataObject the object to stringify
-     * @returns the object as a string.
-     */
-    function convertToCSV(dataObject: CSVStore): string {
-        return `${dataObject.USERNAME},${dataObject.ACTION},${dataObject.TIME}\n`;
-    }
 }
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 307aec6fc..3d8325da9 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -15,16 +15,15 @@ import { basename } from 'path';
 import * as parse from 'pdf-parse';
 import * as request from 'request-promise';
 import { Duplex, Stream } from 'stream';
-import { filesDirectory, publicDirectory } from '.';
 import { Utils } from '../Utils';
-import { Opt } from '../fields/Doc';
-import { ParsedPDF } from '../server/PdfTypes';
 import { createIfNotExists } from './ActionUtilities';
 import { AzureManager } from './ApiManagers/AzureManager';
-import { Directory, clientPathToFile, pathToDirectory, serverPathToFile } from './ApiManagers/UploadManager';
+import { ParsedPDF } from './PdfTypes';
 import { AcceptableMedia, Upload } from './SharedMediaTypes';
+import { Directory, clientPathToFile, filesDirectory, pathToDirectory, publicDirectory, serverPathToFile } from './SocketData';
 import { resolvedServerUrl } from './server_Initialization';
-const spawn = require('child_process').spawn;
+
+const { spawn } = require('child_process');
 const { exec } = require('child_process');
 const requestImageSize = require('../client/util/request-image-size');
 
@@ -42,7 +41,7 @@ export function InjectSize(filename: string, size: SizeSuffix) {
 }
 
 function isLocal() {
-    return /Dash-Web[0-9]*[\\\/]src[\\\/]server[\\\/]public[\\\/](.*)/;
+    return /Dash-Web[0-9]*[\\/]src[\\/]server[\\/]public[\\/](.*)/;
 }
 
 function usingAzure() {
@@ -68,11 +67,21 @@ export namespace DashUploadUtils {
     const size = 'content-length';
     const type = 'content-type';
 
-    const BLOBSTORE_URL = process.env.BLOBSTORE_URL;
-    const RESIZE_FUNCTION_URL = process.env.RESIZE_FUNCTION_URL;
+    const { BLOBSTORE_URL, RESIZE_FUNCTION_URL } = process.env;
+
+    const { imageFormats, videoFormats, applicationFormats, audioFormats } = AcceptableMedia; // TODO:glr
 
-    const { imageFormats, videoFormats, applicationFormats, audioFormats } = AcceptableMedia; //TODO:glr
+    export function fExists(name: string, destination: Directory) {
+        const destinationPath = serverPathToFile(destination, name);
+        return existsSync(destinationPath);
+    }
 
+    export function getAccessPaths(directory: Directory, fileName: string) {
+        return {
+            client: clientPathToFile(directory, fileName),
+            server: serverPathToFile(directory, fileName),
+        };
+    }
     export async function concatVideos(filePaths: string[]): Promise<Upload.AccessPathInfo> {
         // make a list of paths to create the ordered text file for ffmpeg
         const inputListName = 'concat.txt';
@@ -80,14 +89,14 @@ export namespace DashUploadUtils {
         // make a list of paths to create the ordered text file for ffmpeg
         const filePathsText = filePaths.map(filePath => `file '${filePath}'`).join('\n');
         // write the text file to the file system
-        await new Promise<void>((res, reject) =>
+        await new Promise<void>((res, reject) => {
             writeFile(textFilePath, filePathsText, err => {
                 if (err) {
                     reject();
                     console.log(err);
                 } else res();
-            })
-        );
+            });
+        });
 
         // make output file name based on timestamp
         const outputFileName = `output-${Utils.GenerateGuid()}.mp4`;
@@ -95,19 +104,19 @@ export namespace DashUploadUtils {
         const outputFilePath = path.join(pathToDirectory(Directory.videos), outputFileName);
 
         // concatenate the videos
-        await new Promise((resolve, reject) =>
+        await new Promise((resolve, reject) => {
             ffmpeg()
                 .input(textFilePath)
                 .inputOptions(['-f concat', '-safe 0'])
                 // .outputOptions('-c copy')
-                //.videoCodec("copy")
+                // .videoCodec("copy")
                 .save(outputFilePath)
                 .on('error', (err: any) => {
                     console.log(err);
                     reject();
                 })
-                .on('end', resolve)
-        );
+                .on('end', resolve);
+        });
 
         // delete concat.txt from the file system
         unlinkSync(textFilePath);
@@ -135,270 +144,76 @@ export namespace DashUploadUtils {
         };
     }
 
-    export function QueryYoutubeProgress(videoId: string, user?: Express.User) {
+    export const uploadProgress = new Map<string, string>();
+
+    export function QueryYoutubeProgress(videoId: string) {
         // console.log(`PROGRESS:${videoId}`, (user as any)?.email);
         return uploadProgress.get(videoId) ?? 'pending data upload';
     }
 
-    export let uploadProgress = new Map<string, string>();
-
-    export function uploadYoutube(videoId: string, overwriteId: string): Promise<Upload.FileResponse> {
-        return new Promise<Upload.FileResponse<Upload.FileInformation>>((res, rej) => {
-            const name = videoId;
-            const filepath = name.replace(/^-/, '__') + '.mp4';
-            const finalPath = serverPathToFile(Directory.videos, filepath);
-            if (existsSync(finalPath)) {
-                uploadProgress.set(overwriteId, 'computing duration');
-                exec(`yt-dlp -o ${finalPath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => {
-                    const time = Array.from(stdout.trim().split(':')).reverse();
-                    const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
-                    res(resolveExistingFile(name, filepath, Directory.videos, 'video/mp4', duration, undefined));
-                });
-            } else {
-                uploadProgress.set(overwriteId, 'starting download');
-                const ytdlp = spawn(`yt-dlp`, ['-o', filepath, `https://www.youtube.com/watch?v=${videoId}`, '--max-filesize', '100M', '-f', 'mp4']);
-
-                ytdlp.stdout.on('data', (data: any) => uploadProgress.set(overwriteId, data.toString()));
-
-                let errors = '';
-                ytdlp.stderr.on('data', (data: any) => {
-                    uploadProgress.set(overwriteId, 'error:' + data.toString());
-                    errors = data.toString();
-                });
-
-                ytdlp.on('exit', function (code: any) {
-                    if (code) {
-                        res({
-                            source: {
-                                size: 0,
-                                filepath: name,
-                                originalFilename: name,
-                                newFilename: name,
-                                mimetype: 'video',
-                                hashAlgorithm: 'md5',
-                                toJSON: () => ({ newFilename: name, filepath, mimetype: 'video', mtime: new Date(), size: 0, length: 0, originalFilename: name }),
-                            },
-                            result: { name: 'failed youtube query', message: `Could not archive video.  ${code ? errors : uploadProgress.get(videoId)}` },
-                        });
-                    } else {
-                        uploadProgress.set(overwriteId, 'computing duration');
-                        exec(`yt-dlp-o ${filepath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => {
-                            const time = Array.from(stdout.trim().split(':')).reverse();
-                            const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
-                            const data = { size: 0, filepath, name, mimetype: 'video', originalFilename: name, newFilename: name, hashAlgorithm: 'md5' as 'md5', type: 'video/mp4' };
-                            const file = { ...data, toJSON: () => ({ ...data, length: 0, filename: data.filepath.replace(/.*\//, ''), mtime: new Date(), toJson: () => undefined as any }) };
-                            MoveParsedFile(file, Directory.videos).then(output => {
-                                console.log('OUTPUT = ' + output);
-                                res(output);
-                            });
-                        });
-                    }
+    /**
+     * Basically just a wrapper around rename, which 'deletes'
+     * the file at the old path and 'moves' it to the new one. For simplicity, the
+     * caller just has to pass in the name of the target directory, and this function
+     * will resolve the actual target path from that.
+     * @param file The file to move
+     * @param destination One of the specific media asset directories into which to move it
+     * @param suffix If the file doesn't have a suffix and you want to provide it one
+     * to appear in the new location
+     */
+    export async function MoveParsedFile(file: formidable.File, destination: Directory, suffix?: string, text?: string, duration?: number, targetName?: string): Promise<Upload.FileResponse> {
+        const { filepath } = file;
+        let name = targetName ?? path.basename(filepath);
+        suffix && (name += suffix);
+        return new Promise(resolve => {
+            const destinationPath = serverPathToFile(destination, name);
+            rename(filepath, destinationPath, error => {
+                resolve({
+                    source: file,
+                    result: error ?? {
+                        accessPaths: {
+                            agnostic: getAccessPaths(destination, name),
+                        },
+                        rawText: text,
+                        duration,
+                    },
                 });
-            }
+            });
         });
     }
 
-    export async function upload(file: File, overwriteGuid?: string): Promise<Upload.FileResponse> {
-        const isAzureOn = usingAzure();
-        const { mimetype: type, filepath, originalFilename } = file;
-        const types = type?.split('/') ?? [];
-        // uploadProgress.set(overwriteGuid ?? name, 'uploading'); // If the client sent a guid it uses to track upload progress, use that guid. Otherwise, use the file's name.
-
-        const category = types[0];
-        let format = `.${types[1]}`;
-        console.log(green(`Processing upload of file (${originalFilename}) and format (${format}) with upload type (${type}) in category (${category}).`));
-
-        switch (category) {
-            case 'image':
-                if (imageFormats.includes(format)) {
-                    const result = await UploadImage(filepath, basename(filepath));
-                    return { source: file, result };
-                }
-                fs.unlink(filepath, () => {});
-                return { source: file, result: { name: 'Unsupported image format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .jpg` } };
-            case 'video':
-                if (format.includes('x-matroska')) {
-                    console.log('case video');
-                    await new Promise(res =>
-                        ffmpeg(file.filepath)
-                            .videoCodec('copy') // this will copy the data instead of reencode it
-                            .save(file.filepath.replace('.mkv', '.mp4'))
-                            .on('end', res)
-                            .on('error', (e: any) => console.log(e))
-                    );
-                    file.filepath = file.filepath.replace('.mkv', '.mp4');
-                    format = '.mp4';
-                }
-                if (format.includes('quicktime')) {
-                    let abort = false;
-                    await new Promise<void>(res =>
-                        ffmpeg.ffprobe(file.filepath, (err: any, metadata: any) => {
-                            if (metadata.streams.some((stream: any) => stream.codec_name === 'hevc')) {
-                                abort = true;
-                            }
-                            res();
-                        })
-                    );
-                    if (abort) {
-                        // bcz: instead of aborting, we could convert the file using the code below to an mp4.  Problem is that this takes a long time and will clog up the server.
-                        // await new Promise(res =>
-                        //     ffmpeg(file.path)
-                        //         .videoCodec('libx264') // this will copy the data instead of reencode it
-                        //         .audioCodec('mp2')
-                        //         .save(file.path.replace('.MOV', '.mp4').replace('.mov', '.mp4'))
-                        //         .on('end', res)
-                        // );
-                        // file.path = file.path.replace('.mov', '.mp4').replace('.MOV', '.mp4');
-                        // format = '.mp4';
-                        fs.unlink(filepath, () => {});
-                        return { source: file, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .mp4` } };
-                    }
-                }
-                if (videoFormats.includes(format) || format.includes('.webm')) {
-                    return MoveParsedFile(file, Directory.videos);
-                }
-                fs.unlink(filepath, () => {});
-                return { source: file, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .mp4` } };
-            case 'application':
-                if (applicationFormats.includes(format)) {
-                    const val = UploadPdf(file);
-                    if (val) return val;
-                }
-            case 'audio':
-                const components = format.split(';');
-                if (components.length > 1) {
-                    format = components[0];
-                }
-                if (audioFormats.includes(format)) {
-                    return UploadAudio(file, format);
-                }
-                fs.unlink(filepath, () => {});
-                return { source: file, result: { name: 'Unsupported audio format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .mp3` } };
-            case 'text':
-                if (types[1] == 'csv') {
-                    return UploadCsv(file);
-                }
-        }
-
-        console.log(red(`Ignoring unsupported file (${originalFilename}) with upload type (${type}).`));
-        fs.unlink(filepath, () => {});
-        return { source: file, result: new Error(`Could not upload unsupported file (${originalFilename}) with upload type (${type}).`) };
-    }
-
-    async function UploadPdf(file: File) {
-        const fileKey = (await md5File(file.filepath)) + '.pdf';
-        const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
-        if (fExists(fileKey, Directory.pdfs) && fExists(textFilename, Directory.text)) {
-            fs.unlink(file.filepath, () => {});
-            return new Promise<Upload.FileResponse>(res => {
-                const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
-                const readStream = createReadStream(serverPathToFile(Directory.text, textFilename));
-                var rawText = '';
-                readStream
-                    .on('data', chunk => (rawText += chunk.toString())) //
-                    .on('end', () => res(resolveExistingFile(file.originalFilename ?? '', fileKey, Directory.pdfs, file.mimetype, undefined, rawText)));
-            });
-        }
-        const dataBuffer = readFileSync(file.filepath);
-        const result: ParsedPDF | any = await parse(dataBuffer).catch((e: any) => e);
-        if (!result.code) {
-            await new Promise<void>((resolve, reject) => {
-                const writeStream = createWriteStream(serverPathToFile(Directory.text, textFilename));
-                writeStream.write(result?.text, error => (error ? reject(error) : resolve()));
+    const parseExifData = async (source: string) => {
+        const image = await request.get(source, { encoding: null });
+        const { /* data, */ error } = await new Promise<{ data: any; error: any }>(resolve => {
+            // eslint-disable-next-line no-new
+            new ExifImage({ image }, (error, data) => {
+                const reason = (error as any)?.code;
+                resolve({ data, error: reason });
             });
-            return MoveParsedFile(file, Directory.pdfs, undefined, result?.text, undefined, fileKey);
-        }
-        return { source: file, result: { name: 'faile pdf pupload', message: `Could not upload (${file.originalFilename}).${result.message}` } };
-    }
-
-    async function UploadCsv(file: File) {
-        const { filepath: sourcePath } = file;
-        // read the file as a string
-        const data = readFileSync(sourcePath, 'utf8');
-        // split the string into an array of lines
-        return MoveParsedFile(file, Directory.csv, undefined, data);
-        // console.log(csvParser(data));
-    }
-
-    const manualSuffixes = ['.webm'];
-
-    async function UploadAudio(file: File, format: string) {
-        const suffix = manualSuffixes.includes(format) ? format : undefined;
-        return MoveParsedFile(file, Directory.audio, suffix);
-    }
-
-    /**
-     * Uploads an image specified by the @param source to Dash's /public/files/
-     * directory, and returns information generated during that upload
-     *
-     * @param {string} source is either the absolute path of an already uploaded image or
-     * the url of a remote image
-     * @param {string} filename dictates what to call the image. If not specified,
-     * the name {@param prefix}_upload_{GUID}
-     * @param {string} prefix is a string prepended to the generated image name in the
-     * event that @param filename is not specified
-     *
-     * @returns {ImageUploadInformation | Error} This method returns
-     * 1) the paths to the uploaded images (plural due to resizing)
-     * 2) the exif data embedded in the image, or the error explaining why exif couldn't be parsed
-     * 3) the size of the image, in bytes (4432130)
-     * 4) the content type of the image, i.e. image/(jpeg | png | ...)
-     */
-    export const UploadImage = async (source: string, filename?: string, prefix: string = ''): Promise<Upload.ImageInformation | Error> => {
-        const metadata = await InspectImage(source);
-        if (metadata instanceof Error) {
-            return { name: metadata.name, message: metadata.message };
-        }
-        const outputFile = filename || metadata.filename || '';
-
-        return UploadInspectedImage(metadata, outputFile, prefix);
+        });
+        return error ? { data: undefined, error } : { data: await exifr.parse(image), error };
     };
-
-    export async function buildFileDirectories() {
-        if (!existsSync(publicDirectory)) {
-            console.error('\nPlease ensure that the following directory exists...\n');
-            console.log(publicDirectory);
-            process.exit(0);
-        }
-        if (!existsSync(filesDirectory)) {
-            console.error('\nPlease ensure that the following directory exists...\n');
-            console.log(filesDirectory);
-            process.exit(0);
-        }
-        const pending = Object.keys(Directory).map(sub => createIfNotExists(`${filesDirectory}/${sub}`));
-        return Promise.all(pending);
-    }
-
-    export interface RequestedImageSize {
-        width: number;
-        height: number;
-        type: string;
-    }
-
-    export interface ImageResizer {
-        width: number;
-        suffix: SizeSuffix;
-    }
-
     /**
      * Based on the url's classification as local or remote, gleans
      * as much information as possible about the specified image
      *
      * @param source is the path or url to the image in question
      */
-    export const InspectImage = async (source: string): Promise<Upload.InspectionResults | Error> => {
-        let rawMatches: RegExpExecArray | null;
+    export const InspectImage = async (sourceIn: string): Promise<Upload.InspectionResults | Error> => {
+        let source = sourceIn;
+        const rawMatches = /^data:image\/([a-z]+);base64,(.*)/.exec(source);
         let filename: string | undefined;
         /**
          * Just more edge case handling: this if clause handles the case where an image onto the canvas that
          * is represented by a base64 encoded data uri, rather than a proper file. We manually write it out
          * to the server and then carry on as if it had been put there by the Formidable form / file parser.
          */
-        if ((rawMatches = /^data:image\/([a-z]+);base64,(.*)/.exec(source)) !== null) {
+        if (rawMatches !== null) {
             const [ext, data] = rawMatches.slice(1, 3);
-            const resolved = (filename = `upload_${Utils.GenerateGuid()}.${ext}`);
+            filename = `upload_${Utils.GenerateGuid()}.${ext}`;
+            const resolved = filename;
             if (usingAzure()) {
-                const response = await AzureManager.UploadBase64ImageBlob(resolved, data);
+                await AzureManager.UploadBase64ImageBlob(resolved, data);
                 source = `${AzureManager.BASE_STRING}/${resolved}`;
             } else {
                 source = `${resolvedServerUrl}${clientPathToFile(Directory.images, resolved)}`;
@@ -438,7 +253,7 @@ export namespace DashUploadUtils {
 
         // Use the request library to parse out file level image information in the headers
         const { headers } = await new Promise<any>((resolve, reject) => {
-            return request.head(resolvedUrl, (error, res) => (error ? reject(error) : resolve(res)));
+            request.head(resolvedUrl, (error, res) => (error ? reject(error) : resolve(res)));
         }).catch(e => {
             console.log('Error processing headers: ', e);
         });
@@ -449,6 +264,7 @@ export namespace DashUploadUtils {
             // Bundle up the information into an object
             return {
                 source,
+                // eslint-disable-next-line radix
                 contentSize: parseInt(headers[size]),
                 contentType: headers[type],
                 nativeWidth,
@@ -462,49 +278,71 @@ export namespace DashUploadUtils {
         }
     };
 
+    async function correctRotation(imgSourcePath: string) {
+        const buffer = fs.readFileSync(imgSourcePath);
+        try {
+            return (await autorotate.rotate(buffer, { quality: 30 })).buffer;
+        } catch (e) {
+            return buffer;
+        }
+    }
+
     /**
-     * Basically just a wrapper around rename, which 'deletes'
-     * the file at the old path and 'moves' it to the new one. For simplicity, the
-     * caller just has to pass in the name of the target directory, and this function
-     * will resolve the actual target path from that.
-     * @param file The file to move
-     * @param destination One of the specific media asset directories into which to move it
-     * @param suffix If the file doesn't have a suffix and you want to provide it one
-     * to appear in the new location
-     */
-    export async function MoveParsedFile(file: formidable.File, destination: Directory, suffix: string | undefined = undefined, text?: string, duration?: number, targetName?: string): Promise<Upload.FileResponse> {
-        const { filepath } = file;
-        let name = targetName ?? path.basename(filepath);
-        suffix && (name += suffix);
-        return new Promise(resolve => {
-            const destinationPath = serverPathToFile(destination, name);
-            rename(filepath, destinationPath, error => {
-                resolve({
-                    source: file,
-                    result: error
-                        ? error
-                        : {
-                              accessPaths: {
-                                  agnostic: getAccessPaths(destination, name),
-                              },
-                              rawText: text,
-                              duration,
-                          },
-                });
-            });
-        });
+     * define the resizers to use
+     * @param ext the extension
+     * @returns an array of resize descriptions
+     */
+    export function imageResampleSizes(ext: string): DashUploadUtils.ImageResizer[] {
+        return [
+            { suffix: SizeSuffix.Original, width: 0 },
+            ...[...(AcceptableMedia.imageFormats.includes(ext.toLowerCase()) ? Object.values(DashUploadUtils.Sizes) : [])].map(({ suffix, width }) => ({
+                width,
+                suffix,
+            })),
+        ];
     }
 
-    export function fExists(name: string, destination: Directory) {
-        const destinationPath = serverPathToFile(destination, name);
-        return existsSync(destinationPath);
-    }
+    /**
+     * outputResizedImages takes in a readable stream and resizes the images according to the sizes defined at the top of this file.
+     *
+     * The new images will be saved to the server with the corresponding prefixes.
+     * @param imgSourcePath file path for image being resized
+     * @param outputFileName the basename (No suffix) of the outputted file.
+     * @param outputDirectory the directory to output to, usually Directory.Images
+     * @returns a map with suffixes as keys and resized filenames as values.
+     */
+    export async function outputResizedImages(imgSourcePath: string, outputFileName: string, outputDirectory: string) {
+        const writtenFiles: { [suffix: string]: string } = {};
+        const sizes = imageResampleSizes(path.extname(outputFileName));
 
-    export function getAccessPaths(directory: Directory, fileName: string) {
-        return {
-            client: clientPathToFile(directory, fileName),
-            server: serverPathToFile(directory, fileName),
+        const imgBuffer = await correctRotation(imgSourcePath);
+        const imgReadStream = new Duplex();
+        imgReadStream.push(imgBuffer);
+        imgReadStream.push(null);
+        const outputPath = (suffix: SizeSuffix) => {
+            writtenFiles[suffix] = InjectSize(outputFileName, suffix);
+            return path.resolve(outputDirectory, writtenFiles[suffix]);
         };
+        await Promise.all(
+            sizes.filter(({ width }) => !width).map(({ suffix }) => 
+                new Promise<void>(res => {
+                    imgReadStream.pipe(createWriteStream(outputPath(suffix))).on('close', res);
+                })
+        )); // prettier-ignore
+
+        return Jimp.read(imgBuffer)
+            .then(async (imgIn: any) => {
+                let img = imgIn;
+                await Promise.all( sizes.filter(({ width }) => width).map(({ width, suffix }) => {
+                    img = img.resize(width, Jimp.AUTO).write(outputPath(suffix));
+                    return img;
+                } )); // prettier-ignore
+                return writtenFiles;
+            })
+            .catch((e: any) => {
+                console.log('ERROR' + e);
+                return writtenFiles;
+            });
     }
 
     /**
@@ -555,119 +393,265 @@ export namespace DashUploadUtils {
             } catch (e) {
                 // input is a blob or other, try reading it to create a metadata source file.
                 const reqSource = request(metadata.source);
-                let readStream: Stream = reqSource instanceof Promise ? await reqSource : reqSource;
+                const readStream: Stream = reqSource instanceof Promise ? await reqSource : reqSource;
                 const readSource = `${prefix}upload_${Utils.GenerateGuid()}.${metadata.contentType.split('/')[1].toLowerCase()}`;
-                await new Promise<void>((res, rej) =>
+                await new Promise<void>((res, rej) => {
                     readStream
                         .pipe(createWriteStream(readSource))
                         .on('close', () => res())
-                        .on('error', () => rej())
-                );
+                        .on('error', () => rej());
+                });
                 writtenFiles = await outputResizedImages(readSource, resolved, pathToDirectory(Directory.images));
-                fs.unlink(readSource, err => console.log("Couldn't unlink temporary image file:" + readSource));
+                fs.unlink(readSource, err => console.log("Couldn't unlink temporary image file:" + readSource, err));
             }
         }
-        for (const suffix of Object.keys(writtenFiles)) {
+        Array.from(Object.keys(writtenFiles)).forEach(suffix => {
             information.accessPaths[suffix] = getAccessPaths(images, writtenFiles[suffix]);
-        }
+        });
         if (isLocal().test(source) && cleanUp) {
             unlinkSync(source);
         }
         return information;
     };
 
-    const bufferConverterRec = (layer: any) => {
-        for (const key of Object.keys(layer)) {
-            const val: any = layer[key];
-            if (val instanceof Buffer) {
-                layer[key] = val.toString();
-            } else if (Array.isArray(val) && typeof val[0] === 'number') {
-                layer[key] = Buffer.from(val).toString();
-            } else if (typeof val === 'object') {
-                bufferConverterRec(val);
-            }
+    /**
+     * Uploads an image specified by the @param source to Dash's /public/files/
+     * directory, and returns information generated during that upload
+     *
+     * @param {string} source is either the absolute path of an already uploaded image or
+     * the url of a remote image
+     * @param {string} filename dictates what to call the image. If not specified,
+     * the name {@param prefix}_upload_{GUID}
+     * @param {string} prefix is a string prepended to the generated image name in the
+     * event that @param filename is not specified
+     *
+     * @returns {ImageUploadInformation | Error} This method returns
+     * 1) the paths to the uploaded images (plural due to resizing)
+     * 2) the exif data embedded in the image, or the error explaining why exif couldn't be parsed
+     * 3) the size of the image, in bytes (4432130)
+     * 4) the content type of the image, i.e. image/(jpeg | png | ...)
+     */
+    export const UploadImage = async (source: string, filename?: string, prefix: string = ''): Promise<Upload.ImageInformation | Error> => {
+        const metadata = await InspectImage(source);
+        if (metadata instanceof Error) {
+            return { name: metadata.name, message: metadata.message };
         }
+        const outputFile = filename || metadata.filename || '';
+
+        return UploadInspectedImage(metadata, outputFile, prefix);
     };
 
-    const parseExifData = async (source: string) => {
-        const image = await request.get(source, { encoding: null });
-        const { data, error } = await new Promise<{ data: any; error: any }>(resolve => {
-            new ExifImage({ image }, (error, data) => {
-                let reason: Opt<string> = undefined;
-                if (error) {
-                    reason = (error as any).code;
-                }
-                resolve({ data, error: reason });
-            });
+    export function uploadYoutube(videoId: string, overwriteId: string): Promise<Upload.FileResponse> {
+        return new Promise<Upload.FileResponse<Upload.FileInformation>>(res => {
+            const name = videoId;
+            const filepath = name.replace(/^-/, '__') + '.mp4';
+            const finalPath = serverPathToFile(Directory.videos, filepath);
+            if (existsSync(finalPath)) {
+                uploadProgress.set(overwriteId, 'computing duration');
+                exec(`yt-dlp -o ${finalPath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any /* , stderr: any */) => {
+                    const time = Array.from(stdout.trim().split(':')).reverse();
+                    const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
+                    res(resolveExistingFile(name, filepath, Directory.videos, 'video/mp4', duration, undefined));
+                });
+            } else {
+                uploadProgress.set(overwriteId, 'starting download');
+                const ytdlp = spawn(`yt-dlp`, ['-o', filepath, `https://www.youtube.com/watch?v=${videoId}`, '--max-filesize', '100M', '-f', 'mp4']);
+
+                ytdlp.stdout.on('data', (data: any) => uploadProgress.set(overwriteId, data.toString()));
+
+                let errors = '';
+                ytdlp.stderr.on('data', (data: any) => {
+                    uploadProgress.set(overwriteId, 'error:' + data.toString());
+                    errors = data.toString();
+                });
+
+                ytdlp.on('exit', (code: any) => {
+                    if (code) {
+                        res({
+                            source: {
+                                size: 0,
+                                filepath: name,
+                                originalFilename: name,
+                                newFilename: name,
+                                mimetype: 'video',
+                                hashAlgorithm: 'md5',
+                                toJSON: () => ({ newFilename: name, filepath, mimetype: 'video', mtime: new Date(), size: 0, length: 0, originalFilename: name }),
+                            },
+                            result: { name: 'failed youtube query', message: `Could not archive video.  ${code ? errors : uploadProgress.get(videoId)}` },
+                        });
+                    } else {
+                        uploadProgress.set(overwriteId, 'computing duration');
+                        exec(`yt-dlp-o ${filepath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (/* error: any, stdout: any, stderr: any */) => {
+                            // const time = Array.from(stdout.trim().split(':')).reverse();
+                            // const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
+                            const data = { size: 0, filepath, name, mimetype: 'video', originalFilename: name, newFilename: name, hashAlgorithm: 'md5' as 'md5', type: 'video/mp4' };
+                            const file = { ...data, toJSON: () => ({ ...data, length: 0, filename: data.filepath.replace(/.*\//, ''), mtime: new Date(), toJson: () => undefined as any }) };
+                            MoveParsedFile(file, Directory.videos).then(output => res(output));
+                        });
+                    }
+                });
+            }
         });
-        //data && bufferConverterRec(data);
-        return error ? { data: undefined, error } : { data: await exifr.parse(image), error };
-    };
+    }
+    const manualSuffixes = ['.webm'];
 
-    const { pngs, jpgs, webps, tiffs } = AcceptableMedia;
-    const pngOptions = {
-        compressionLevel: 9,
-        adaptiveFiltering: true,
-        force: true,
-    };
+    async function UploadAudio(file: File, format: string) {
+        const suffix = manualSuffixes.includes(format) ? format : undefined;
+        return MoveParsedFile(file, Directory.audio, suffix);
+    }
 
-    async function correctRotation(imgSourcePath: string) {
-        const buffer = fs.readFileSync(imgSourcePath);
-        try {
-            return (await autorotate.rotate(buffer, { quality: 30 })).buffer;
-        } catch (e) {
-            return buffer;
+    async function UploadPdf(file: File) {
+        const fileKey = (await md5File(file.filepath)) + '.pdf';
+        const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
+        if (fExists(fileKey, Directory.pdfs) && fExists(textFilename, Directory.text)) {
+            fs.unlink(file.filepath, () => {});
+            return new Promise<Upload.FileResponse>(res => {
+                const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
+                const readStream = createReadStream(serverPathToFile(Directory.text, textFilename));
+                let rawText = '';
+                readStream
+                    .on('data', chunk => {
+                        rawText += chunk.toString();
+                    })
+                    .on('end', () => res(resolveExistingFile(file.originalFilename ?? '', fileKey, Directory.pdfs, file.mimetype, undefined, rawText)));
+            });
         }
+        const dataBuffer = readFileSync(file.filepath);
+        const result: ParsedPDF | any = await parse(dataBuffer).catch((e: any) => e);
+        if (!result.code) {
+            await new Promise<void>((resolve, reject) => {
+                const writeStream = createWriteStream(serverPathToFile(Directory.text, textFilename));
+                writeStream.write(result?.text, error => (error ? reject(error) : resolve()));
+            });
+            return MoveParsedFile(file, Directory.pdfs, undefined, result?.text, undefined, fileKey);
+        }
+        return { source: file, result: { name: 'faile pdf pupload', message: `Could not upload (${file.originalFilename}).${result.message}` } };
     }
 
-    /**
-     * outputResizedImages takes in a readable stream and resizes the images according to the sizes defined at the top of this file.
-     *
-     * The new images will be saved to the server with the corresponding prefixes.
-     * @param imgSourcePath file path for image being resized
-     * @param outputFileName the basename (No suffix) of the outputted file.
-     * @param outputDirectory the directory to output to, usually Directory.Images
-     * @returns a map with suffixes as keys and resized filenames as values.
-     */
-    export async function outputResizedImages(imgSourcePath: string, outputFileName: string, outputDirectory: string) {
-        const writtenFiles: { [suffix: string]: string } = {};
-        const sizes = imageResampleSizes(path.extname(outputFileName));
+    async function UploadCsv(file: File) {
+        const { filepath: sourcePath } = file;
+        // read the file as a string
+        const data = readFileSync(sourcePath, 'utf8');
+        // split the string into an array of lines
+        return MoveParsedFile(file, Directory.csv, undefined, data);
+        // console.log(csvParser(data));
+    }
 
-        const imgBuffer = await correctRotation(imgSourcePath);
-        const imgReadStream = new Duplex();
-        imgReadStream.push(imgBuffer);
-        imgReadStream.push(null);
-        const outputPath = (suffix: SizeSuffix) => path.resolve(outputDirectory, (writtenFiles[suffix] = InjectSize(outputFileName, suffix)));
-        await Promise.all(
-            sizes.filter(({ width }) => !width).map(({ suffix }) => 
-                new Promise<void>(res => imgReadStream.pipe(createWriteStream(outputPath(suffix))).on('close', res))
-        )); // prettier-ignore
+    export async function upload(file: File /* , overwriteGuid?: string */): Promise<Upload.FileResponse> {
+        // const isAzureOn = usingAzure();
+        const { mimetype, filepath, originalFilename } = file;
+        const types = mimetype?.split('/') ?? [];
+        // uploadProgress.set(overwriteGuid ?? name, 'uploading'); // If the client sent a guid it uses to track upload progress, use that guid. Otherwise, use the file's name.
 
-        return Jimp.read(imgBuffer)
-            .then(async (img: any) => {
-                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: any) => {
-                console.log('ERROR' + e);
-                return writtenFiles;
-            });
+        const category = types[0];
+        let format = `.${types[1]}`;
+        console.log(green(`Processing upload of file (${originalFilename}) and format (${format}) with upload type (${mimetype}) in category (${category}).`));
+
+        switch (category) {
+            case 'image':
+                if (imageFormats.includes(format)) {
+                    const result = await UploadImage(filepath, basename(filepath));
+                    return { source: file, result };
+                }
+                fs.unlink(filepath, () => {});
+                return { source: file, result: { name: 'Unsupported image format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .jpg` } };
+            case 'video': {
+                const vidFile = file;
+                if (format.includes('x-matroska')) {
+                    await new Promise(res => {
+                        ffmpeg(vidFile.filepath)
+                            .videoCodec('copy') // this will copy the data instead of reencode it
+                            .save(vidFile.filepath.replace('.mkv', '.mp4'))
+                            .on('end', res)
+                            .on('error', (e: any) => console.log(e));
+                    });
+                    vidFile.filepath = vidFile.filepath.replace('.mkv', '.mp4');
+                    format = '.mp4';
+                }
+                if (format.includes('quicktime')) {
+                    let abort = false;
+                    await new Promise<void>(res => {
+                        ffmpeg.ffprobe(vidFile.filepath, (err: any, metadata: any) => {
+                            if (metadata.streams.some((stream: any) => stream.codec_name === 'hevc')) {
+                                abort = true;
+                            }
+                            res();
+                        });
+                    });
+                    if (abort) {
+                        // bcz: instead of aborting, we could convert the file using the code below to an mp4.  Problem is that this takes a long time and will clog up the server.
+                        // await new Promise(res =>
+                        //     ffmpeg(file.path)
+                        //         .videoCodec('libx264') // this will copy the data instead of reencode it
+                        //         .audioCodec('mp2')
+                        //         .save(vidFile.path.replace('.MOV', '.mp4').replace('.mov', '.mp4'))
+                        //         .on('end', res)
+                        // );
+                        // vidFile.path = vidFile.path.replace('.mov', '.mp4').replace('.MOV', '.mp4');
+                        // format = '.mp4';
+                        fs.unlink(filepath, () => {});
+                        return { source: file, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .mp4` } };
+                    }
+                }
+                if (videoFormats.includes(format) || format.includes('.webm')) {
+                    return MoveParsedFile(vidFile, Directory.videos);
+                }
+                fs.unlink(filepath, () => {});
+                return { source: vidFile, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .mp4` } };
+            }
+            case 'application':
+                if (applicationFormats.includes(format)) {
+                    const val = UploadPdf(file);
+                    if (val) return val;
+                }
+                break;
+            case 'audio': {
+                const components = format.split(';');
+                if (components.length > 1) {
+                    [format] = components;
+                }
+                if (audioFormats.includes(format)) {
+                    return UploadAudio(file, format);
+                }
+                fs.unlink(filepath, () => {});
+                return { source: file, result: { name: 'Unsupported audio format', message: `Could not upload unsupported file (${originalFilename}). Please convert to an .mp3` } };
+            }
+            case 'text':
+                if (types[1] === 'csv') {
+                    return UploadCsv(file);
+                }
+                break;
+            default:
+        }
+
+        console.log(red(`Ignoring unsupported file (${originalFilename}) with upload type (${mimetype}).`));
+        fs.unlink(filepath, () => {});
+        return { source: file, result: new Error(`Could not upload unsupported file (${originalFilename}) with upload type (${mimetype}).`) };
     }
 
-    /**
-     * define the resizers to use
-     * @param ext the extension
-     * @returns an array of resize descriptions
-     */
-    export function imageResampleSizes(ext: string): DashUploadUtils.ImageResizer[] {
-        return [
-            { suffix: SizeSuffix.Original, width: 0 },
-            ...[...(AcceptableMedia.imageFormats.includes(ext.toLowerCase()) ? Object.values(DashUploadUtils.Sizes) : [])].map(({ suffix, width }) => ({
-                width,
-                suffix,
-            })),
-        ];
+    export async function buildFileDirectories() {
+        if (!existsSync(publicDirectory)) {
+            console.error('\nPlease ensure that the following directory exists...\n');
+            console.log(publicDirectory);
+            process.exit(0);
+        }
+        if (!existsSync(filesDirectory)) {
+            console.error('\nPlease ensure that the following directory exists...\n');
+            console.log(filesDirectory);
+            process.exit(0);
+        }
+        const pending = Object.keys(Directory).map(sub => createIfNotExists(`${filesDirectory}/${sub}`));
+        return Promise.all(pending);
+    }
+
+    export interface RequestedImageSize {
+        width: number;
+        height: number;
+        type: string;
+    }
+
+    export interface ImageResizer {
+        width: number;
+        suffix: SizeSuffix;
     }
 }
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 8f0af08bc..03150c841 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,22 +1,47 @@
-import { Point } from "../pen-gestures/ndollar";
-import { Utils } from "../Utils";
+import * as uuid from 'uuid';
+import { Point } from '../pen-gestures/ndollar';
 
+function GenerateDeterministicGuid(seed: string): string {
+    return uuid.v5(seed, uuid.v5.URL);
+}
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
 export class Message<T> {
     private _name: string;
     private _guid: string;
 
     constructor(name: string) {
         this._name = name;
-        this._guid = Utils.GenerateDeterministicGuid(name);
+        this._guid = GenerateDeterministicGuid(name);
     }
 
-    get Name(): string { return this._name; }
-    get Message(): string { return this._guid; }
+    get Name(): string {
+        return this._name;
+    }
+    get Message(): string {
+        return this._guid;
+    }
 }
 
 export enum Types {
-    Number, List, Key, Image, Web, Document, Text, Icon, RichText, DocumentReference,
-    Html, Video, Audio, Ink, PDF, Tuple, Boolean, Script, Templates
+    Number,
+    List,
+    Key,
+    Image,
+    Web,
+    Document,
+    Text,
+    Icon,
+    RichText,
+    DocumentReference,
+    Html,
+    Video,
+    Audio,
+    Ink,
+    PDF,
+    Tuple,
+    Boolean,
+    Script,
+    Templates,
 }
 
 export interface Transferable {
@@ -26,7 +51,9 @@ export interface Transferable {
 }
 
 export enum YoutubeQueryTypes {
-    Channels, SearchVideo, VideoDetails
+    Channels,
+    SearchVideo,
+    VideoDetails,
 }
 
 export interface YoutubeQueryInput {
@@ -45,7 +72,7 @@ export interface Diff extends Reference {
 
 export interface GestureContent {
     readonly points: Array<Point>;
-    readonly bounds: { right: number, left: number, bottom: number, top: number, width: number, height: number };
+    readonly bounds: { right: number; left: number; bottom: number; top: number; width: number; height: number };
     readonly width?: string;
     readonly color?: string;
 }
@@ -73,27 +100,27 @@ export interface RoomMessage {
 }
 
 export namespace MessageStore {
-    export const Foo = new Message<string>("Foo");
-    export const Bar = new Message<string>("Bar");
-    export const SetField = new Message<Transferable>("Set Field"); // send Transferable (no reply)
-    export const GetField = new Message<string>("Get Field"); // send string 'id' get Transferable back
-    export const GetFields = new Message<string[]>("Get Fields"); // send string[] of 'id' get Transferable[] back
-    export const GetDocument = new Message<string>("Get Document");
-    export const DeleteAll = new Message<any>("Delete All");
-    export const ConnectionTerminated = new Message<string>("Connection Terminated");
-
-    export const GesturePoints = new Message<GestureContent>("Gesture Points");
-    export const MobileInkOverlayTrigger = new Message<MobileInkOverlayContent>("Trigger Mobile Ink Overlay");
-    export const UpdateMobileInkOverlayPosition = new Message<UpdateMobileInkOverlayPositionContent>("Update Mobile Ink Overlay Position");
-    export const MobileDocumentUpload = new Message<MobileDocumentUploadContent>("Upload Document From Mobile");
-
-    export const GetRefField = new Message<string>("Get Ref Field");
-    export const GetRefFields = new Message<string[]>("Get Ref Fields");
-    export const UpdateField = new Message<Diff>("Update Ref Field");
-    export const CreateField = new Message<Reference>("Create Ref Field");
-    export const YoutubeApiQuery = new Message<YoutubeQueryInput>("Youtube Api Query");
-    export const DeleteField = new Message<string>("Delete field");
-    export const DeleteFields = new Message<string[]>("Delete fields");
-
-    export const UpdateStats = new Message<string>("updatestats");
+    export const Foo = new Message<string>('Foo');
+    export const Bar = new Message<string>('Bar');
+    export const SetField = new Message<Transferable>('Set Field'); // send Transferable (no reply)
+    export const GetField = new Message<string>('Get Field'); // send string 'id' get Transferable back
+    export const GetFields = new Message<string[]>('Get Fields'); // send string[] of 'id' get Transferable[] back
+    export const GetDocument = new Message<string>('Get Document');
+    export const DeleteAll = new Message<any>('Delete All');
+    export const ConnectionTerminated = new Message<string>('Connection Terminated');
+
+    export const GesturePoints = new Message<GestureContent>('Gesture Points');
+    export const MobileInkOverlayTrigger = new Message<MobileInkOverlayContent>('Trigger Mobile Ink Overlay');
+    export const UpdateMobileInkOverlayPosition = new Message<UpdateMobileInkOverlayPositionContent>('Update Mobile Ink Overlay Position');
+    export const MobileDocumentUpload = new Message<MobileDocumentUploadContent>('Upload Document From Mobile');
+
+    export const GetRefField = new Message<string>('Get Ref Field');
+    export const GetRefFields = new Message<string[]>('Get Ref Fields');
+    export const UpdateField = new Message<Diff>('Update Ref Field');
+    export const CreateField = new Message<Reference>('Create Ref Field');
+    export const YoutubeApiQuery = new Message<YoutubeQueryInput>('Youtube Api Query');
+    export const DeleteField = new Message<string>('Delete field');
+    export const DeleteFields = new Message<string[]>('Delete fields');
+
+    export const UpdateStats = new Message<string>('updatestats');
 }
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index 540bca776..d8e0455f6 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -1,9 +1,9 @@
 import { cyan, green, red } from 'colors';
 import { Express, Request, Response } from 'express';
-import { AdminPriviliges } from '.';
 import { Utils } from '../Utils';
-import { DashUserModel } from './authentication/DashUserModel';
 import RouteSubscriber from './RouteSubscriber';
+import { AdminPrivileges } from './SocketData';
+import { DashUserModel } from './authentication/DashUserModel';
 
 export enum Method {
     GET,
@@ -21,6 +21,34 @@ export type SecureHandler = (core: AuthorizedCore) => any | Promise<any>;
 export type PublicHandler = (core: CoreArguments) => any | Promise<any>;
 export type ErrorHandler = (core: CoreArguments & { error: any }) => any | Promise<any>;
 
+export const STATUS = {
+    OK: 200,
+    BAD_REQUEST: 400,
+    EXECUTION_ERROR: 500,
+    PERMISSION_DENIED: 403,
+};
+
+export function _error(res: Response, message: string, error?: any) {
+    console.error(message, error);
+    res.statusMessage = message;
+    res.status(STATUS.EXECUTION_ERROR).send(error);
+}
+
+export function _success(res: Response, body: any) {
+    res.status(STATUS.OK).send(body);
+}
+
+export function _invalid(res: Response, message: string) {
+    res.statusMessage = message;
+    res.status(STATUS.BAD_REQUEST).send();
+}
+
+export function _permissionDenied(res: Response, message?: string) {
+    if (message) {
+        res.statusMessage = message;
+    }
+    res.status(STATUS.PERMISSION_DENIED).send(`Permission Denied! ${message}`);
+}
 export interface RouteInitializer {
     method: Method;
     subscription: string | RouteSubscriber | (string | RouteSubscriber)[];
@@ -71,7 +99,7 @@ export default class RouteManager {
                 console.log('please remove all duplicate routes before continuing');
             }
             if (malformedCount) {
-                console.log(`please ensure all routes adhere to ^\/$|^\/[A-Za-z]+(\/\:[A-Za-z?_]+)*$`);
+                console.log(`please ensure all routes adhere to ^/$|^/[A-Za-z]+(/:[A-Za-z?_]+)*$`);
             }
             process.exit(1);
         } else {
@@ -94,7 +122,7 @@ export default class RouteManager {
         typeof initializer.subscription === 'string' && RouteManager.routes.push(initializer.subscription);
         initializer.subscription instanceof RouteSubscriber && RouteManager.routes.push(initializer.subscription.root);
         initializer.subscription instanceof Array &&
-            initializer.subscription.map(sub => {
+            initializer.subscription.forEach(sub => {
                 typeof sub === 'string' && RouteManager.routes.push(sub);
                 sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root);
             });
@@ -120,23 +148,23 @@ export default class RouteManager {
             };
             if (user) {
                 if (requireAdmin && isRelease && process.env.PASSWORD) {
-                    if (AdminPriviliges.get(user.id)) {
-                        AdminPriviliges.delete(user.id);
+                    if (AdminPrivileges.get(user.id)) {
+                        AdminPrivileges.delete(user.id);
                     } else {
-                        return res.redirect(`/admin/${req.originalUrl.substring(1).replace('/', ':')}`);
+                        res.redirect(`/admin/${req.originalUrl.substring(1).replace('/', ':')}`);
+                        return;
                     }
                 }
                 await tryExecute(secureHandler, { ...core, user });
-            } else {
-                //req.session!.target = target;
-                if (publicHandler) {
-                    await tryExecute(publicHandler, core);
-                    if (!res.headersSent) {
-                        // res.redirect("/login");
-                    }
-                } else {
-                    res.redirect('/login');
+            }
+            // req.session!.target = target;
+            else if (publicHandler) {
+                await tryExecute(publicHandler, core);
+                if (!res.headersSent) {
+                    // res.redirect("/login");
                 }
+            } else {
+                res.redirect('/login');
             }
             setTimeout(() => {
                 if (!res.headersSent) {
@@ -153,7 +181,7 @@ export default class RouteManager {
             } else {
                 route = subscriber.build;
             }
-            if (!/^\/$|^\/[A-Za-z\*]+(\/\:[A-Za-z?_\*]+)*$/g.test(route)) {
+            if (!/^\/$|^\/[A-Za-z*]+(\/:[A-Za-z?_*]+)*$/g.test(route)) {
                 this.failedRegistrations.push({
                     reason: RegistrationError.Malformed,
                     route,
@@ -180,6 +208,7 @@ export default class RouteManager {
                     case Method.POST:
                         this.server.post(route, supervised);
                         break;
+                    default:
                 }
             }
         };
@@ -190,32 +219,3 @@ export default class RouteManager {
         }
     };
 }
-
-export const STATUS = {
-    OK: 200,
-    BAD_REQUEST: 400,
-    EXECUTION_ERROR: 500,
-    PERMISSION_DENIED: 403,
-};
-
-export function _error(res: Response, message: string, error?: any) {
-    console.error(message, error);
-    res.statusMessage = message;
-    res.status(STATUS.EXECUTION_ERROR).send(error);
-}
-
-export function _success(res: Response, body: any) {
-    res.status(STATUS.OK).send(body);
-}
-
-export function _invalid(res: Response, message: string) {
-    res.statusMessage = message;
-    res.status(STATUS.BAD_REQUEST).send();
-}
-
-export function _permission_denied(res: Response, message?: string) {
-    if (message) {
-        res.statusMessage = message;
-    }
-    res.status(STATUS.PERMISSION_DENIED).send(`Permission Denied! ${message}`);
-}
diff --git a/src/server/SocketData.ts b/src/server/SocketData.ts
new file mode 100644
index 000000000..e857996e5
--- /dev/null
+++ b/src/server/SocketData.ts
@@ -0,0 +1,35 @@
+import { Socket } from 'socket.io';
+import * as path from 'path';
+
+export const timeMap: { [id: string]: number } = {};
+export const userOperations = new Map<string, number>();
+export const socketMap = new Map<Socket, string>();
+
+export const publicDirectory = path.resolve(__dirname, 'public');
+export const filesDirectory = path.resolve(publicDirectory, 'files');
+
+export const AdminPrivileges: Map<string, boolean> = new Map();
+
+export const resolvedPorts: { server: number; socket: number } = { server: 1050, socket: 4321 };
+
+export enum Directory {
+    parsed_files = 'parsed_files',
+    images = 'images',
+    videos = 'videos',
+    pdfs = 'pdfs',
+    text = 'text',
+    audio = 'audio',
+    csv = 'csv',
+}
+
+export function serverPathToFile(directory: Directory, filename: string) {
+    return path.normalize(`${filesDirectory}/${directory}/${filename}`);
+}
+
+export function pathToDirectory(directory: Directory) {
+    return path.normalize(`${filesDirectory}/${directory}`);
+}
+
+export function clientPathToFile(directory: Directory, filename: string) {
+    return `/files/${directory}/${filename}`;
+}
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 3940b7a3d..55e5fd7c0 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -1,9 +1,9 @@
-import { google } from 'googleapis';
-import { OAuth2Client, Credentials, OAuth2ClientOptions } from 'google-auth-library';
-import { Opt } from '../../../fields/Doc';
 import { GaxiosResponse } from 'gaxios';
-import * as request from 'request-promise';
+import { Credentials, OAuth2Client, OAuth2ClientOptions } from 'google-auth-library';
+import { google } from 'googleapis';
 import * as qs from 'query-string';
+import * as request from 'request-promise';
+import { Opt } from '../../../fields/Doc';
 import { Database } from '../../database';
 import { GoogleCredentialsLoader } from './CredentialsLoader';
 
diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts
index 9c1525df0..b5d1dba28 100644
--- a/src/server/authentication/AuthenticationManager.ts
+++ b/src/server/authentication/AuthenticationManager.ts
@@ -6,7 +6,7 @@ import './Passport';
 import * as async from 'async';
 import * as nodemailer from 'nodemailer';
 import * as c from 'crypto';
-import { emptyFunction, Utils } from '../../Utils';
+import { emptyFunction, Utils } from '../../ClientUtils';
 import { MailOptions } from 'nodemailer/lib/stream-transport';
 import { check, validationResult } from 'express-validator';
 
diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts
index dbb7a79ed..3bc21ecb6 100644
--- a/src/server/authentication/DashUserModel.ts
+++ b/src/server/authentication/DashUserModel.ts
@@ -1,9 +1,8 @@
-//@ts-ignore
 import * as bcrypt from 'bcrypt-nodejs';
-//@ts-ignore
 import * as mongoose from 'mongoose';
 import { Utils } from '../../Utils';
 
+type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => void) => void;
 export type DashUserModel = mongoose.Document & {
     email: String;
     password: string;
@@ -26,8 +25,6 @@ export type DashUserModel = mongoose.Document & {
     comparePassword: comparePasswordFunction;
 };
 
-type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => void) => void;
-
 export type AuthToken = {
     accessToken: string;
     kind: string;
@@ -75,16 +72,19 @@ userSchema.pre('save', function save(next) {
         bcrypt.hash(
             user.password,
             salt,
-            () => void {},
+            () => {},
             (err: mongoose.Error, hash: string) => {
                 if (err) {
                     return next(err);
                 }
                 user.password = hash;
                 next();
+                return undefined;
             }
         );
+        return undefined;
     });
+    return undefined;
 });
 
 const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) {
diff --git a/src/server/database.ts b/src/server/database.ts
index 3a087ce38..3a28dc87e 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -9,6 +9,7 @@ import { Transferable } from './Message';
 import { Upload } from './SharedMediaTypes';
 
 export namespace Database {
+    // eslint-disable-next-line import/no-mutable-exports
     export let disconnect: Function;
 
     class DocSchema implements mongodb.BSON.Document {
@@ -30,7 +31,10 @@ export namespace Database {
     export async function tryInitializeConnection() {
         try {
             const { connection } = mongoose;
-            disconnect = async () => new Promise<any>(resolve => connection.close().then(resolve));
+            disconnect = async () =>
+                new Promise<any>(resolve => {
+                    connection.close().then(resolve);
+                });
             if (connection.readyState === ConnectionStates.disconnected) {
                 await new Promise<void>((resolve, reject) => {
                     connection.on('error', reject);
@@ -39,7 +43,7 @@ export namespace Database {
                         resolve();
                     });
                     mongoose.connect(url, {
-                        //useNewUrlParser: true,
+                        // useNewUrlParser: true,
                         dbName: schema,
                         // reconnectTries: Number.MAX_VALUE,
                         // reconnectInterval: 1000,
@@ -81,8 +85,8 @@ export namespace Database {
                 const collection = this.db.collection<DocSchema>(collectionName);
                 const prom = this.currentWrites[id];
                 let newProm: Promise<void>;
-                const run = (): Promise<void> => {
-                    return new Promise<void>(resolve => {
+                const run = (): Promise<void> =>
+                    new Promise<void>(resolve => {
                         collection
                             .updateOne({ _id: id }, value, { upsert })
                             .then(res => {
@@ -96,13 +100,12 @@ export namespace Database {
                                 console.log('MOngo UPDATE ONE ERROR:', error);
                             });
                     });
-                };
                 newProm = prom ? prom.then(run) : run();
                 this.currentWrites[id] = newProm;
                 return newProm;
-            } else {
-                this.onConnect.push(() => this.update(id, value, callback, upsert, collectionName));
             }
+            this.onConnect.push(() => this.update(id, value, callback, upsert, collectionName));
+            return undefined;
         }
 
         public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult<mongodb.Document>) => void, upsert = true, collectionName = DocumentsCollection) {
@@ -110,8 +113,8 @@ export namespace Database {
                 const collection = this.db.collection<DocSchema>(collectionName);
                 const prom = this.currentWrites[id];
                 let newProm: Promise<void>;
-                const run = (): Promise<void> => {
-                    return new Promise<void>(resolve => {
+                const run = (): Promise<void> =>
+                    new Promise<void>(resolve => {
                         collection.replaceOne({ _id: id }, value, { upsert }).then(res => {
                             if (this.currentWrites[id] === newProm) {
                                 delete this.currentWrites[id];
@@ -120,7 +123,6 @@ export namespace Database {
                             callback(undefined as any, res as any);
                         });
                     });
-                };
                 newProm = prom ? prom.then(run) : run();
                 this.currentWrites[id] = newProm;
             } else {
@@ -132,8 +134,10 @@ export namespace Database {
             const cursor = this.db?.listCollections();
             const collectionNames: string[] = [];
             if (cursor) {
+                // eslint-disable-next-line no-await-in-loop
                 while (await cursor.hasNext()) {
-                    const collection: any = await cursor.next();
+                    // eslint-disable-next-line no-await-in-loop
+                    const collection = await cursor.next();
                     collection && collectionNames.push(collection.name);
                 }
             }
@@ -141,26 +145,30 @@ export namespace Database {
         }
 
         public delete(query: any, collectionName?: string): Promise<mongodb.DeleteResult>;
+        // eslint-disable-next-line no-dupe-class-members
         public delete(id: string, collectionName?: string): Promise<mongodb.DeleteResult>;
-        public delete(id: any, collectionName = DocumentsCollection) {
+        // eslint-disable-next-line no-dupe-class-members
+        public delete(idIn: any, collectionName = DocumentsCollection) {
+            let id = idIn;
             if (typeof id === 'string') {
                 id = { _id: id };
             }
             if (this.db) {
-                const db = this.db;
-                return new Promise(res =>
-                    db
-                        .collection(collectionName)
+                const { db } = this;
+                return new Promise(res => {
+                    db.collection(collectionName)
                         .deleteMany(id)
-                        .then(result => res(result))
-                );
-            } else {
-                return new Promise(res => this.onConnect.push(() => res(this.delete(id, collectionName))));
+                        .then(result => res(result));
+                });
             }
+            return new Promise(res => {
+                this.onConnect.push(() => res(this.delete(id, collectionName)));
+            });
         }
 
         public async dropSchema(...targetSchemas: string[]): Promise<any> {
             const executor = async (database: mongodb.Db) => {
+                // eslint-disable-next-line no-use-before-define
                 const existing = await Instance.getCollectionNames();
                 let valid: string[];
                 if (targetSchemas.length) {
@@ -173,12 +181,13 @@ export namespace Database {
             };
             if (this.db) {
                 return executor(this.db);
-            } else {
-                this.onConnect.push(() => this.db && executor(this.db));
             }
+            this.onConnect.push(() => this.db && executor(this.db));
+            return undefined;
         }
 
-        public async insert(value: any, collectionName = DocumentsCollection) {
+        public async insert(valueIn: any, collectionName = DocumentsCollection) {
+            const value = valueIn;
             if (this.db && value !== null) {
                 if ('id' in value) {
                     value._id = value.id;
@@ -188,36 +197,36 @@ export namespace Database {
                 const collection = this.db.collection<DocSchema>(collectionName);
                 const prom = this.currentWrites[id];
                 let newProm: Promise<void>;
-                const run = (): Promise<void> => {
-                    return new Promise<void>(resolve => {
+                const run = (): Promise<void> =>
+                    new Promise<void>(resolve => {
                         collection
                             .insertOne(value)
-                            .then(res => {
+                            .then(() => {
                                 if (this.currentWrites[id] === newProm) {
                                     delete this.currentWrites[id];
                                 }
                                 resolve();
                             })
-                            .catch(err => {
-                                console.log('Mongo INSERT ERROR: ', err);
-                            });
+                            .catch(err => console.log('Mongo INSERT ERROR: ', err));
                     });
-                };
                 newProm = prom ? prom.then(run) : run();
                 this.currentWrites[id] = newProm;
                 return newProm;
-            } else if (value !== null) {
+            }
+            if (value !== null) {
                 this.onConnect.push(() => this.insert(value, collectionName));
             }
+            return undefined;
         }
 
         public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection) {
             if (this.db) {
                 const collection = this.db.collection<DocSchema>(collectionName);
-                collection.findOne({ _id: id }).then(result => {
+                collection.findOne({ _id: id }).then(resultIn => {
+                    const result = resultIn;
                     if (result) {
                         result.id = result._id;
-                        //delete result._id;
+                        // delete result._id;
                         fn(result as any);
                     } else {
                         fn(undefined);
@@ -235,7 +244,8 @@ export namespace Database {
                     .find({ _id: { $in: ids } })
                     .toArray();
                 fn(
-                    found.map((doc: any) => {
+                    found.map((docIn: any) => {
+                        const doc = docIn;
                         doc.id = doc._id;
                         delete doc._id;
                         return doc;
@@ -253,24 +263,26 @@ export namespace Database {
                     const count = Math.min(ids.length, 1000);
                     const index = ids.length - count;
                     const fetchIds = ids.splice(index, count).filter(id => !visited.has(id));
-                    if (!fetchIds.length) {
-                        continue;
-                    }
-                    const docs = await new Promise<{ [key: string]: any }[]>(res => this.getDocuments(fetchIds, res, collectionName));
-                    for (const doc of docs) {
-                        const id = doc.id;
-                        visited.add(id);
-                        ids.push(...(await fn(doc)));
+                    if (fetchIds.length) {
+                        // eslint-disable-next-line no-await-in-loop
+                        const docs = await new Promise<{ [key: string]: any }[]>(res => {
+                            this.getDocuments(fetchIds, res, collectionName);
+                        });
+                        docs.forEach(async doc => {
+                            const { id } = doc;
+                            visited.add(id);
+                            ids.push(...(await fn(doc)));
+                        });
                     }
                 }
-            } else {
-                return new Promise(res => {
-                    this.onConnect.push(() => {
-                        this.visit(ids, fn, collectionName);
-                        res();
-                    });
-                });
+                return undefined;
             }
+            return new Promise(res => {
+                this.onConnect.push(() => {
+                    this.visit(ids, fn, collectionName);
+                    res();
+                });
+            });
         }
 
         public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = DocumentsCollection): Promise<mongodb.FindCursor> {
@@ -280,36 +292,31 @@ export namespace Database {
                     cursor = cursor.project(projection);
                 }
                 return Promise.resolve<mongodb.FindCursor>(cursor);
-            } else {
-                return new Promise<mongodb.FindCursor>(res => {
-                    this.onConnect.push(() => res(this.query(query, projection, collectionName)));
-                });
             }
+            return new Promise<mongodb.FindCursor>(res => {
+                this.onConnect.push(() => {
+                    res(this.query(query, projection, collectionName));
+                });
+            });
         }
 
         public updateMany(query: any, update: any, collectionName = DocumentsCollection) {
             if (this.db) {
-                const db = this.db;
-                return new Promise<mongodb.UpdateResult>(res =>
-                    db
-                        .collection(collectionName)
+                const { db } = this;
+                return new Promise<mongodb.UpdateResult>(res => {
+                    db.collection(collectionName)
                         .updateMany(query, update)
                         .then(result => res(result))
-                        .catch(error => {
-                            console.log('Mongo INSERT MANY ERROR:', error);
-                        })
-                );
-            } else {
-                return new Promise<mongodb.UpdateResult>(res => {
-                    this.onConnect.push(() =>
-                        this.updateMany(query, update, collectionName)
-                            .then(res)
-                            .catch(error => {
-                                console.log('Mongo UPDATAE MANY ERROR: ', error);
-                            })
-                    );
+                        .catch(error => console.log('Mongo INSERT MANY ERROR:', error));
                 });
             }
+            return new Promise<mongodb.UpdateResult>(res => {
+                this.onConnect.push(() =>
+                    this.updateMany(query, update, collectionName)
+                        .then(res)
+                        .catch(error => console.log('Mongo UPDATAE MANY ERROR: ', error))
+                );
+            });
         }
 
         public print() {
@@ -375,9 +382,7 @@ export namespace Database {
          * Checks to see if an image with the given @param contentSize
          * already exists in the aux database, i.e. has already been downloaded from Google Photos.
          */
-        export const QueryUploadHistory = async (contentSize: number) => {
-            return SanitizedSingletonQuery<Upload.ImageInformation>({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory);
-        };
+        export const QueryUploadHistory = async (contentSize: number) => SanitizedSingletonQuery<Upload.ImageInformation>({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory);
 
         /**
          * Records the uploading of the image with the given @param information,
@@ -405,28 +410,25 @@ export namespace Database {
              * Retrieves the credentials associaed with @param userId
              * and optionally removes their database id according to @param removeId.
              */
-            export const Fetch = async (userId: string, removeId = true): Promise<Opt<StoredCredentials>> => {
-                return SanitizedSingletonQuery<StoredCredentials>({ userId }, AuxiliaryCollections.GoogleAccess, removeId);
-            };
+            export const Fetch = async (userId: string, removeId = true): Promise<Opt<StoredCredentials>> => SanitizedSingletonQuery<StoredCredentials>({ userId }, AuxiliaryCollections.GoogleAccess, removeId);
 
             /**
              * Writes the @param enrichedCredentials to the database, associated
              * with @param userId for later retrieval and updating.
              */
-            export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => {
-                return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAccess);
-            };
+            export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAccess);
 
             /**
-             * Updates the @param access_token and @param expiry_date fields
+             * Updates the @param accessToken and @param expiryDate fields
              * in the stored credentials associated with @param userId.
              */
-            export const Update = async (userId: string, access_token: string, expiry_date: number) => {
+            export const Update = async (userId: string, accessToken: string, expiryDate: number) => {
                 const entry = await Fetch(userId, false);
                 if (entry) {
-                    const parameters = { $set: { access_token, expiry_date } };
+                    const parameters = { $set: { access_token: accessToken, expiry_date: expiryDate } };
                     return Instance.update(entry._id, parameters, emptyFunction, true, AuxiliaryCollections.GoogleAccess);
                 }
+                return undefined;
             };
 
             /**
diff --git a/src/server/index.ts b/src/server/index.ts
index 47c37c9f0..5a86f36d9 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,9 +1,10 @@
-import * as dotenv from 'dotenv';
 import { yellow } from 'colors';
+import * as dotenv from 'dotenv';
 import * as mobileDetect from 'mobile-detect';
 import * as path from 'path';
 import * as qs from 'query-string';
-import { log_execution } from './ActionUtilities';
+import { logExecution } from './ActionUtilities';
+import { AdminPrivileges, resolvedPorts } from './SocketData';
 import DataVizManager from './ApiManagers/DataVizManager';
 import DeleteManager from './ApiManagers/DeleteManager';
 import DownloadManager from './ApiManagers/DownloadManager';
@@ -24,13 +25,10 @@ import { Database } from './database';
 import { Logger } from './ProcessFactory';
 import RouteManager, { Method, PublicHandler } from './RouteManager';
 import RouteSubscriber from './RouteSubscriber';
-import initializeServer, { resolvedPorts } from './server_Initialization';
+import initializeServer from './server_Initialization';
 dotenv.config();
-export const AdminPriviliges: Map<string, boolean> = new Map();
 export const onWindows = process.platform === 'win32';
 export let sessionAgent: AppliedSessionAgent;
-export const publicDirectory = path.resolve(__dirname, 'public');
-export const filesDirectory = path.resolve(publicDirectory, 'files');
 
 /**
  * These are the functions run before the server starts
@@ -45,7 +43,7 @@ async function preliminaryFunctions() {
     SSL.loadCredentials();
     GoogleApiServerUtils.processProjectCredentials();
     if (process.env.DB !== 'MEM') {
-        await log_execution({
+        await logExecution({
             startMessage: 'attempting to initialize mongodb connection',
             endMessage: 'connection outcome determined',
             action: Database.tryInitializeConnection,
@@ -136,7 +134,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
             const { previous_target } = req.params;
             let redirect: string;
             if (password === PASSWORD) {
-                AdminPriviliges.set(id, true);
+                AdminPrivileges.set(id, true);
                 redirect = `/${previous_target.replace(':', '/')}`;
             } else {
                 redirect = `/admin/${previous_target}`;
@@ -174,7 +172,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
  * the main monitor (master) thread.
  */
 export async function launchServer() {
-    await log_execution({
+    await logExecution({
         startMessage: '\nstarting execution of preliminary functions',
         endMessage: 'completed preliminary functions\n',
         action: preliminaryFunctions,
diff --git a/src/server/remapUrl.ts b/src/server/remapUrl.ts
index b8e17ec66..ca7ca241f 100644
--- a/src/server/remapUrl.ts
+++ b/src/server/remapUrl.ts
@@ -1,58 +1,69 @@
-import { Database } from "./database";
-import { resolvedPorts } from "./server_Initialization";
+import { URL } from 'url';
+import { Database } from './database';
+import { resolvedPorts } from './SocketData';
 
-//npx ts-node src/server/remapUrl.ts
+// npx ts-node src/server/remapUrl.ts
 
 const suffixMap: { [type: string]: true } = {
-    "video": true,
-    "pdf": true,
-    "audio": true,
-    "web": true,
-    "image": true,
-    "map": true,
+    video: true,
+    pdf: true,
+    audio: true,
+    web: true,
+    image: true,
+    map: true,
 };
 
 async function update() {
-    await new Promise(res => setTimeout(res, 10));
-    console.log("update");
+    await new Promise(res => {
+        setTimeout(res, 10);
+    });
+    console.log('update');
     const cursor = await Database.Instance.query({});
-    console.log("Cleared");
+    console.log('Cleared');
     const updates: [string, any][] = [];
     function updateDoc(doc: any) {
-        if (doc.__type !== "Doc") {
+        if (doc.__type !== 'Doc') {
             return;
         }
-        const fields = doc.fields;
+        const { fields } = doc;
         if (!fields) {
             return;
         }
-        const update: any = {
-        };
+        const updated: any = {};
         let dynfield = false;
-        for (const key in fields) {
+        Array.from(Object.keys(fields)).forEach(key => {
             const value = fields[key];
             if (value && value.__type && suffixMap[value.__type]) {
                 const url = new URL(value.url);
-                if (url.href.includes("localhost") && url.href.includes("Bill")) {
+                if (url.href.includes('localhost') && url.href.includes('Bill')) {
                     dynfield = true;
 
-                    update.$set = { ["fields." + key + ".url"]: `${url.protocol}//dash-web.eastus2.cloudapp.azure.com:${resolvedPorts.server}${url.pathname}` };
+                    updated.$set = { ['fields.' + key + '.url']: `${url.protocol}//dash-web.eastus2.cloudapp.azure.com:${resolvedPorts.server}${url.pathname}` };
                 }
             }
-        }
+        });
         if (dynfield) {
-            updates.push([doc._id, update]);
+            updates.push([doc._id, updated]);
         }
     }
     await cursor.forEach(updateDoc);
-    await Promise.all(updates.map(doc => {
-        console.log(doc[0], doc[1]);
-        return new Promise<void>(res => Database.Instance.update(doc[0], doc[1], () => {
-            console.log("wrote " + JSON.stringify(doc[1]));
-            res();
-        }, false));
-    }));
-    console.log("Done");
+    await Promise.all(
+        updates.map(doc => {
+            console.log(doc[0], doc[1]);
+            return new Promise<void>(res => {
+                Database.Instance.update(
+                    doc[0],
+                    doc[1],
+                    () => {
+                        console.log('wrote ' + JSON.stringify(doc[1]));
+                        res();
+                    },
+                    false
+                );
+            });
+        })
+    );
+    console.log('Done');
     // await Promise.all(updates.map(update => {
     //     return limit(() => Search.updateDocument(update));
     // }));
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 2d52ea906..8db7e9933 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -1,73 +1,59 @@
 import * as bodyParser from 'body-parser';
+import * as brotli from 'brotli';
 import { blue, yellow } from 'colors';
+import * as flash from 'connect-flash';
+import * as MongoStoreConnect from 'connect-mongo';
 import * as cors from 'cors';
 import * as express from 'express';
+import * as expressFlash from 'express-flash';
 import * as session from 'express-session';
 import { createServer } from 'https';
 import * as passport from 'passport';
 import * as request from 'request';
 import * as webpack from 'webpack';
 import * as wdm from 'webpack-dev-middleware';
+// eslint-disable-next-line import/no-extraneous-dependencies
 import * as whm from 'webpack-hot-middleware';
 import * as zlib from 'zlib';
-import { publicDirectory } from '.';
+import * as config from '../../webpack.config.js';
 import { logPort } from './ActionUtilities';
+import RouteManager from './RouteManager';
+import RouteSubscriber from './RouteSubscriber';
+import { publicDirectory, resolvedPorts } from './SocketData';
 import { SSL } from './apis/google/CredentialsLoader';
 import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager';
 import { Database } from './database';
-import RouteManager from './RouteManager';
-import RouteSubscriber from './RouteSubscriber';
 import { WebSocket } from './websocket';
-import * as expressFlash from 'express-flash';
-import * as flash from 'connect-flash';
-import * as brotli from 'brotli';
-import * as MongoStoreConnect from 'connect-mongo';
-import * as config from '../../webpack.config';
 
 /* RouteSetter is a wrapper around the server that prevents the server
    from being exposed. */
 export type RouteSetter = (server: RouteManager) => void;
-//export let disconnect: Function;
+// export let disconnect: Function;
 
-export let resolvedPorts: { server: number; socket: number } = { server: 1050, socket: 4321 };
+// eslint-disable-next-line import/no-mutable-exports
 export let resolvedServerUrl: string;
 
-export default async function InitializeServer(routeSetter: RouteSetter) {
-    const isRelease = determineEnvironment();
-    const app = buildWithMiddleware(express());
-    const compiler = webpack(config as any);
-
-    // route table managed by express.  routes are tested sequentially against each of these map rules.  when a match is found, the handler is called to process the request
-    app.use(wdm(compiler, { publicPath: config.output.publicPath }));
-    app.use(whm(compiler));
-    app.get(new RegExp(/^\/+$/), (req, res) => res.redirect(req.user ? '/home' : '/login')); // target urls that consist of one or more '/'s with nothing in between
-    app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader('Access-Control-Allow-Origin', '*') })); //all urls that start with dash's public directory: /files/  (e.g., /files/images, /files/audio, etc)
-    app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) }));
-    registerAuthenticationRoutes(app); // this adds routes to authenticate a user (login, etc)
-    registerCorsProxy(app); // this adds a /corsProxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies
-    isRelease && !SSL.Loaded && SSL.exit();
-    routeSetter(new RouteManager(app, isRelease)); // this sets up all the regular supervised routes (things like /home, download/upload api's, pdf, search, session, etc)
-    registerEmbeddedBrowseRelativePathHandler(app); // this allows renered web pages which internally have relative paths to find their content
+const week = 7 * 24 * 60 * 60 * 1000;
+const secret = '64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc';
+const store = process.env.DB === 'MEM' ? new session.MemoryStore() : MongoStoreConnect.create({ mongoUrl: Database.url });
 
-    isRelease && process.env.serverPort && (resolvedPorts.server = Number(process.env.serverPort));
-    const server = isRelease ? createServer(SSL.Credentials, app) : app;
-    await new Promise<void>(resolve => server.listen(resolvedPorts.server, resolve));
-    logPort('server', resolvedPorts.server);
+/* Determine if the enviroment is dev mode or release mode. */
+function determineEnvironment() {
+    const isRelease = process.env.RELEASE === 'true';
 
-    resolvedServerUrl = `${isRelease && process.env.serverName ? `https://${process.env.serverName}.com` : 'http://localhost'}:${resolvedPorts.server}`;
+    const color = isRelease ? blue : yellow;
+    const label = isRelease ? 'release' : 'development';
+    console.log(`\nrunning server in ${color(label)} mode`);
 
-    // initialize the web socket (bidirectional communication: if a user changes
-    // a field on one client, that change must be broadcast to all other clients)
-    await WebSocket.initialize(isRelease, SSL.Credentials);
+    // // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE
+    // // on the client side, thanks to dotenv in webpack.config.js
+    // let clientUtils = fs.readFileSync('./src/client/util/ClientUtils.ts.temp', 'utf8');
+    // clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`;
+    // fs.writeFileSync('./src/client/util/ClientUtils.ts', clientUtils, 'utf8');
 
-    //disconnect = async () => new Promise<Error>(resolve => server.close(resolve));
     return isRelease;
 }
 
-const week = 7 * 24 * 60 * 60 * 1000;
-const secret = '64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc';
-const store = process.env.DB === 'MEM' ? new session.MemoryStore() : MongoStoreConnect.create({ mongoUrl: Database.url });
-
 function buildWithMiddleware(server: express.Express) {
     [
         session({
@@ -100,72 +86,43 @@ function buildWithMiddleware(server: express.Express) {
     return server;
 }
 
-/* Determine if the enviroment is dev mode or release mode. */
-function determineEnvironment() {
-    const isRelease = process.env.RELEASE === 'true';
-
-    const color = isRelease ? blue : yellow;
-    const label = isRelease ? 'release' : 'development';
-    console.log(`\nrunning server in ${color(label)} mode`);
-
-    // // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE
-    // // on the client side, thanks to dotenv in webpack.config.js
-    // let clientUtils = fs.readFileSync('./src/client/util/ClientUtils.ts.temp', 'utf8');
-    // clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`;
-    // fs.writeFileSync('./src/client/util/ClientUtils.ts', clientUtils, 'utf8');
-
-    return isRelease;
-}
-
-function registerAuthenticationRoutes(server: express.Express) {
-    server.get('/signup', getSignup);
-    server.post('/signup', postSignup);
-
-    server.get('/login', getLogin);
-    server.post('/login', postLogin);
-
-    server.get('/logout', getLogout);
-
-    server.get('/forgotPassword', getForgot);
-    server.post('/forgotPassword', postForgot);
-
-    const reset = new RouteSubscriber('resetPassword').add('token').build;
-    server.get(reset, getReset);
-    server.post(reset, postReset);
-}
-
-function registerCorsProxy(server: express.Express) {
-    server.use('/corsProxy', async (req, res) => {
-        res.setHeader('Access-Control-Allow-Origin', '*');
-        res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
-        res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
-        const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : '';
-        let requrlraw = decodeURIComponent(req.url.substring(1));
-        const qsplit = requrlraw.split('?q=');
-        const newqsplit = requrlraw.split('&q=');
-        if (qsplit.length > 1 && newqsplit.length > 1) {
-            const lastq = newqsplit[newqsplit.length - 1];
-            requrlraw = qsplit[0] + '?q=' + lastq.split('&')[0] + '&' + qsplit[1].split('&')[1];
-        }
-        const requrl = requrlraw.startsWith('/') ? referer + requrlraw : requrlraw;
-        // cors weirdness here...
-        // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative,
-        // then we redirect again to the cors referer and just add the relative path.
-        if (!requrl.startsWith('http') && req.originalUrl.startsWith('/corsProxy') && referer?.includes('corsProxy')) {
-            res.redirect(referer + (referer.endsWith('/') ? '' : '/') + requrl);
+function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
+    server.use('*', (req, res) => {
+        // res.setHeader('Access-Control-Allow-Origin', '*');
+        // res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
+        // res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
+        const relativeUrl = req.originalUrl;
+        if (!res.headersSent && req.headers.referer?.includes('corsProxy')) {
+            if (!req.user) res.redirect('/home'); // When no user is logged in, we interpret a relative URL as being a reference to something they don't have access to and redirect to /home
+            // a request for something by a proxied referrer means it must be a relative reference.  So construct a proxied absolute reference here.
+            try {
+                const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart)
+                const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ )
+                const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ''); // the url of the referer without the proxy (e.g., : https://en.wikipedia.org/wiki/Engelbart)
+                const absoluteTargetBaseUrl = actualReferUrl.match(/https?:\/\/[^/]*/)![0]; // the base of the original url (e.g.,  https://en.wikipedia.org)
+                const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
+                const redirectUrl = relativeUrl.startsWith('//') ? 'http:' + relativeUrl : redirectedProxiedUrl;
+                res.redirect(redirectUrl);
+            } catch (e) {
+                console.log('Error embed: ', e);
+            }
+        } else if (relativeUrl.startsWith('/search') && !req.headers.referer?.includes('corsProxy')) {
+            // detect search query and use default search engine
+            res.redirect(req.headers.referer + 'corsProxy/' + encodeURIComponent('http://www.google.com' + relativeUrl));
         } else {
-            proxyServe(req, requrl, res);
+            res.end();
         }
     });
 }
 
 function proxyServe(req: any, requrl: string, response: any) {
+    // eslint-disable-next-line global-require
     const htmlBodyMemoryStream = new (require('memorystream'))();
-    var wasinBrFormat = false;
+    let wasinBrFormat = false;
     const sendModifiedBody = () => {
         const header = response.headers['content-encoding'];
-        const refToCors = (match: any, tag: string, sym: string, href: string, offset: any, string: any) => `${tag}=${sym + resolvedServerUrl}/corsProxy/${href + sym}`;
-        const relpathToCors = (match: any, href: string, offset: any, string: any) => `="${resolvedServerUrl + '/corsProxy/' + decodeURIComponent(req.originalUrl.split('/corsProxy/')[1].match(/https?:\/\/[^\/]*/)?.[0] ?? '') + '/' + href}"`;
+        const refToCors = (match: any, tag: string, sym: string, href: string) => `${tag}=${sym + resolvedServerUrl}/corsProxy/${href + sym}`;
+        // const relpathToCors = (match: any, href: string, offset: any, string: any) => `="${resolvedServerUrl + '/corsProxy/' + decodeURIComponent(req.originalUrl.split('/corsProxy/')[1].match(/https?:\/\/[^\/]*/)?.[0] ?? '') + '/' + href}"`;
         if (header) {
             try {
                 const bodyStream = htmlBodyMemoryStream.read();
@@ -174,8 +131,8 @@ function proxyServe(req: any, requrl: string, response: any) {
                     const htmlText = htmlInputText
                         .toString('utf8')
                         .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
-                        .replace(/(src|href)=([\'\"])(https?[^\2\n]*)\1/g, refToCors) // replace src or href='http(s)://...' or href="http(s)://.."
-                        //.replace(/= *"\/([^"]*)"/g, relpathToCors)
+                        .replace(/(src|href)=(['"])(https?[^\2\n]*)\1/g, refToCors) // replace src or href='http(s)://...' or href="http(s)://.."
+                        // .replace(/= *"\/([^"]*)"/g, relpathToCors)
                         .replace(/data-srcset="[^"]*"/g, '')
                         .replace(/srcset="[^"]*"/g, '')
                         .replace(/target="_blank"/g, '');
@@ -198,7 +155,7 @@ function proxyServe(req: any, requrl: string, response: any) {
         }
     };
     const retrieveHTTPBody = () => {
-        //req.headers.cookie = '';
+        // req.headers.cookie = '';
         req.pipe(request(requrl))
             .on('error', (e: any) => {
                 console.log(`CORS url error: ${requrl}`, e);
@@ -227,6 +184,7 @@ function proxyServe(req: any, requrl: string, response: any) {
                 res.headers['x-permitted-cross-domain-policies'] = 'all';
                 res.headers['x-frame-options'] = '';
                 res.headers['content-security-policy'] = '';
+                // eslint-disable-next-line no-multi-assign
                 response.headers = response._headers = res.headers;
             })
             .on('end', sendModifiedBody)
@@ -236,31 +194,78 @@ function proxyServe(req: any, requrl: string, response: any) {
     retrieveHTTPBody();
 }
 
-function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
-    server.use('*', (req, res) => {
-        // res.setHeader('Access-Control-Allow-Origin', '*');
-        // res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
-        // res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
-        const relativeUrl = req.originalUrl;
-        if (!res.headersSent && req.headers.referer?.includes('corsProxy')) {
-            if (!req.user) res.redirect('/home'); // When no user is logged in, we interpret a relative URL as being a reference to something they don't have access to and redirect to /home
-            // a request for something by a proxied referrer means it must be a relative reference.  So construct a proxied absolute reference here.
-            try {
-                const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart)
-                const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ )
-                const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ''); // the url of the referer without the proxy (e.g., : https://en.wikipedia.org/wiki/Engelbart)
-                const absoluteTargetBaseUrl = actualReferUrl.match(/https?:\/\/[^\/]*/)![0]; // the base of the original url (e.g.,  https://en.wikipedia.org)
-                const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
-                const redirectUrl = relativeUrl.startsWith('//') ? 'http:' + relativeUrl : redirectedProxiedUrl;
-                res.redirect(redirectUrl);
-            } catch (e) {
-                console.log('Error embed: ', e);
-            }
-        } else if (relativeUrl.startsWith('/search') && !req.headers.referer?.includes('corsProxy')) {
-            // detect search query and use default search engine
-            res.redirect(req.headers.referer + 'corsProxy/' + encodeURIComponent('http://www.google.com' + relativeUrl));
+function registerCorsProxy(server: express.Express) {
+    server.use('/corsProxy', async (req, res) => {
+        res.setHeader('Access-Control-Allow-Origin', '*');
+        res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
+        res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
+        const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : '';
+        let requrlraw = decodeURIComponent(req.url.substring(1));
+        const qsplit = requrlraw.split('?q=');
+        const newqsplit = requrlraw.split('&q=');
+        if (qsplit.length > 1 && newqsplit.length > 1) {
+            const lastq = newqsplit[newqsplit.length - 1];
+            requrlraw = qsplit[0] + '?q=' + lastq.split('&')[0] + '&' + qsplit[1].split('&')[1];
+        }
+        const requrl = requrlraw.startsWith('/') ? referer + requrlraw : requrlraw;
+        // cors weirdness here...
+        // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative,
+        // then we redirect again to the cors referer and just add the relative path.
+        if (!requrl.startsWith('http') && req.originalUrl.startsWith('/corsProxy') && referer?.includes('corsProxy')) {
+            res.redirect(referer + (referer.endsWith('/') ? '' : '/') + requrl);
         } else {
-            res.end();
+            proxyServe(req, requrl, res);
         }
     });
 }
+
+function registerAuthenticationRoutes(server: express.Express) {
+    server.get('/signup', getSignup);
+    server.post('/signup', postSignup);
+
+    server.get('/login', getLogin);
+    server.post('/login', postLogin);
+
+    server.get('/logout', getLogout);
+
+    server.get('/forgotPassword', getForgot);
+    server.post('/forgotPassword', postForgot);
+
+    const reset = new RouteSubscriber('resetPassword').add('token').build;
+    server.get(reset, getReset);
+    server.post(reset, postReset);
+}
+
+export default async function InitializeServer(routeSetter: RouteSetter) {
+    const isRelease = determineEnvironment();
+    const app = buildWithMiddleware(express());
+    const compiler = webpack(config as any);
+
+    // route table managed by express.  routes are tested sequentially against each of these map rules.  when a match is found, the handler is called to process the request
+    app.use(wdm(compiler, { publicPath: config.output.publicPath }));
+    app.use(whm(compiler));
+    app.get(/^\/+$/, (req, res) => res.redirect(req.user ? '/home' : '/login')); // target urls that consist of one or more '/'s with nothing in between
+    app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader('Access-Control-Allow-Origin', '*') })); // all urls that start with dash's public directory: /files/  (e.g., /files/images, /files/audio, etc)
+    app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) }));
+    registerAuthenticationRoutes(app); // this adds routes to authenticate a user (login, etc)
+    registerCorsProxy(app); // this adds a /corsProxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies
+    isRelease && !SSL.Loaded && SSL.exit();
+    routeSetter(new RouteManager(app, isRelease)); // this sets up all the regular supervised routes (things like /home, download/upload api's, pdf, search, session, etc)
+    registerEmbeddedBrowseRelativePathHandler(app); // this allows renered web pages which internally have relative paths to find their content
+
+    isRelease && process.env.serverPort && (resolvedPorts.server = Number(process.env.serverPort));
+    const server = isRelease ? createServer(SSL.Credentials, app) : app;
+    await new Promise<void>(resolve => {
+        server.listen(resolvedPorts.server, resolve);
+    });
+    logPort('server', resolvedPorts.server);
+
+    resolvedServerUrl = `${isRelease && process.env.serverName ? `https://${process.env.serverName}.com` : 'http://localhost'}:${resolvedPorts.server}`;
+
+    // initialize the web socket (bidirectional communication: if a user changes
+    // a field on one client, that change must be broadcast to all other clients)
+    await WebSocket.initialize(isRelease, SSL.Credentials);
+
+    // disconnect = async () => new Promise<Error>(resolve => server.close(resolve));
+    return isRelease;
+}
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index 38134f2c1..cb16bce72 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -3,152 +3,26 @@ import { createServer } from 'https';
 import * as _ from 'lodash';
 import { networkInterfaces } from 'os';
 import { Server, Socket } from 'socket.io';
-import { Utils } from '../Utils';
+import { ServerUtils } from '../ServerUtils';
 import { logPort } from './ActionUtilities';
-import { timeMap } from './ApiManagers/UserManager';
 import { Client } from './Client';
 import { DashStats } from './DashStats';
 import { DocumentsCollection } from './IDatabase';
 import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from './Message';
 import { Search } from './Search';
+import { resolvedPorts, socketMap, timeMap, userOperations } from './SocketData';
 import { GoogleCredentialsLoader } from './apis/google/CredentialsLoader';
 import YoutubeApi from './apis/youtube/youtubeApiSample';
 import { initializeGuest } from './authentication/DashUserModel';
 import { Database } from './database';
-import { resolvedPorts } from './server_Initialization';
 
 export namespace WebSocket {
+    let CurUser: string | undefined;
+    // eslint-disable-next-line import/no-mutable-exports
     export let _socket: Socket;
+    // eslint-disable-next-line import/no-mutable-exports
+    export let _disconnect: Function;
     export const clients: { [key: string]: Client } = {};
-    export const socketMap = new Map<Socket, string>();
-    export const userOperations = new Map<string, number>();
-    export let disconnect: Function;
-
-    export async function initialize(isRelease: boolean, credentials:any) {
-        let io: Server;
-        if (isRelease) {
-            const { socketPort } = process.env;
-            if (socketPort) {
-                resolvedPorts.socket = Number(socketPort);
-            } 
-            const httpsServer = createServer(credentials);
-            io = new Server(httpsServer, {})
-            httpsServer.listen(resolvedPorts.socket);
-        } else {
-            io = new Server();
-            io.listen(resolvedPorts.socket);
-        }
-        logPort('websocket', resolvedPorts.socket);
-
-        io.on('connection', socket => {
-            _socket = socket;
-            socket.use((_packet, next) => {
-                const userEmail = socketMap.get(socket);
-                if (userEmail) {
-                    timeMap[userEmail] = Date.now();
-                }
-                next();
-            });
-
-            socket.emit(MessageStore.UpdateStats.Message, DashStats.getUpdatedStatsBundle());
-
-            // convenience function to log server messages on the client
-            function log(message?: any, ...optionalParams: any[]) {
-                socket.emit('log', ['Message from server:', message, ...optionalParams]);
-            }
-
-            socket.on('message', function (message, room) {
-                console.log('Client said: ', message);
-                socket.in(room).emit('message', message);
-            });
-
-            socket.on('create or join', function (room) {
-                console.log('Received request to create or join room ' + room);
-
-                const clientsInRoom = socket.rooms.has(room);
-                const numClients = clientsInRoom ? Object.keys(room.sockets).length : 0;
-                console.log('Room ' + room + ' now has ' + numClients + ' client(s)');
-
-                if (numClients === 0) {
-                    socket.join(room);
-                    console.log('Client ID ' + socket.id + ' created room ' + room);
-                    socket.emit('created', room, socket.id);
-                } else if (numClients === 1) {
-                    console.log('Client ID ' + socket.id + ' joined room ' + room);
-                    socket.in(room).emit('join', room);
-                    socket.join(room);
-                    socket.emit('joined', room, socket.id);
-                    socket.in(room).emit('ready');
-                } else {
-                    // max two clients
-                    socket.emit('full', room);
-                }
-            });
-
-            socket.on('ipaddr', function () {
-                const ifaces = networkInterfaces();
-                for (const dev in ifaces) {
-                    ifaces[dev]?.forEach(function (details) {
-                        if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
-                            socket.emit('ipaddr', details.address);
-                        }
-                    });
-                }
-            });
-
-            socket.on('bye', function () {
-                console.log('received bye');
-            });
-
-            socket.on('disconnect', function () {
-                let currentUser = socketMap.get(socket);
-                if (!(currentUser === undefined)) {
-                    let currentUsername = currentUser.split(' ')[0];
-                    DashStats.logUserLogout(currentUsername, socket);
-                    delete timeMap[currentUsername];
-                }
-            });
-
-            Utils.Emit(socket, MessageStore.Foo, 'handshooken');
-
-            Utils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid));
-            Utils.AddServerHandler(socket, MessageStore.SetField, args => setField(socket, args));
-            Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
-            Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
-            if (isRelease) {
-                Utils.AddServerHandler(socket, MessageStore.DeleteAll, () => doDelete(false));
-            }
-
-            Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField);
-            Utils.AddServerHandlerCallback(socket, MessageStore.YoutubeApiQuery, HandleYoutubeQuery);
-            Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));
-            Utils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id));
-            Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids));
-            Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content));
-            Utils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content));
-            Utils.AddServerHandler(socket, MessageStore.UpdateMobileInkOverlayPosition, content => processUpdateOverlayPosition(socket, content));
-            Utils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content));
-            Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
-            Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
-
-            /**
-             * Whenever we receive the go-ahead, invoke the import script and pass in
-             * as an emitter and a terminator the functions that simply broadcast a result
-             * or indicate termination to the client via the web socket
-             */
-
-            disconnect = () => {
-                socket.broadcast.emit('connection_terminated', Date.now());
-                socket.disconnect(true);
-            };
-        });
-
-        setInterval(function () {
-            // Utils.Emit(socket, MessageStore.UpdateStats, DashStats.getUpdatedStatsBundle());
-
-            io.emit(MessageStore.UpdateStats.Message, DashStats.getUpdatedStatsBundle());
-        }, DashStats.SAMPLING_INTERVAL);
-    }
 
     function processGesturePoints(socket: Socket, content: GestureContent) {
         socket.broadcast.emit('receiveGesturePoints', content);
@@ -174,8 +48,11 @@ export namespace WebSocket {
                 break;
             case YoutubeQueryTypes.SearchVideo:
                 YoutubeApi.authorizedGetVideos(ProjectCredentials, query.userInput, callback);
+                break;
             case YoutubeQueryTypes.VideoDetails:
                 YoutubeApi.authorizedGetVideoDetails(ProjectCredentials, query.videoIds, callback);
+                break;
+            default:
         }
     }
 
@@ -189,6 +66,9 @@ export namespace WebSocket {
         initializeGuest();
     }
 
+    function printActiveUsers() {
+        socketMap.forEach((user, socket) => !socket.disconnected && console.log(user));
+    }
     function barReceived(socket: Socket, userEmail: string) {
         clients[userEmail] = new Client(userEmail.toString());
         const currentdate = new Date();
@@ -203,7 +83,7 @@ export namespace WebSocket {
     }
 
     function getField([id, callback]: [string, (result?: Transferable) => void]) {
-        Database.Instance.getDocument(id, (result?: Transferable) => callback(result ? result : undefined));
+        Database.Instance.getDocument(id, (result?: Transferable) => callback(result));
     }
 
     function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) {
@@ -248,27 +128,24 @@ export namespace WebSocket {
         list: [
             '_l',
             list => {
-                const results = [];
-                for (const value of list.fields) {
-                    const term = ToSearchTerm(value);
-                    if (term) {
-                        results.push(term.value);
-                    }
-                }
+                const results: any[] = [];
+                // eslint-disable-next-line no-use-before-define
+                list.fields.forEach((value: any) => ToSearchTerm(value) && results.push(ToSearchTerm(value)!.value));
                 return results.length ? results : null;
             },
         ],
     };
 
-    function ToSearchTerm(val: any): { suffix: string; value: any } | undefined {
+    function ToSearchTerm(valIn: any): { suffix: string; value: any } | undefined {
+        let val = valIn;
         if (val === null || val === undefined) {
-            return;
+            return undefined;
         }
         const type = val.__type || typeof val;
 
         let suffix = suffixMap[type];
         if (!suffix) {
-            return;
+            return undefined;
         }
         if (Array.isArray(suffix)) {
             const accessor = suffix[1];
@@ -277,7 +154,7 @@ export namespace WebSocket {
             } else {
                 val = val[accessor];
             }
-            suffix = suffix[0];
+            [suffix] = suffix;
         }
         return { suffix, value: val };
     }
@@ -285,8 +162,28 @@ export namespace WebSocket {
     function getSuffix(value: string | [string, any]): string {
         return typeof value === 'string' ? value : value[0];
     }
+    const pendingOps = new Map<string, { diff: Diff; socket: Socket }[]>();
 
-    function addToListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
+    function dispatchNextOp(id: string) {
+        const next = pendingOps.get(id)!.shift();
+        if (next) {
+            const { diff, socket } = next;
+            if (diff.diff.$addToSet) {
+                // eslint-disable-next-line no-use-before-define
+                return GetRefFieldLocal([diff.id, (result?: Transferable) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
+            }
+            if (diff.diff.$remFromSet) {
+                // eslint-disable-next-line no-use-before-define
+                return GetRefFieldLocal([diff.id, (result?: Transferable) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
+            }
+            // eslint-disable-next-line no-use-before-define
+            return SetField(socket, diff);
+        }
+        return !pendingOps.get(id)!.length && pendingOps.delete(id);
+    }
+
+    function addToListField(socket: Socket, diffIn: Diff, curListItems?: Transferable): void {
+        const diff = diffIn;
         diff.diff.$set = diff.diff.$addToSet;
         delete diff.diff.$addToSet; // convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones
         const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
@@ -296,7 +193,7 @@ export namespace WebSocket {
             return;
         }
         const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((item: any) => item !== undefined) || [];
-        diff.diff.$set[updatefield].fields = [...curList, ...newListItems]; //, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
+        diff.diff.$set[updatefield].fields = [...curList, ...newListItems]; // , ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))];
         const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length;
         delete diff.diff.length;
         Database.Instance.update(
@@ -305,11 +202,13 @@ export namespace WebSocket {
             () => {
                 if (sendBack) {
                     console.log('Warning: list modified during update. Composite list is being returned.');
-                    const id = socket.id;
-                    (socket as any).id = '';
+                    const { id } = socket;
+                    (socket as any).id = ''; // bcz: HACK.  this prevents the update message from going back to the client that made the change.
                     socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
                     (socket as any).id = id;
-                } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
+                } else {
+                    socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
+                }
                 dispatchNextOp(diff.id);
             },
             false
@@ -352,28 +251,28 @@ export namespace WebSocket {
      * items to delete)
      * @param curListItems the server's current copy of the data
      */
-    function remFromListField(socket: Socket, diff: Diff, curListItems?: Transferable): void {
+    function remFromListField(socket: Socket, diffIn: Diff, curListItems?: Transferable): void {
+        const diff = diffIn;
         diff.diff.$set = diff.diff.$remFromSet;
         delete diff.diff.$remFromSet;
         const updatefield = Array.from(Object.keys(diff.diff.$set))[0];
         const remListItems = diff.diff.$set[updatefield].fields;
         const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((f: any) => f !== null) || [];
-        const hint = diff.diff.$set.hint;
+        const { hint } = diff.diff.$set;
 
         if (hint) {
             // indexesToRemove stores the indexes that we mark for deletion, which is later used to filter the list (delete the elements)
-            let indexesToRemove: number[] = [];
+            const indexesToRemove: number[] = [];
             for (let i = 0; i < hint.deleteCount; i++) {
                 if (curList.length > i + hint.start && _.isEqual(curList[i + hint.start], remListItems[i])) {
                     indexesToRemove.push(i + hint.start);
-                    continue;
-                }
-
-                let closestIndex = findClosestIndex(curList, indexesToRemove, remListItems[i], i + hint.start);
-                if (closestIndex !== -1) {
-                    indexesToRemove.push(closestIndex);
                 } else {
-                    console.log('Item to delete was not found - index = -1');
+                    const closestIndex = findClosestIndex(curList, indexesToRemove, remListItems[i], i + hint.start);
+                    if (closestIndex !== -1) {
+                        indexesToRemove.push(closestIndex);
+                    } else {
+                        console.log('Item to delete was not found - index = -1');
+                    }
                 }
             }
 
@@ -398,45 +297,23 @@ export namespace WebSocket {
                 if (sendBack) {
                     // the two copies are different, so the server sends its copy.
                     console.log('SEND BACK');
-                    const id = socket.id;
-                    (socket as any).id = '';
+                    const { id } = socket;
+                    (socket as any).id = ''; // bcz: HACK.  this prevents the update message from going back to the client that made the change.
                     socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
                     (socket as any).id = id;
-                } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
+                } else {
+                    socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
+                }
                 dispatchNextOp(diff.id);
             },
             false
         );
     }
 
-    const pendingOps = new Map<string, { diff: Diff; socket: Socket }[]>();
-
-    function dispatchNextOp(id: string) {
-        const next = pendingOps.get(id)!.shift();
-        if (next) {
-            const { diff, socket } = next;
-            if (diff.diff.$addToSet) {
-                return GetRefFieldLocal([diff.id, (result?: Transferable) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
-            }
-            if (diff.diff.$remFromSet) {
-                return GetRefFieldLocal([diff.id, (result?: Transferable) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
-            }
-            return GetRefFieldLocal([diff.id, (result?: Transferable) => SetField(socket, diff, result)]);
-        }
-        if (!pendingOps.get(id)!.length) pendingOps.delete(id);
-    }
-
-    function printActiveUsers() {
-        socketMap.forEach((user, socket) => {
-            !socket.disconnected && console.log(user);
-        });
-    }
-    var CurUser: string | undefined = undefined;
-
     function UpdateField(socket: Socket, diff: Diff) {
         const curUser = socketMap.get(socket);
-        if (!curUser) return;
-        let currentUsername = curUser.split(' ')[0];
+        if (!curUser) return false;
+        const currentUsername = curUser.split(' ')[0];
         userOperations.set(currentUsername, userOperations.get(currentUsername) !== undefined ? userOperations.get(currentUsername)! + 1 : 0);
 
         if (CurUser !== socketMap.get(socket)) {
@@ -454,15 +331,18 @@ export namespace WebSocket {
         if (diff.diff.$remFromSet) {
             return GetRefFieldLocal([diff.id, (result?: Transferable) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own
         }
-        return GetRefFieldLocal([diff.id, (result?: Transferable) => SetField(socket, diff, result)]);
+        // eslint-disable-next-line no-use-before-define
+        return SetField(socket, diff);
     }
-    function SetField(socket: Socket, diff: Diff, curListItems?: Transferable) {
+    function SetField(socket: Socket, diff: Diff /* , curListItems?: Transferable */) {
         Database.Instance.update(diff.id, diff.diff, () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
         const docfield = diff.diff.$set || diff.diff.$unset;
         if (docfield) {
             const update: any = { id: diff.id };
             let dynfield = false;
+            // eslint-disable-next-line no-restricted-syntax
             for (let key in docfield) {
+                // eslint-disable-next-line no-continue
                 if (!key.startsWith('fields.')) continue;
                 dynfield = true;
                 const val = docfield[key];
@@ -504,4 +384,124 @@ export namespace WebSocket {
     function CreateField(newValue: any) {
         Database.Instance.insert(newValue);
     }
+    export async function initialize(isRelease: boolean, credentials: any) {
+        let io: Server;
+        if (isRelease) {
+            const { socketPort } = process.env;
+            if (socketPort) {
+                resolvedPorts.socket = Number(socketPort);
+            }
+            const httpsServer = createServer(credentials);
+            io = new Server(httpsServer, {});
+            httpsServer.listen(resolvedPorts.socket);
+        } else {
+            io = new Server();
+            io.listen(resolvedPorts.socket);
+        }
+        logPort('websocket', resolvedPorts.socket);
+
+        io.on('connection', socket => {
+            _socket = socket;
+            socket.use((_packet, next) => {
+                const userEmail = socketMap.get(socket);
+                if (userEmail) {
+                    timeMap[userEmail] = Date.now();
+                }
+                next();
+            });
+
+            socket.emit(MessageStore.UpdateStats.Message, DashStats.getUpdatedStatsBundle());
+
+            socket.on('message', (message, room) => {
+                console.log('Client said: ', message);
+                socket.in(room).emit('message', message);
+            });
+
+            socket.on('create or join', room => {
+                console.log('Received request to create or join room ' + room);
+
+                const clientsInRoom = socket.rooms.has(room);
+                const numClients = clientsInRoom ? Object.keys(room.sockets).length : 0;
+                console.log('Room ' + room + ' now has ' + numClients + ' client(s)');
+
+                if (numClients === 0) {
+                    socket.join(room);
+                    console.log('Client ID ' + socket.id + ' created room ' + room);
+                    socket.emit('created', room, socket.id);
+                } else if (numClients === 1) {
+                    console.log('Client ID ' + socket.id + ' joined room ' + room);
+                    socket.in(room).emit('join', room);
+                    socket.join(room);
+                    socket.emit('joined', room, socket.id);
+                    socket.in(room).emit('ready');
+                } else {
+                    // max two clients
+                    socket.emit('full', room);
+                }
+            });
+
+            socket.on('ipaddr', () => {
+                const ifaces = networkInterfaces();
+                for (const dev in ifaces) {
+                    ifaces[dev]?.forEach(details => {
+                        if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
+                            socket.emit('ipaddr', details.address);
+                        }
+                    });
+                }
+            });
+
+            socket.on('bye', () => {
+                console.log('received bye');
+            });
+
+            socket.on('disconnect', () => {
+                const currentUser = socketMap.get(socket);
+                if (!(currentUser === undefined)) {
+                    const currentUsername = currentUser.split(' ')[0];
+                    DashStats.logUserLogout(currentUsername, socket);
+                    delete timeMap[currentUsername];
+                }
+            });
+
+            ServerUtils.Emit(socket, MessageStore.Foo, 'handshooken');
+
+            ServerUtils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid));
+            ServerUtils.AddServerHandler(socket, MessageStore.SetField, args => setField(socket, args));
+            ServerUtils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
+            ServerUtils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
+            if (isRelease) {
+                ServerUtils.AddServerHandler(socket, MessageStore.DeleteAll, () => doDelete(false));
+            }
+
+            ServerUtils.AddServerHandler(socket, MessageStore.CreateField, CreateField);
+            ServerUtils.AddServerHandlerCallback(socket, MessageStore.YoutubeApiQuery, HandleYoutubeQuery);
+            ServerUtils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));
+            ServerUtils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id));
+            ServerUtils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids));
+            ServerUtils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content));
+            ServerUtils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content));
+            ServerUtils.AddServerHandler(socket, MessageStore.UpdateMobileInkOverlayPosition, content => processUpdateOverlayPosition(socket, content));
+            ServerUtils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content));
+            ServerUtils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
+            ServerUtils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
+
+            /**
+             * Whenever we receive the go-ahead, invoke the import script and pass in
+             * as an emitter and a terminator the functions that simply broadcast a result
+             * or indicate termination to the client via the web socket
+             */
+
+            _disconnect = () => {
+                socket.broadcast.emit('connection_terminated', Date.now());
+                socket.disconnect(true);
+            };
+        });
+
+        setInterval(() => {
+            // Utils.Emit(socket, MessageStore.UpdateStats, DashStats.getUpdatedStatsBundle());
+
+            io.emit(MessageStore.UpdateStats.Message, DashStats.getUpdatedStatsBundle());
+        }, DashStats.SAMPLING_INTERVAL);
+    }
 }
diff --git a/tsconfig.json b/tsconfig.json
index 6cdf87d06..e8bec2744 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,7 +12,7 @@
         "allowJs": true,
         "sourceMap": true,
         "outDir": "dist",
-        "lib": ["dom", "es2017"],
+        "lib": ["DOM", "es2017"],
         "typeRoots": ["./src/typings", "node_modules/@types", "./src/extensions/General"],
         "resolveJsonModule": true,
         "moduleResolution": "node"
diff --git a/webpack.config.js b/webpack.config.js
index d92086bc2..6ddd68d97 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -2,13 +2,18 @@
 const path = require('path');
 const webpack = require('webpack');
 const HtmlWebpackPlugin = require('html-webpack-plugin');
-const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
+// eslint-disable-next-line import/no-extraneous-dependencies
+//const ESLintPlugin = require('eslint-webpack-plugin');
+// const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
 const { parsed } = require('dotenv').config();
 
 const plugins = [
     new HtmlWebpackPlugin({
         title: 'Caching',
     }),
+    // new ESLintPlugin({
+    //     extensions: ['ts'],
+    // }),
     // new ForkTsCheckerWebpackPlugin({
     //     typescript: {
     //         // useTypescriptIncrementalApi: true,
-- 
cgit v1.2.3-70-g09d2


From b1376d401e709515cee078cc08b05fd3fb89caeb Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 Apr 2024 18:12:30 -0400
Subject: completing eslint pass

---
 src/client/apis/GoogleAuthenticationManager.tsx    | 119 ++++++------
 .../apis/google_docs/GooglePhotosClientUtils.ts    |   2 +-
 src/client/documents/Documents.ts                  |  37 ++--
 src/client/util/CalendarManager.tsx                |   5 +-
 src/client/util/CaptureManager.tsx                 |  17 +-
 src/client/util/CurrentUserUtils.ts                |  24 +--
 src/client/util/DictationManager.ts                |  63 ++++---
 src/client/util/GroupMemberView.tsx                |  32 ++--
 src/client/util/History.ts                         |  34 ++--
 .../util/Import & Export/ImportMetadataEntry.tsx   |  40 ++--
 src/client/util/KeyCodes.ts                        |   2 +-
 src/client/util/LinkFollower.ts                    |   2 +-
 src/client/util/PingManager.ts                     |   6 +-
 src/client/util/RTFMarkup.tsx                      |  25 +--
 src/client/util/ReplayMovements.ts                 |  22 +--
 src/client/util/ScriptManager.ts                   |  10 +-
 src/client/util/Scripting.ts                       |   4 +-
 src/client/util/SelectionManager.ts                |  16 +-
 src/client/util/ServerStats.tsx                    |  45 +++--
 src/client/util/SharingManager.tsx                 |   5 +-
 src/client/util/TrackMovements.ts                  |  24 +--
 src/client/util/Transform.ts                       |  14 +-
 src/client/util/TypedEvent.ts                      |  18 +-
 src/client/util/bezierFit.ts                       |   6 +-
 .../util/reportManager/ReportManagerComponents.tsx | 131 +++++++------
 .../util/reportManager/reportManagerSchema.ts      |   1 +
 .../util/reportManager/reportManagerUtils.ts       |  17 +-
 src/client/views/animationtimeline/Timeline.tsx    |  60 +++---
 .../views/animationtimeline/TimelineOverview.tsx   |  16 +-
 .../views/collections/CollectionCalendarView.tsx   |  30 ++-
 .../views/collections/CollectionCarouselView.tsx   |   7 +-
 .../views/collections/CollectionDockingView.tsx    |   6 +-
 .../collections/CollectionNoteTakingViewColumn.tsx |  39 ++--
 .../CollectionNoteTakingViewDivider.tsx            |   2 +-
 .../CollectionStackingViewFieldColumn.tsx          |   6 +-
 .../views/collections/CollectionTreeView.tsx       |  21 +--
 src/client/views/collections/KeyRestrictionRow.tsx |  32 +++-
 .../CollectionFreeFormBackgroundGrid.tsx           |   1 +
 .../CollectionFreeFormInfoState.tsx                |  31 ++--
 .../CollectionFreeFormLayoutEngines.tsx            |   8 +-
 .../CollectionFreeFormRemoteCursors.tsx            |  72 ++++----
 .../collectionFreeForm/MarqueeOptionsMenu.tsx      |  12 +-
 .../collectionGrid/CollectionGridView.tsx          |  52 ++++--
 .../collectionLinear/CollectionLinearView.tsx      |   3 +-
 .../views/collections/collectionLinear/index.ts    |   2 +-
 .../CollectionMulticolumnView.tsx                  |  57 ++++--
 .../CollectionMultirowView.tsx                     |  24 ++-
 .../collectionMulticolumn/MulticolumnResizer.tsx   |  24 +--
 .../MulticolumnWidthLabel.tsx                      |   2 +-
 .../collectionMulticolumn/MultirowHeightLabel.tsx  |   3 +-
 .../collectionMulticolumn/MultirowResizer.tsx      |  26 +--
 .../collectionSchema/SchemaColumnHeader.tsx        |  18 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |  23 +--
 src/client/views/global/globalEnums.tsx            |  52 +++---
 src/client/views/linking/LinkMenuGroup.tsx         |  27 ++-
 src/client/views/linking/LinkMenuItem.tsx          |  10 +-
 .../views/linking/LinkRelationshipSearch.tsx       |  63 -------
 src/client/views/nodes/AudioBox.tsx                |  11 +-
 .../views/nodes/DataVizBox/SchemaCSVPopUp.tsx      |  62 +++----
 .../views/nodes/DataVizBox/components/PieChart.tsx | 197 ++++++++++----------
 src/client/views/nodes/DataVizBox/utils/D3Utils.ts |  29 +--
 src/client/views/nodes/DocumentLinksButton.tsx     |   6 +-
 src/client/views/nodes/EquationBox.tsx             |  11 +-
 src/client/views/nodes/FaceRectangle.tsx           |  10 +-
 .../views/nodes/FontIconBox/ButtonInterface.ts     |   2 +-
 src/client/views/nodes/FontIconBox/TrailsIcon.tsx  |  86 ++++-----
 src/client/views/nodes/LinkAnchorBox.tsx           |   2 +-
 src/client/views/nodes/LinkBox.tsx                 |  83 +++++----
 src/client/views/nodes/LinkDescriptionPopup.tsx    |  33 ++--
 src/client/views/nodes/LinkDocPreview.tsx          |  64 ++++---
 src/client/views/nodes/LoadingBox.tsx              |   4 +-
 .../views/nodes/MapBox/AnimationSpeedIcons.tsx     |  39 ++--
 src/client/views/nodes/MapBox/AnimationUtility.ts  |  57 +++---
 .../views/nodes/MapBox/DirectionsAnchorMenu.tsx    |  11 +-
 src/client/views/nodes/MapBox/GeocoderControl.tsx  |   7 +-
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx    |  99 +++++-----
 src/client/views/nodes/MapBox/MapboxApiUtility.ts  |  76 ++++----
 src/client/views/nodes/MapBox/MarkerIcons.tsx      |   3 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |  28 +--
 src/client/views/nodes/RadialMenu.tsx              | 140 +++++---------
 src/client/views/nodes/RadialMenuItem.tsx          |   4 +-
 .../views/nodes/RecordingBox/ProgressBar.tsx       | 205 +++++++++++----------
 .../views/nodes/RecordingBox/RecordingBox.tsx      |  17 +-
 .../views/nodes/RecordingBox/RecordingView.tsx     |  41 ++---
 src/client/views/nodes/RecordingBox/index.ts       |   4 +-
 src/client/views/nodes/ScriptingBox.tsx            | 162 ++++++++--------
 src/client/views/nodes/SliderBox-components.tsx    | 114 ++++++------
 src/client/views/nodes/VideoBox.tsx                |  42 +++--
 src/client/views/nodes/audio/AudioWaveform.tsx     |   8 +-
 src/client/views/nodes/audio/WaveCanvas.tsx        |  12 +-
 src/client/views/nodes/calendarBox/CalendarBox.tsx |   7 +-
 .../nodes/formattedText/DashDocCommentView.tsx     | 159 ++++++++--------
 .../views/nodes/formattedText/DashDocView.tsx      | 123 +++++++------
 .../views/nodes/formattedText/EquationEditor.tsx   |   3 +-
 .../views/nodes/formattedText/EquationView.tsx     |  93 +++++-----
 .../views/nodes/formattedText/FootnoteView.tsx     |   8 +-
 .../formattedText/FormattedTextBoxComment.tsx      |  24 ++-
 .../views/nodes/formattedText/OrderedListView.tsx  |   9 +-
 .../views/nodes/formattedText/ParagraphNodeSpec.ts | 101 +++++-----
 .../views/nodes/formattedText/RichTextRules.ts     |  15 +-
 .../views/nodes/formattedText/SummaryView.tsx      |  24 +--
 .../views/nodes/generativeFill/GenerativeFill.tsx  |  21 ++-
 .../nodes/generativeFill/GenerativeFillButtons.tsx |  10 +-
 .../GenerativeFillMathHelpers.ts                   |   8 +-
 .../generativeFillUtils/ImageHandler.ts            |  36 ++--
 .../generativeFillUtils/PointerHandler.ts          |  18 +-
 .../views/nodes/importBox/ImportElementBox.tsx     |   3 +-
 src/client/views/nodes/trails/index.ts             |   6 +-
 src/client/views/pdf/AnchorMenu.tsx                |  14 +-
 src/mobile/ImageUpload.tsx                         |   3 +-
 110 files changed, 1913 insertions(+), 1891 deletions(-)
 delete mode 100644 src/client/views/linking/LinkRelationshipSearch.tsx

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

diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index 855f48f7e..5269f763b 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -1,14 +1,14 @@
-import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Opt } from "../../fields/Doc";
-import { Networking } from "../Network";
-import { ScriptingGlobals } from "../util/ScriptingGlobals";
-import { MainViewModal } from "../views/MainViewModal";
-import "./GoogleAuthenticationManager.scss";
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Opt } from '../../fields/Doc';
+import { Networking } from '../Network';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { MainViewModal } from '../views/MainViewModal';
+import './GoogleAuthenticationManager.scss';
 
-const AuthenticationUrl = "https://accounts.google.com/o/oauth2/v2/auth";
-const prompt = "Paste authorization code here...";
+const AuthenticationUrl = 'https://accounts.google.com/o/oauth2/v2/auth';
+const prompt = 'Paste authorization code here...';
 
 @observer
 export class GoogleAuthenticationManager extends React.Component<{}> {
@@ -23,11 +23,11 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
     private disposer: Opt<IReactionDisposer>;
 
     private set isOpen(value: boolean) {
-        runInAction(() => this.openState = value);
+        runInAction(() => (this.openState = value));
     }
 
     private set shouldShowPasteTarget(value: boolean) {
-        runInAction(() => this.showPasteTargetState = value);
+        runInAction(() => (this.showPasteTargetState = value));
     }
 
     public cancel() {
@@ -35,7 +35,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
     }
 
     public fetchOrGenerateAccessToken = async (displayIfFound = false) => {
-        let response: any = await Networking.FetchFromServer("/readGoogleAccessToken");
+        let response: any = await Networking.FetchFromServer('/readGoogleAccessToken');
         // if this is an authentication url, activate the UI to register the new access token
         if (new RegExp(AuthenticationUrl).test(response)) {
             this.isOpen = true;
@@ -47,7 +47,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
                     async authenticationCode => {
                         if (authenticationCode && /\d{1}\/[\w-]{55}/.test(authenticationCode)) {
                             this.disposer?.();
-                            const response = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode });
+                            const response = await Networking.PostToServer('/writeGoogleAccessToken', { authenticationCode });
                             runInAction(() => {
                                 this.success = true;
                                 this.credentials = response;
@@ -71,7 +71,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
             this.isOpen = true;
         }
         return response.access_token;
-    }
+    };
 
     resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => {
         if (!visibleForMS && !fadesOutInMS) {
@@ -89,14 +89,20 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
         this.displayLauncher = false;
         this.shouldShowPasteTarget = false;
         if (visibleForMS > 0 && fadesOutInMS > 0) {
-            setTimeout(action(() => {
-                this.isOpen = false;
-                setTimeout(action(() => {
-                    this.success = undefined;
-                    this.displayLauncher = true;
-                    this.credentials = undefined;
-                }), fadesOutInMS);
-            }), visibleForMS);
+            setTimeout(
+                action(() => {
+                    this.isOpen = false;
+                    setTimeout(
+                        action(() => {
+                            this.success = undefined;
+                            this.displayLauncher = true;
+                            this.credentials = undefined;
+                        }),
+                        fadesOutInMS
+                    );
+                }),
+                visibleForMS
+            );
         }
     });
 
@@ -108,61 +114,44 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
     private get renderPrompt() {
         return (
             <div className={'authorize-container'}>
-
-                {this.displayLauncher ? <button
-                    className={"dispatch"}
-                    onClick={() => {
-                        window.open(this.authenticationLink);
-                        setTimeout(() => this.shouldShowPasteTarget = true, 500);
-                    }}
-                    style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}
-                >Authorize a Google account...</button> : (null)}
-                {this.showPasteTargetState ? <input
-                    className={'paste-target'}
-                    onChange={action(e => this.authenticationCode = e.currentTarget.value)}
-                    placeholder={prompt}
-                /> : (null)}
-                {this.credentials ?
+                {this.displayLauncher ? (
+                    <button
+                        className={'dispatch'}
+                        onClick={() => {
+                            window.open(this.authenticationLink);
+                            setTimeout(() => (this.shouldShowPasteTarget = true), 500);
+                        }}
+                        style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}>
+                        Authorize a Google account...
+                    </button>
+                ) : null}
+                {this.showPasteTargetState ? <input className={'paste-target'} onChange={action(e => (this.authenticationCode = e.currentTarget.value))} placeholder={prompt} /> : null}
+                {this.credentials ? (
                     <>
-                        <img
-                            className={'avatar'}
-                            src={this.credentials.userInfo.picture}
-                        />
-                        <span
-                            className={'welcome'}
-                        >Welcome to Dash, {this.credentials.userInfo.name}
-                        </span>
+                        <img className={'avatar'} src={this.credentials.userInfo.picture} />
+                        <span className={'welcome'}>Welcome to Dash, {this.credentials.userInfo.name}</span>
                         <div
                             className={'disconnect'}
                             onClick={async () => {
-                                await Networking.FetchFromServer("/revokeGoogleAccessToken");
+                                await Networking.FetchFromServer('/revokeGoogleAccessToken');
                                 this.resetState(0, 0);
-                            }}
-                        >Disconnect Account</div>
-                    </> : (null)}
+                            }}>
+                            Disconnect Account
+                        </div>
+                    </>
+                ) : null}
             </div>
         );
     }
 
     private get dialogueBoxStyle() {
-        const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
-        return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 };
+        const borderColor = this.success === undefined ? 'black' : this.success ? 'green' : 'red';
+        return { borderColor, transition: '0.2s borderColor ease', zIndex: 1002 };
     }
 
     render() {
-        return (
-            <MainViewModal
-                isDisplayed={this.openState}
-                interactive={true}
-                contents={this.renderPrompt}
-                // overlayDisplayedOpacity={0.9}
-                dialogueBoxStyle={this.dialogueBoxStyle}
-                overlayStyle={{ zIndex: 1001 }}
-                closeOnExternalClick={action(() => this.isOpen = false)}
-            />
-        );
+        return <MainViewModal isDisplayed={this.openState} interactive={true} contents={this.renderPrompt} dialogueBoxStyle={this.dialogueBoxStyle} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={action(() => (this.isOpen = false))} />;
     }
-
 }
 
-ScriptingGlobals.add("GoogleAuthenticationManager", GoogleAuthenticationManager);
\ No newline at end of file
+ScriptingGlobals.add('GoogleAuthenticationManager', GoogleAuthenticationManager);
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 07a2708ec..1eb977813 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -291,7 +291,7 @@ export namespace GooglePhotos {
                 response.results.map(
                     item =>
                         new Promise<string>(resolve => {
-                            Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl));
+                            Query.GetImage(item.mediaItem.id).then(itm => resolve(itm.baseUrl));
                         })
                 )
             );
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f827ea81c..cf397e85a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,9 +1,13 @@
+/* eslint-disable prefer-destructuring */
+/* eslint-disable default-param-last */
+/* eslint-disable no-use-before-define */
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { saveAs } from 'file-saver';
 import * as JSZip from 'jszip';
 import { action, reaction, runInAction } from 'mobx';
 import { basename } from 'path';
 import { ClientUtils, OmitKeys } from '../../ClientUtils';
+// eslint-disable-next-line import/extensions
 import * as JSZipUtils from '../../JSZipUtils';
 import { decycle } from '../../decycler/decycler';
 import { DateField } from '../../fields/DateField';
@@ -81,6 +85,7 @@ export enum FInfoFieldType {
     string = 'string',
     boolean = 'boolean',
     number = 'number',
+    // eslint-disable-next-line @typescript-eslint/no-shadow
     Doc = 'Doc',
     enumeration = 'enum',
     date = 'date',
@@ -1360,9 +1365,9 @@ export namespace DocUtils {
         if (key === LinkedTo) {
             // links are not a field value, so handled here.  value is an expression of form ([field=]idToDoc("..."))
             const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
-            const matchLink = (value: string, anchor: Doc) => {
-                const linkedToExp = (value ?? '').split('=');
-                if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
+            const matchLink = (val: string, anchor: Doc) => {
+                const linkedToExp = (val ?? '').split('=');
+                if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val;
                 return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
             };
             // prettier-ignore
@@ -1493,17 +1498,17 @@ export namespace DocUtils {
         if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
         if (target.doc === Doc.UserDoc()) return undefined;
 
-        const makeLink = action((linkDoc: Doc, showPopup?: number[]) => {
-            if (showPopup) {
+        const makeLink = action((linkDoc: Doc, showAt?: number[]) => {
+            if (showAt) {
                 LinkManager.Instance.currentLink = linkDoc;
 
                 TaskCompletionBox.textDisplayed = 'Link Created';
-                TaskCompletionBox.popupX = showPopup[0];
-                TaskCompletionBox.popupY = showPopup[1] - 33;
+                TaskCompletionBox.popupX = showAt[0];
+                TaskCompletionBox.popupY = showAt[1] - 33;
                 TaskCompletionBox.taskCompleted = true;
 
-                LinkDescriptionPopup.Instance.popupX = showPopup[0];
-                LinkDescriptionPopup.Instance.popupY = showPopup[1];
+                LinkDescriptionPopup.Instance.popupX = showAt[0];
+                LinkDescriptionPopup.Instance.popupY = showAt[1];
                 LinkDescriptionPopup.Instance.display = true;
 
                 const rect = document.body.getBoundingClientRect();
@@ -1811,7 +1816,7 @@ export namespace DocUtils {
         batch.end();
         return doc;
     }
-    export function findTemplate(templateName: string, type: string, signature: string) {
+    export function findTemplate(templateName: string, type: string) {
         let docLayoutTemplate: Opt<Doc>;
         const iconViews = DocListCast(Cast(Doc.UserDoc().template_icons, Doc, null)?.data);
         const templBtns = DocListCast(Cast(Doc.UserDoc().template_buttons, Doc, null)?.data);
@@ -1841,7 +1846,7 @@ export namespace DocUtils {
         const templateName = templateSignature.replace(/\(.*\)/, '');
         doc.layout_fieldKey = 'layout_' + (templateSignature || (docLayoutTemplate?.title ?? ''));
         // eslint-disable-next-line no-param-reassign
-        docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature);
+        docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type));
 
         const customName = 'layout_' + templateSignature;
         const _width = NumCast(doc._width);
@@ -2166,12 +2171,16 @@ export namespace DocUtils {
 
         const docs: { [id: string]: any } = {};
         const links: { [id: string]: any } = {};
-        Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1]));
-        Array.from(linkMap.entries()).forEach(l => (links[l[0]] = l[1]));
+        Array.from(map.entries()).forEach(f => {
+            docs[f[0]] = f[1];
+        });
+        Array.from(linkMap.entries()).forEach(l => {
+            links[l[0]] = l[1];
+        });
         const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer));
 
         const zip = new JSZip();
-        var count = 0;
+        let count = 0;
         const promArr = Array.from(proms)
             .filter(url => url?.startsWith('/files'))
             .map(url => url.replace('/', '')); // window.location.origin));
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 46aa4d238..8e3936d34 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -54,7 +54,6 @@ export class CalendarManager extends ObservableReactComponent<{}> {
     @observable private targetDoc: Doc | undefined = undefined; // the target document
     @observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the target doc
     @observable private dialogueBoxOpacity = 1; // for the modal
-    @observable private overlayOpacity = 0.4; // for the modal
 
     @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
 
@@ -205,14 +204,12 @@ export class CalendarManager extends ObservableReactComponent<{}> {
                     if (docs.length) {
                         docs.forEach(doc => doc && Doc.BrushDoc(doc));
                         this.dialogueBoxOpacity = 0.1;
-                        this.overlayOpacity = 0.1;
                     }
                 })}
                 onPointerLeave={action(() => {
                     if (docs.length) {
                         docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
                         this.dialogueBoxOpacity = 1;
-                        this.overlayOpacity = 0.4;
                     }
                 })}>
                 {contents}
@@ -352,6 +349,6 @@ export class CalendarManager extends ObservableReactComponent<{}> {
     }
 
     render() {
-        return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+        return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.close} />;
     }
 }
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 2939ba581..a03c80f2a 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -13,18 +15,22 @@ import { SelectionManager } from './SelectionManager';
 
 @observer
 export class CaptureManager extends React.Component<{}> {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: CaptureManager;
     static _settingsStyle = addStyleSheet();
     @observable _document: any = undefined;
     @observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not.
 
+    // eslint-disable-next-line react/sort-comp
     constructor(props: {}) {
         super(props);
         makeObservable(this);
         CaptureManager.Instance = this;
     }
 
-    public close = action(() => (this.isOpen = false));
+    public close = action(() => {
+        this.isOpen = false;
+    });
     public open = action((doc: Doc) => {
         this.isOpen = true;
         this._document = doc;
@@ -99,15 +105,15 @@ export class CaptureManager extends React.Component<{}> {
             <div className="capture-interface">
                 <div className="capture-t1">
                     <div className="recordButtonOutline" style={{}}>
-                        <div className="recordButtonInner" style={{}}></div>
+                        <div className="recordButtonInner" style={{}} />
                     </div>
                     Conversation Capture
                 </div>
-                <div className="capture-t2"></div>
+                <div className="capture-t2" />
                 {this.visibilityContent}
                 {this.linksContent}
                 <div className="close-button" onClick={this.close}>
-                    <FontAwesomeIcon icon={'times'} color="black" size={'lg'} />
+                    <FontAwesomeIcon icon="times" color="black" size="lg" />
                 </div>
                 {this.closeButtons}
             </div>
@@ -119,11 +125,10 @@ export class CaptureManager extends React.Component<{}> {
             <MainViewModal
                 contents={this.captureInterface}
                 isDisplayed={this.isOpen}
-                interactive={true}
+                interactive
                 closeOnExternalClick={this.close}
                 dialogueBoxStyle={{ width: '500px', height: '350px', border: 'none', background: 'whitesmoke' }}
                 overlayStyle={{ background: 'black' }}
-                overlayDisplayedOpacity={0.6}
             />
         );
     }
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index acbd0c0b9..65dce34a5 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -67,7 +67,7 @@ export class CurrentUserUtils {
 
     // initializes experimental advanced template views - slideView,  headerView
     static setupUserDocumentCreatorButtons(doc: Doc, userDocTemplates: Opt<Doc>) {
-        const userTemplates = DocListCast(userDocTemplates?.data).filter(doc => !Doc.IsSystem(doc));
+        const userTemplates = DocListCast(userDocTemplates?.data).filter(fdoc => !Doc.IsSystem(fdoc));
         const reqdOpts:DocumentOptions = {
             title: "User Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true, hidden: false,
             _dragOnlyWithinContainer: true, layout_hideContextMenu: true, isSystem: true, _forceActive: true,
@@ -87,7 +87,7 @@ export class CurrentUserUtils {
             { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
-            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
+            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === opts.opts.title): undefined;
             return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, allOpts),allOpts);
         });
 
@@ -110,7 +110,7 @@ export class CurrentUserUtils {
         const reqdClickList = reqdTempOpts.map(opts => {
             const title = opts.opts.title?.toString();
             const allOpts = {...reqdClickOpts, ...opts.opts};
-            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === title): undefined;
+            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === title): undefined;
             const script = ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name});
             const scriptDoc = Docs.Create.ScriptingDocument(script, allOpts, title)
             return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(scriptDoc);
@@ -130,7 +130,7 @@ export class CurrentUserUtils {
             { title: "Topic",  backgroundColor: "lightblue", icon: "book-open"  , _layout_showTitle: "title"}];
         const reqdNoteList = [...reqdTempOpts.map(opts => {
             const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true};
-            const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.title === opts.title): undefined;
+            const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(fdoc => fdoc.title === opts.title): undefined;
             return DocUtils.AssignOpts(noteTemp, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts));
         }), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))];
 
@@ -249,7 +249,7 @@ export class CurrentUserUtils {
                         Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true,  _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
                     ], {...opts, title: "Slide View Template"}));
         const plotlyApi = () => {
-            let plotly = Doc.MyPublishedDocs.find(doc => doc.title === "@plotly");
+            let plotly = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@plotly");
             if (!plotly) {
                 plotly = Docs.Create.TextDocument(
                     `await import("https://cdn.plot.ly/plotly-2.27.0.min.js");
@@ -289,7 +289,7 @@ export class CurrentUserUtils {
             return slide; 
         }
         const mermaidsApi = () => {
-            let mermaids = Doc.MyPublishedDocs.find(doc => doc.title === "@mermaids");
+            let mermaids = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@mermaids");
             if (!mermaids) {
                 mermaids = Docs.Create.TextDocument(
                 `const mdef = (await import("https://cdn.jsdelivr.net/npm/mermaid@10.8.0/dist/mermaid.esm.min.mjs")).default;
@@ -416,7 +416,7 @@ pie title Minerals in my tap water
     /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
     static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc {
         const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
-            const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
+            const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(fdoc => fdoc.title === reqdOpts.title): undefined;
             const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, 
                 _width: 60, _height: 60, _dragOnlyWithinContainer: true, 
                 btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, isSystem: true,
@@ -460,7 +460,7 @@ pie title Minerals in my tap water
         this.setupLeftSidebarPanel(doc);
         const myLeftSidebarMenu = DocCast(doc[field]);
         const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, toolTip, hidden, scripts, funcs }) => {
-            const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined;
+            const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(fdoc => fdoc.title === title) : undefined;
             const reqdBtnOpts:DocumentOptions = {
                 title, icon, target, toolTip, hidden, btnType: ButtonType.MenuButton, isSystem: true, undoIgnoreFields: new List<string>(['height', 'data_columnHeaders']), dontRegisterView: true,
                 _width: 60, _height: 60,  _dragOnlyWithinContainer: true,
@@ -614,7 +614,7 @@ pie title Minerals in my tap water
     static setupDockedButtons(doc: Doc, field="myDockedBtns") {
         const dockedBtns = DocCast(doc[field]);
         const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:string}) =>
-            DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? 
+            DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(fdoc => fdoc.title === opts.title), opts) ?? 
                             CurrentUserUtils.createToolButton(opts), scripts, funcs);
         
         const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
@@ -780,7 +780,7 @@ pie title Minerals in my tap water
             childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: !params.scripts?.onClick,
             linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc};
         
-        const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
+        const items = (menutBtn?:Doc) => !menutBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menutBtn) );
         const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList;
         const btnDoc = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title), 
                 (opts) => creator(opts, items(menuBtnDoc)), reqdSubMenuOpts, items(menuBtnDoc)), params.scripts, params.funcs);
@@ -802,7 +802,7 @@ pie title Minerals in my tap water
         Doc.UserDoc().workspaceReplayingState = undefined;
         const dockedBtns = DocCast(doc[field]);
         const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:any}) =>
-            DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? 
+            DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(fdoc => fdoc.title === opts.title), opts) ?? 
                             CurrentUserUtils.createToolButton(opts), scripts, funcs);
         
         const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
@@ -964,7 +964,7 @@ pie title Minerals in my tap water
                     case FInfoFieldType.Doc: opts.fieldValues = new List<Doc>(options.values as any); break;
                     default: opts.fieldValues = new List<string>(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType
                 }
-                DocUtils.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts);
+                DocUtils.AssignDocField(infos, pair[0], docOpts => Doc.assign(new Doc(), OmitKeys(docOpts,["values"]).omit), opts);
             }
         });
     }
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 207d3ea0b..08fd80882 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-use-before-define */
 import * as interpreter from 'words-to-numbers';
 // @ts-ignore  bcz: how are you supposed to include these definitions since dom-speech-recognition isn't a module?
 import type {} from '@types/dom-speech-recognition';
@@ -6,7 +7,7 @@ import { Doc, Opt } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
 import { listSpec } from '../../fields/Schema';
-import { Cast, CastCtor, DocCast } from '../../fields/Types';
+import { Cast, CastCtor } from '../../fields/Types';
 import { AudioField, ImageField } from '../../fields/URLField';
 import { DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
@@ -14,6 +15,7 @@ import { DictationOverlay } from '../views/DictationOverlay';
 import { DocumentView, OpenWhere } from '../views/nodes/DocumentView';
 import { SelectionManager } from './SelectionManager';
 import { UndoManager } from './UndoManager';
+import { DocData } from '../../fields/DocSymbols';
 
 /**
  * This namespace provides a singleton instance of a manager that
@@ -64,9 +66,10 @@ export namespace DictationManager {
         export let isListening = false;
         let isManuallyStopped = false;
 
-        let current: string | undefined = undefined;
+        let current: string | undefined;
         let sessionResults: string[] = [];
 
+        // eslint-disable-next-line new-cap
         const recognizer: Opt<SpeechRecognition> = webkitSpeechRecognition ? new webkitSpeechRecognition() : undefined;
 
         export type InterimResultHandler = (results: string) => any;
@@ -87,7 +90,7 @@ export namespace DictationManager {
         let pendingListen: Promise<string> | string | undefined;
 
         export const listen = async (options?: Partial<ListeningOptions>) => {
-            if (pendingListen instanceof Promise) return pendingListen.then(pl => innerListen(options));
+            if (pendingListen instanceof Promise) return pendingListen.then(() => innerListen(options));
             return innerListen(options);
         };
         const innerListen = async (options?: Partial<ListeningOptions>) => {
@@ -150,29 +153,29 @@ export namespace DictationManager {
 
             recognizer.start();
 
-            return new Promise<string>((resolve, reject) => {
+            return new Promise<string>(resolve => {
                 recognizer.onerror = (e: any) => {
                     // e is SpeechRecognitionError but where is that defined?
                     if (!(indefinite && e.error === 'no-speech')) {
                         recognizer.stop();
                         resolve(e);
-                        //reject(e);
                     }
                 };
 
                 recognizer.onresult = (e: SpeechRecognitionEvent) => {
                     current = synthesize(e, intra);
-                    let matchedTerminator: string | undefined;
-                    if (options?.terminators && (matchedTerminator = options.terminators.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false)))) {
+                    const matchedTerminator = options?.terminators?.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false));
+                    if (options?.terminators && matchedTerminator) {
                         current = matchedTerminator;
                         recognizer.abort();
                         return complete();
                     }
                     !isManuallyStopped && handler?.(current);
-                    //isManuallyStopped && complete();
+                    // isManuallyStopped && complete()
+                    return undefined;
                 };
 
-                recognizer.onend = (e: Event) => {
+                recognizer.onend = () => {
                     if (!indefinite || isManuallyStopped) {
                         return complete();
                     }
@@ -182,6 +185,7 @@ export namespace DictationManager {
                         current = undefined;
                     }
                     recognizer.start();
+                    return undefined;
                 };
 
                 const complete = () => {
@@ -202,7 +206,7 @@ export namespace DictationManager {
             });
         };
 
-        export const stop = (salvageSession = true) => {
+        export const stop = (/* salvageSession = true */) => {
             if (!isListening || !recognizer) {
                 return;
             }
@@ -212,7 +216,7 @@ export namespace DictationManager {
         };
 
         const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => {
-            const results = e.results;
+            const { results } = e;
             const transcripts: string[] = [];
             for (let i = 0; i < results.length; i++) {
                 transcripts.push(results.item(i).item(0).transcript.trim());
@@ -233,22 +237,25 @@ export namespace DictationManager {
         export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value);
         export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry);
 
-        export const execute = async (phrase: string) => {
-            return UndoManager.RunInBatch(async () => {
+        export const execute = async (phrase: string) =>
+            UndoManager.RunInBatch(async () => {
                 console.log('PHRASE: ' + phrase);
                 const targets = SelectionManager.Views;
                 if (!targets || !targets.length) {
-                    return;
+                    return undefined;
                 }
 
+                // eslint-disable-next-line no-param-reassign
                 phrase = phrase.toLowerCase();
                 const entry = Independent.get(phrase);
 
                 if (entry) {
                     let success = false;
-                    const restrictTo = entry.restrictTo;
+                    const { restrictTo } = entry;
+                    // eslint-disable-next-line no-restricted-syntax
                     for (const target of targets) {
                         if (!restrictTo || validate(target, restrictTo)) {
+                            // eslint-disable-next-line no-await-in-loop
                             await entry.action(target);
                             success = true;
                         }
@@ -256,16 +263,19 @@ export namespace DictationManager {
                     return success;
                 }
 
-                for (const entry of Dependent) {
-                    const regex = entry.expression;
+                // eslint-disable-next-line no-restricted-syntax
+                for (const depEntry of Dependent) {
+                    const regex = depEntry.expression;
                     const matches = regex.exec(phrase);
                     regex.lastIndex = 0;
                     if (matches !== null) {
                         let success = false;
-                        const restrictTo = entry.restrictTo;
+                        const { restrictTo } = depEntry;
+                        // eslint-disable-next-line no-restricted-syntax
                         for (const target of targets) {
                             if (!restrictTo || validate(target, restrictTo)) {
-                                await entry.action(target, matches);
+                                // eslint-disable-next-line no-await-in-loop
+                                await depEntry.action(target, matches);
                                 success = true;
                             }
                         }
@@ -275,7 +285,6 @@ export namespace DictationManager {
 
                 return false;
             }, 'Execute Command');
-        };
 
         const ConstructorMap = new Map<DocumentType, CastCtor>([
             [DocumentType.COL, listSpec(Doc)],
@@ -294,6 +303,7 @@ export namespace DictationManager {
         };
 
         const validate = (target: DocumentView, types: DocumentType[]) => {
+            // eslint-disable-next-line no-restricted-syntax
             for (const type of types) {
                 if (tryCast(target, type)) {
                     return true;
@@ -318,7 +328,9 @@ export namespace DictationManager {
             [
                 'clear',
                 {
-                    action: (target: DocumentView) => (Doc.GetProto(target.Document).data = new List()),
+                    action: (target: DocumentView) => {
+                        Doc.GetProto(target.Document).data = new List();
+                    },
                     restrictTo: [DocumentType.COL],
                 },
             ],
@@ -328,7 +340,7 @@ export namespace DictationManager {
                 {
                     action: (target: DocumentView) => {
                         const newBox = Docs.Create.TextDocument('', { _width: 400, _height: 200, title: 'My Outline', _layout_autoHeight: true });
-                        const proto = DocCast(newBox.proto);
+                        const proto = newBox[DocData];
                         const prompt = 'Press alt + r to start dictating here...';
                         const head = 3;
                         const anchor = head + prompt.length;
@@ -341,7 +353,7 @@ export namespace DictationManager {
             ],
         ]);
 
-        const Dependent = new Array<DependentEntry>(
+        const Dependent = [
             {
                 expression: /create (\w+) documents of type (image|nested collection)/g,
                 action: (target: DocumentView, matches: RegExpExecArray) => {
@@ -361,6 +373,7 @@ export namespace DictationManager {
                             case 'nested collection':
                                 created = Docs.Create.FreeformDocument([], {});
                                 break;
+                            default:
                         }
                         created && Doc.AddDocToList(dataDoc, fieldKey, created);
                     }
@@ -375,7 +388,7 @@ export namespace DictationManager {
                     mode && (target.Document._type_collection = mode);
                 },
                 restrictTo: [DocumentType.COL],
-            }
-        );
+            },
+        ];
     }
 }
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 894583711..c703c3f98 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -1,4 +1,7 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button, IconButton, Size, Type } from 'browndash-components';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -8,7 +11,6 @@ import { StrCast } from '../../fields/Types';
 import { MainViewModal } from '../views/MainViewModal';
 import { GroupManager, UserOptions } from './GroupManager';
 import './GroupMemberView.scss';
-import { Button, IconButton, Size, Type } from 'browndash-components';
 import { SettingsManager } from './SettingsManager';
 
 interface GroupMemberViewProps {
@@ -38,20 +40,23 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
                         className="group-title"
                         style={{ marginLeft: !hasEditAccess ? '-14%' : 0 }}
                         value={StrCast(this.group.title || this.group.groupName)}
-                        onChange={e => (this.group.title = e.currentTarget.value)}
-                        disabled={!hasEditAccess}></input>
-                    <div className={'memberView-closeButton'}>
-                        <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} />
+                        onChange={e => {
+                            this.group.title = e.currentTarget.value;
+                        }}
+                        disabled={!hasEditAccess}
+                    />
+                    <div className="memberView-closeButton">
+                        <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} />
                     </div>
                     {GroupManager.Instance.hasEditAccess(this.group) ? (
                         <div className="group-buttons">
                             <div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
                                 <Select
                                     className="add-member-dropdown"
-                                    isSearchable={true}
+                                    isSearchable
                                     options={options}
                                     onChange={selectedOption => GroupManager.Instance.addMemberToGroup(this.group, (selectedOption as UserOptions).value)}
-                                    placeholder={'Add members'}
+                                    placeholder="Add members"
                                     value={null}
                                     styles={{
                                         control: () => ({
@@ -80,7 +85,12 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
                             </div>
                         </div>
                     ) : null}
-                    <div className="sort-emails" style={{ paddingTop: hasEditAccess ? 0 : 35 }} onClick={action(() => (this.memberSort = this.memberSort === 'ascending' ? 'descending' : this.memberSort === 'descending' ? 'none' : 'ascending'))}>
+                    <div
+                        className="sort-emails"
+                        style={{ paddingTop: hasEditAccess ? 0 : 35 }}
+                        onClick={action(() => {
+                            this.memberSort = this.memberSort === 'ascending' ? 'descending' : this.memberSort === 'descending' ? 'none' : 'ascending';
+                        })}>
                         Emails {this.memberSort === 'ascending' ? '↑' : this.memberSort === 'descending' ? '↓' : ''} {/* → */}
                     </div>
                 </div>
@@ -90,8 +100,8 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
                         <div className="editing-row" key={member}>
                             <div className="user-email">{member}</div>
                             {hasEditAccess ? (
-                                <div className={'remove-button'} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}>
-                                    <IconButton icon={<FontAwesomeIcon icon={'trash-alt'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} />
+                                <div className="remove-button" onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}>
+                                    <IconButton icon={<FontAwesomeIcon icon="trash-alt" />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} />
                                 </div>
                             ) : null}
                         </div>
@@ -102,6 +112,6 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
     }
 
     render() {
-        return <MainViewModal isDisplayed={true} interactive={true} contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />;
+        return <MainViewModal isDisplayed interactive contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />;
     }
 }
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index b500a5af9..52d0223d5 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,3 +1,9 @@
+/* eslint-disable no-use-before-define */
+/* eslint-disable no-empty */
+/* eslint-disable no-continue */
+/* eslint-disable guard-for-in */
+/* eslint-disable no-restricted-syntax */
+/* eslint-disable no-param-reassign */
 import * as qs from 'query-string';
 import { Doc } from '../../fields/Doc';
 import { OmitKeys, ClientUtils } from '../../ClientUtils';
@@ -32,6 +38,7 @@ export namespace HistoryUtil {
                     case 'doc':
                         onDocUrl(url);
                         break;
+                    default:
                 }
             }
         }
@@ -124,11 +131,11 @@ export namespace HistoryUtil {
                 const val = customParser(pathname, opts, current);
                 if (val === null) {
                     return undefined;
-                } else if (val === undefined) {
+                }
+                if (val === undefined) {
                     return current;
-                } else {
-                    return val;
                 }
+                return val;
             }
             return current;
         };
@@ -142,26 +149,27 @@ export namespace HistoryUtil {
             }
             const queryObj = OmitKeys(state, keys).extract;
             const query: any = {};
-            Object.keys(queryObj).forEach(key => (query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key])));
+            Object.keys(queryObj).forEach(key => {
+                query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key]);
+            });
             const queryString = qs.stringify(query);
             return path + (queryString ? `?${queryString}` : '');
         };
     }
 
     addParser('doc', {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => {
-        if (pathname.length !== 2) return undefined;
-
-        current.initializers = current.initializers || {};
-        const docId = pathname[1];
-        current.docId = docId;
-    });
-    addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => {
-        return `${current}/${state.docId}`;
+        if (pathname.length === 2) {
+            current.initializers = current.initializers || {};
+            const docId = pathname[1];
+            current.docId = docId;
+        }
+        return undefined;
     });
+    addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => `${current}/${state.docId}`);
 
     export function parseUrl(location: Location | URL): ParsedUrl | undefined {
         const pathname = location.pathname.substring(1);
-        const search = location.search;
+        const { search } = location;
         const opts = search.length ? qs.parse(search, { sort: false }) : {};
         const pathnameSplit = pathname.split('/');
 
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
index 58a09b9c9..db1e3d6cd 100644
--- a/src/client/util/Import & Export/ImportMetadataEntry.tsx	
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx	
@@ -1,10 +1,13 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { EditableView } from '../../views/EditableView';
-import { action, computed } from 'mobx';
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable no-use-before-define */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../../fields/Doc';
-import { StrCast, BoolCast } from '../../../fields/Types';
+import { BoolCast, StrCast } from '../../../fields/Types';
+import { EditableView } from '../../views/EditableView';
 
 interface KeyValueProps {
     Document: Doc;
@@ -93,18 +96,27 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
                     alignItems: 'center',
                     alignContent: 'center',
                 }}>
-                <input onChange={e => (this.onDataDoc = e.target.checked)} ref={this.checkRef} style={{ margin: '0 10px 0 15px' }} type="checkbox" title={'Add to Data Document?'} checked={this.onDataDoc} />
-                <div className={'key_container'} style={keyValueStyle}>
-                    <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine={true} />
+                <input
+                    onChange={e => {
+                        this.onDataDoc = e.target.checked;
+                    }}
+                    ref={this.checkRef}
+                    style={{ margin: '0 10px 0 15px' }}
+                    type="checkbox"
+                    title="Add to Data Document?"
+                    checked={this.onDataDoc}
+                />
+                <div className="key_container" style={keyValueStyle}>
+                    <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine />
                 </div>
-                <div className={'value_container'} style={keyValueStyle}>
-                    <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine={true} />
+                <div className="value_container" style={keyValueStyle}>
+                    <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine />
                 </div>
-                <div onClick={() => this.props.remove(this)} title={'Delete Entry'}>
+                <div onClick={() => this.props.remove(this)} title="Delete Entry">
                     <FontAwesomeIcon
-                        icon={'plus'}
-                        color={'red'}
-                        size={'1x'}
+                        icon="plus"
+                        color="red"
+                        size="1x"
                         style={{
                             marginLeft: 15,
                             marginRight: 15,
diff --git a/src/client/util/KeyCodes.ts b/src/client/util/KeyCodes.ts
index de2457a5a..fb21f78ee 100644
--- a/src/client/util/KeyCodes.ts
+++ b/src/client/util/KeyCodes.ts
@@ -133,4 +133,4 @@ export class KeyCodes {
     public static NUM_9: number = 57;
     public static SUBTRACT: number = 189;
     public static ADD: number = 187;
-}
\ No newline at end of file
+}
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 85bada8c9..09262f5ac 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -44,7 +44,7 @@ export class LinkFollower {
 
     public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) {
         const getView = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc));
-        const isAnchor = (sourceDoc: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, sourceDoc) || Doc.AreProtosEqual(anchor.annotationOn as Doc, sourceDoc);
+        const isAnchor = (source: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(anchor.annotationOn as Doc, source);
         const linkDocs = link ? [link] : LinkManager.Links(sourceDoc);
         const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_1 as Doc)); // link docs where 'sourceDoc' is link_anchor_1
         const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_2 as Doc)); // link docs where 'sourceDoc' is link_anchor_2
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index 7638e2ce0..e5e69c5ac 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -1,8 +1,10 @@
 import { action, makeObservable, observable, runInAction } from 'mobx';
 import { Networking } from '../Network';
 import { CurrentUserUtils } from './CurrentUserUtils';
+
 export class PingManager {
     // create static instance and getter for global use
+    // eslint-disable-next-line no-use-before-define
     @observable static _instance: PingManager;
     @observable IsBeating = true;
     static get Instance(): PingManager {
@@ -29,7 +31,9 @@ export class PingManager {
     sendPing = async (): Promise<void> => {
         try {
             const res = await Networking.PostToServer('/ping', { date: new Date() });
-            runInAction(() => (CurrentUserUtils.ServerVersion = res.message));
+            runInAction(() => {
+                CurrentUserUtils.ServerVersion = res.message;
+            });
             !this.IsBeating && this.setIsBeating(true);
         } catch {
             if (this.IsBeating) {
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 35b1579df..05fb849fd 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -6,23 +6,16 @@ import { SettingsManager } from './SettingsManager';
 
 @observer
 export class RTFMarkup extends React.Component<{}> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: RTFMarkup;
     @observable private isOpen = false; // whether the SharingManager modal is open or not
 
-    @action
-    public open = () => (this.isOpen = true);
-
-    @action
-    public close = () => (this.isOpen = false);
-
     constructor(props: {}) {
         super(props);
         makeObservable(this);
         RTFMarkup.Instance = this;
     }
 
-    @observable _stats: { [key: string]: any } | undefined = undefined;
-
     /**
      * @returns the main interface of the SharingManager.
      */
@@ -30,11 +23,11 @@ export class RTFMarkup extends React.Component<{}> {
         return (
             <div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`(@wiki:phrase)`}</b>
+                    <b style={{ fontSize: 'larger' }}>(@wiki:phrase)</b>
                     {` display wikipedia page for entered text (terminate with carriage return)`}
                 </p>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`(( any text ))`}</b>
+                    <b style={{ fontSize: 'larger' }}>(( any text ))</b>
                     {` submit text to Chat GPT to have results appended afterward`}
                 </p>
                 <p>
@@ -129,13 +122,23 @@ export class RTFMarkup extends React.Component<{}> {
         );
     }
 
+    @action
+    public open = () => {
+        this.isOpen = true;
+    };
+
+    @action
+    public close = () => {
+        this.isOpen = false;
+    };
+
     render() {
         return (
             <MainViewModal
                 dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, alignContent: 'normal', color: SettingsManager.userColor, padding: '16px' }}
                 contents={this.cheatSheet}
                 isDisplayed={this.isOpen}
-                interactive={true}
+                interactive
                 closeOnExternalClick={this.close}
             />
         );
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index b881f18b4..2c8fdf483 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -14,6 +14,7 @@ export class ReplayMovements {
     private isPlaying: boolean;
 
     // create static instance and getter for global use
+    // eslint-disable-next-line no-use-before-define
     @observable static _instance: ReplayMovements;
     static get Instance(): ReplayMovements {
         return ReplayMovements._instance;
@@ -90,7 +91,7 @@ export class ReplayMovements {
     loadPresentation = (presentation: Presentation) => {
         const { movements } = presentation;
         if (movements === null) {
-            throw '[recordingApi.ts] followMovements() failed: no presentation data';
+            throw new Error('[recordingApi.ts] followMovements() failed: no presentation data');
         }
 
         movements.forEach((movement, i) => {
@@ -106,9 +107,7 @@ export class ReplayMovements {
     // returns undefined if the docView isn't open on the screen
     getCollectionFFView = (doc: Doc) => {
         const isInView = DocumentManager.Instance.getDocumentView(doc);
-        if (isInView) {
-            return isInView.ComponentView as CollectionFreeFormView;
-        }
+        return isInView?.ComponentView as CollectionFreeFormView;
     };
 
     // will open the doc in a tab then return the CollectionFFView that holds it
@@ -136,9 +135,9 @@ export class ReplayMovements {
         if (movements === null) return new Map();
         // generate a set of all unique docIds
         const docIdtoFirstMove = new Map<Doc, Movement>();
-        for (const move of movements) {
+        movements.forEach(move => {
             if (!docIdtoFirstMove.has(move.doc)) docIdtoFirstMove.set(move.doc, move);
-        }
+        });
         return docIdtoFirstMove;
     };
 
@@ -151,10 +150,10 @@ export class ReplayMovements {
         // console.info('playMovements', presentation, timeViewed, docIdtoDoc);
 
         if (presentation.movements === null || presentation.movements.length === 0) {
-            //|| this.playFFView === null) {
-            return new Error('[recordingApi.ts] followMovements() failed: no presentation data');
+            // || this.playFFView === null) {
+            return '[recordingApi.ts] followMovements() failed: no presentation data';
         }
-        if (this.isPlaying) return;
+        if (this.isPlaying) return undefined;
 
         this.isPlaying = true;
         Doc.UserDoc().presentationMode = 'watching';
@@ -170,10 +169,10 @@ export class ReplayMovements {
 
             // for the open tabs, set it to the first move
             const docIdtoFirstMove = this.getFirstMovements(filteredMovements);
-            for (const [doc, firstMove] of docIdtoFirstMove) {
+            Array.from(docIdtoFirstMove).forEach(([doc, firstMove]) => {
                 const colFFView = this.getCollectionFFView(doc);
                 if (colFFView) this.zoomAndPan(firstMove, colFFView);
-            }
+            });
         };
         handleFirstMovements();
 
@@ -197,5 +196,6 @@ export class ReplayMovements {
                 }
             }, timeDiff);
         });
+        return undefined;
     };
 }
diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts
index 87509f2ea..9158f6c0b 100644
--- a/src/client/util/ScriptManager.ts
+++ b/src/client/util/ScriptManager.ts
@@ -7,8 +7,10 @@ import { ScriptingGlobals } from './ScriptingGlobals';
 
 export class ScriptManager {
     static _initialized = false;
+    // eslint-disable-next-line no-use-before-define
     private static _instance: ScriptManager;
     public static get Instance(): ScriptManager {
+        // eslint-disable-next-line no-return-assign
         return this._instance || (this._instance = new this());
     }
     private constructor() {
@@ -58,14 +60,16 @@ export class ScriptManager {
 
         const params = Cast(scriptDoc['data-params'], listSpec('string'), []);
         const paramNames = params.reduce((o: string, p: string) => {
+            let out = o;
             if (params.indexOf(p) === params.length - 1) {
-                o = o + p.split(':')[0].trim();
+                out += p.split(':')[0].trim();
             } else {
-                o = o + p.split(':')[0].trim() + ',';
+                out += p.split(':')[0].trim() + ',';
             }
-            return o;
+            return out;
         }, '' as string);
 
+        // eslint-disable-next-line no-new-func
         const f = new Function(paramNames, StrCast(scriptDoc.script));
 
         Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false });
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index de5e8b92e..f7d7ba6a4 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -124,7 +124,7 @@ class ScriptingCompilerHost {
         return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
     }
     writeFile(fileName: string, content: string) {
-        const file = this.files.find(file => file.fileName === fileName);
+        const file = this.files.find(f => f.fileName === fileName);
         if (file) {
             file.content = content;
         } else {
@@ -147,7 +147,7 @@ class ScriptingCompilerHost {
         return this.files.some(file => file.fileName === fileName);
     }
     readFile(fileName: string): string | undefined {
-        const file = this.files.find(file => file.fileName === fileName);
+        const file = this.files.find(f => f.fileName === fileName);
         if (file) {
             return file.content;
         }
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 36b926053..1328e90e9 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -11,6 +11,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
 import { UndoManager } from './UndoManager';
 
 export class SelectionManager {
+    // eslint-disable-next-line no-use-before-define
     private static _manager: SelectionManager;
     private static get Instance() {
         return SelectionManager._manager ?? new SelectionManager();
@@ -61,7 +62,9 @@ export class SelectionManager {
             dv.IsSelected = false;
             dv._props.whenChildContentsActiveChanged(false);
         });
-        runInAction(() => (this.Instance.SelectedViews.length = 0));
+        runInAction(() => {
+            this.Instance.SelectedViews.length = 0;
+        });
         if (found) this.SelectView(found, false);
     };
 
@@ -71,17 +74,20 @@ export class SelectionManager {
     public static get Docs() { return this.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore
 }
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
     if (Doc.noviceMode && expertMode) return false;
     if (type === 'tab') {
         return SelectionManager.Views.lastElement()?._props.renderDepth === 0;
     }
-    let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement());
+    const selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement());
     return selected?.type === type || selected?.type_collection === type || !type;
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function deselectAll() {
     SelectionManager.DeselectAll();
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function undo() {
     SelectionManager.DeselectAll();
     return UndoManager.Undo();
@@ -89,17 +95,19 @@ ScriptingGlobals.add(function undo() {
 
 export function ShowUndoStack() {
     SelectionManager.DeselectAll();
-    var buffer = '';
+    let buffer = '';
     UndoManager.undoStack.forEach((batch, i) => {
         buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n';
-        ///batch.forEach(undo => (buffer += '   ' + undo.prop + '\n'));
+        // /batch.forEach(undo => (buffer += '   ' + undo.prop + '\n'));
     });
     alert(buffer);
 }
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function redo() {
     SelectionManager.DeselectAll();
     return UndoManager.Redo();
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
     const docs = SelectionManager.Views.map(dv => dv.Document).filter(
         d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index c8df9182d..891561245 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -1,44 +1,28 @@
-import { action, computed, makeObservable, observable } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { MainViewModal } from '../views/MainViewModal';
 import './SharingManager.scss';
 import { PingManager } from './PingManager';
-import { StrCast } from '../../fields/Types';
-import { Doc } from '../../fields/Doc';
 import { SettingsManager } from './SettingsManager';
 
 @observer
 export class ServerStats extends React.Component<{}> {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: ServerStats;
     @observable private isOpen = false; // whether the SharingManager modal is open or not
 
+    @observable _stats: { [key: string]: any } | undefined = undefined;
     // private get linkVisible() {
     //     return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
     // }
 
-    @action
-    public open = async () => {
-        /**
-         * Populates the list of users.
-         */
-        fetch('/stats').then((res: Response) => res.text().then(action(stats => (this._stats = JSON.parse(stats)))));
-
-        this.isOpen = true;
-    };
-
-    public close = action(() => {
-        this.isOpen = false;
-    });
-
     constructor(props: {}) {
         super(props);
         makeObservable(this);
         ServerStats.Instance = this;
     }
 
-    @observable _stats: { [key: string]: any } | undefined = undefined;
-
     /**
      * @returns the main interface of the SharingManager.
      */
@@ -63,7 +47,28 @@ export class ServerStats extends React.Component<{}> {
         );
     }
 
+    // eslint-disable-next-line react/sort-comp
+    public close = action(() => {
+        this.isOpen = false;
+    });
+    public open = async () => {
+        /**
+         * Populates the list of users.
+         */
+        fetch('/stats').then((res: Response) =>
+            res.text().then(
+                action(stats => {
+                    this._stats = JSON.parse(stats);
+                })
+            )
+        );
+
+        runInAction(() => {
+            this.isOpen = true;
+        });
+    };
+
     render() {
-        return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} />;
+        return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive closeOnExternalClick={this.close} />;
     }
 }
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 6676e4e03..5b4ac5aff 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -78,7 +78,6 @@ export class SharingManager extends React.Component<{}> {
     @observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the document being shared
     // @observable private copied = false;
     @observable private dialogueBoxOpacity = 1; // for the modal
-    @observable private overlayOpacity = 0.4; // for the modal
     @observable private selectedUsers: UserOptions[] | null = null; // users (individuals/groups) selected to share with
     @observable private permissions: SharingPermissions = SharingPermissions.Edit; // the permission with which to share with other users
     @observable private individualSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of individuals
@@ -661,14 +660,12 @@ export class SharingManager extends React.Component<{}> {
                     if (docs.length) {
                         docs.forEach(doc => doc && Doc.BrushDoc(doc));
                         this.dialogueBoxOpacity = 0.1;
-                        this.overlayOpacity = 0.1;
                     }
                 })}
                 onPointerLeave={action(() => {
                     if (docs.length) {
                         docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
                         this.dialogueBoxOpacity = 1;
-                        this.overlayOpacity = 0.4;
                     }
                 })}>
                 {contents}
@@ -742,6 +739,6 @@ export class SharingManager extends React.Component<{}> {
     }
 
     render() {
-        return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+        return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.close} />;
     }
 }
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index f9c2d522f..25a3c9ad8 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -1,8 +1,7 @@
-import { IReactionDisposer, makeObservable, observable, observe, reaction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { NumCast } from '../../fields/Types';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { Id } from '../../fields/FieldSymbols';
 import { CollectionViewType } from '../documents/DocumentTypes';
 
 export type Movement = {
@@ -33,6 +32,7 @@ export class TrackMovements {
     private tabChangeDisposeFunc: IReactionDisposer | null;
 
     // create static instance and getter for global use
+    // eslint-disable-next-line no-use-before-define
     @observable static _instance: TrackMovements;
     static get Instance(): TrackMovements {
         return TrackMovements._instance;
@@ -92,12 +92,13 @@ export class TrackMovements {
         // so that the size comparisons are correct, we must filter to only the FFViews
         const isFFView = (doc: Doc) => doc && doc._type_collection === CollectionViewType.Freeform;
         const tabbedFFViews = new Set<Doc>();
-        for (const DashDoc of tabbedDocs) {
+        tabbedDocs.forEach(DashDoc => {
             if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc);
-        }
+        });
 
         // new tab was added - need to add it
         if (tabbedFFViews.size > this.recordingFFViews.size) {
+            // eslint-disable-next-line no-restricted-syntax
             for (const DashDoc of tabbedDocs) {
                 if (!this.recordingFFViews.has(DashDoc)) {
                     if (isFFView(DashDoc)) {
@@ -111,6 +112,7 @@ export class TrackMovements {
         }
         // tab was removed - need to remove it from recordingFFViews
         else if (tabbedFFViews.size < this.recordingFFViews.size) {
+            // eslint-disable-next-line no-restricted-syntax
             for (const [doc] of this.recordingFFViews) {
                 if (!tabbedFFViews.has(doc)) {
                     this.removeRecordingFFView(doc);
@@ -208,11 +210,11 @@ export class TrackMovements {
             return;
         }
 
-        for (const [id, disposeFunc] of this.recordingFFViews) {
+        Array.from(this.recordingFFViews).forEach(([id, disposeFunc]) => {
             // console.info('calling dispose func : docId', id);
             disposeFunc();
-            this.recordingFFViews.delete(id);
-        }
+            this.recordingFFViews?.delete(id);
+        });
     };
 
     private trackMovement = (panX: number, panY: number, doc: Doc, scale: number = 0) => {
@@ -241,9 +243,9 @@ export class TrackMovements {
     // method that concatenates an array of presentatations into one
     public concatPresentations = (presentations: Presentation[]): Presentation => {
         // these three will lead to the combined presentation
-        let combinedMovements: Movement[] = [];
+        const combinedMovements: Movement[] = [];
         let sumTime = 0;
-        let combinedMetas: any[] = [];
+        const combinedMetas: any[] = [];
 
         presentations.forEach(presentation => {
             const { movements, totalTime, meta } = presentation;
@@ -251,9 +253,7 @@ export class TrackMovements {
             // update movements if they had one
             if (movements) {
                 // add the summed time to the movements
-                const addedTimeMovements = movements.map(move => {
-                    return { ...move, time: move.time + sumTime };
-                });
+                const addedTimeMovements = movements.map(move => ({ ...move, time: move.time + sumTime }));
                 // concat the movements already in the combined presentation with these new ones
                 combinedMovements.push(...addedTimeMovements);
             }
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index dca37c960..1a07dd6ae 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -116,20 +116,14 @@ export class Transform {
 
     preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform);
 
-    transformPoint = (x: number, y: number): [number, number] => {
-        x *= this._scale;
-        x += this._translateX;
-        y *= this._scale;
-        y += this._translateY;
-        return [x, y];
-    };
+    transformPoint = (x: number, y: number): [number, number] => [x * this._scale + this._translateX, y * this._scale + this._translateY];
 
     transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale];
 
     transformBounds(x: number, y: number, width: number, height: number): { x: number; y: number; width: number; height: number } {
-        [x, y] = this.transformPoint(x, y);
-        [width, height] = this.transformDirection(width, height);
-        return { x, y, width, height };
+        const [tx, ty] = this.transformPoint(x, y);
+        const [twidth, theight] = this.transformDirection(width, height);
+        return { x: tx, y: ty, width: twidth, height: theight };
     }
 
     inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale, -this._rotate);
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
index 90fd299c1..9ef2aa8d7 100644
--- a/src/client/util/TypedEvent.ts
+++ b/src/client/util/TypedEvent.ts
@@ -14,27 +14,27 @@ export class TypedEvent<T> {
     on = (listener: Listener<T>): Disposable => {
         this.listeners.push(listener);
         return {
-            dispose: () => this.off(listener)
+            dispose: () => this.off(listener),
         };
-    }
+    };
 
     once = (listener: Listener<T>): void => {
         this.listenersOncer.push(listener);
-    }
+    };
 
     off = (listener: Listener<T>) => {
         const callbackIndex = this.listeners.indexOf(listener);
         if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
-    }
+    };
 
     emit = (event: T) => {
         /** Update any general listeners */
-        this.listeners.forEach((listener) => listener(event));
+        this.listeners.forEach(listener => listener(event));
 
         /** Clear the `once` queue */
-        this.listenersOncer.forEach((listener) => listener(event));
+        this.listenersOncer.forEach(listener => listener(event));
         this.listenersOncer = [];
-    }
+    };
 
-    pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e));
-}
\ No newline at end of file
+    pipe = (te: TypedEvent<T>): Disposable => this.on(e => te.emit(e));
+}
diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index 6bbf55e5a..d6f3f2340 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-use-before-define */
 /* eslint-disable prefer-destructuring */
 /* eslint-disable no-param-reassign */
 /* eslint-disable camelcase */
@@ -242,6 +243,7 @@ function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
  * is robust: a near-tangential intersection will yield zero or two
  * intersections.
  */
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
 function recursively_intersect(a: Point[], t0: number, t1: number, deptha: number, b: Point[], u0: number, u1: number, depthb: number, parameters: number[][]) {
     if (deptha > 0) {
         const a1 = new Array<Point>(4);
@@ -538,8 +540,8 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
         for (let i = 0; i < maxIterations; i++) {
             const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve); //  Improved parameter values
             GenerateBezier(d, first, last, uPrime, tHat1, tHat2, bezCurve);
-            const { maxError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
-            if (maxError < error) {
+            const { maxError: maximumError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
+            if (maximumError < error) {
                 result.push(bezCurve[1]);
                 result.push(bezCurve[2]);
                 result.push(bezCurve[3]);
diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx
index 1e226bf6d..cecebc648 100644
--- a/src/client/util/reportManager/ReportManagerComponents.tsx
+++ b/src/client/util/reportManager/ReportManagerComponents.tsx
@@ -1,9 +1,15 @@
+/* eslint-disable react/require-default-props */
+/* eslint-disable prefer-destructuring */
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable no-use-before-define */
 import * as React from 'react';
-import { Issue } from './reportManagerSchema';
-import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils';
 import ReactMarkdown from 'react-markdown';
 import rehypeRaw from 'rehype-raw';
 import remarkGfm from 'remark-gfm';
+import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils';
+import { Issue } from './reportManagerSchema';
 import { StrCast } from '../../../fields/Types';
 import { Doc } from '../../../fields/Doc';
 
@@ -18,7 +24,7 @@ interface FilterProps<T> {
 }
 
 // filter ui for issues (horizontal list of tags)
-export const Filter = <T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) => {
+export function Filter<T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) {
     // establishing theme
     const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
     const colors = darkMode ? darkColors : lightColors;
@@ -28,7 +34,7 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }:
     return (
         <div className="issues-filter">
             <Tag
-                text={'All'}
+                text="All"
                 onClick={() => {
                     setActiveValue(null);
                 }}
@@ -38,25 +44,23 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }:
                 borderColor={activeValue === null ? StrCast(Doc.UserDoc().userColor) : colors.border}
                 border
             />
-            {items.map(item => {
-                return (
-                    <Tag
-                        key={item}
-                        text={item}
-                        onClick={() => {
-                            setActiveValue(item);
-                        }}
-                        fontSize="12px"
-                        backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'}
-                        color={activeValue === item ? activeTagTextColor : colors.textGrey}
-                        border
-                        borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border}
-                    />
-                );
-            })}
+            {items.map(item => (
+                <Tag
+                    key={item}
+                    text={item}
+                    onClick={() => {
+                        setActiveValue(item);
+                    }}
+                    fontSize="12px"
+                    backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'}
+                    color={activeValue === item ? activeTagTextColor : colors.textGrey}
+                    border
+                    borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border}
+                />
+            ))}
         </div>
     );
-};
+}
 
 interface IssueCardProps {
     issue: Issue;
@@ -64,7 +68,7 @@ interface IssueCardProps {
 }
 
 // Component for the issue cards list on the left
-export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
+export function IssueCard({ issue, onSelect }: IssueCardProps) {
     const [textColor, setTextColor] = React.useState('');
     const [bgColor, setBgColor] = React.useState('transparent');
     const [borderColor, setBorderColor] = React.useState('transparent');
@@ -103,14 +107,14 @@ export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
             <h3 className="issue-title">{issue.title}</h3>
         </div>
     );
-};
+}
 
 interface IssueViewProps {
     issue: Issue;
 }
 
 // Detailed issue view that displays on the right
-export const IssueView = ({ issue }: IssueViewProps) => {
+export function IssueView({ issue }: IssueViewProps) {
     const [issueBody, setIssueBody] = React.useState('');
 
     // Parses the issue body into a formatted markdown (main functionality is replacing urls with tags)
@@ -127,15 +131,16 @@ export const IssueView = ({ issue }: IssueViewProps) => {
             parts.map(async part => {
                 if (imgTagRegex.test(part) || videoTagRegex.test(part) || audioTagRegex.test(part)) {
                     return `\n${await parseFileTag(part)}\n`;
-                } else if (fileRegex.test(part)) {
+                }
+                if (fileRegex.test(part)) {
                     const tag = await parseDashFiles(part);
                     return tag;
-                } else if (localRegex.test(part)) {
+                }
+                if (localRegex.test(part)) {
                     const tag = await parseLocalFiles(part);
                     return tag;
-                } else {
-                    return part;
                 }
+                return part;
             })
         );
 
@@ -143,7 +148,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
     };
 
     // Extracts the src from an image tag and either returns the raw url if not accessible or a new image tag
-    const parseFileTag = async (tag: string): Promise<string> => {
+    const parseFileTag = async (tag: string): Promise<string | undefined> => {
         const regex = /src="([^"]+)"/;
         let url = '';
         const match = tag.match(regex);
@@ -160,18 +165,19 @@ export const IssueView = ({ issue }: IssueViewProps) => {
             case '.png':
             case '.jpeg':
             case '.gif':
-                return await getDisplayedFile(url, 'image');
+                return getDisplayedFile(url, 'image');
             // video
             case '.mp4':
             case '.mpeg':
             case '.webm':
             case '.mov':
-                return await getDisplayedFile(url, 'video');
-            //audio
+                return getDisplayedFile(url, 'video');
+            // audio
             case '.mp3':
             case '.wav':
             case '.ogg':
-                return await getDisplayedFile(url, 'audio');
+                return getDisplayedFile(url, 'audio');
+            default:
         }
         return tag;
     };
@@ -183,14 +189,15 @@ export const IssueView = ({ issue }: IssueViewProps) => {
         const dashAudioRegex = /https:\/\/browndash\.com\/files[/\\]audio/;
 
         if (dashImgRegex.test(url)) {
-            return await getDisplayedFile(url, 'image');
-        } else if (dashVideoRegex.test(url)) {
-            return await getDisplayedFile(url, 'video');
-        } else if (dashAudioRegex.test(url)) {
-            return await getDisplayedFile(url, 'audio');
-        } else {
-            return url;
+            return getDisplayedFile(url, 'image');
         }
+        if (dashVideoRegex.test(url)) {
+            return getDisplayedFile(url, 'video');
+        }
+        if (dashAudioRegex.test(url)) {
+            return getDisplayedFile(url, 'audio');
+        }
+        return url;
     };
 
     // Returns the corresponding HTML tag for a src url
@@ -200,31 +207,37 @@ export const IssueView = ({ issue }: IssueViewProps) => {
         const dashAudioRegex = /http:\/\/localhost:1050\.com\/files[/\\]audio/;
 
         if (imgRegex.test(url)) {
-            return await getDisplayedFile(url, 'image');
-        } else if (dashVideoRegex.test(url)) {
-            return await getDisplayedFile(url, 'video');
-        } else if (dashAudioRegex.test(url)) {
-            return await getDisplayedFile(url, 'audio');
-        } else {
-            return url;
+            return getDisplayedFile(url, 'image');
+        }
+        if (dashVideoRegex.test(url)) {
+            return getDisplayedFile(url, 'video');
+        }
+        if (dashAudioRegex.test(url)) {
+            return getDisplayedFile(url, 'audio');
         }
+        return url;
     };
 
-    const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string> => {
+    const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string | undefined> => {
         switch (fileType) {
-            case 'image':
+            case 'image': {
                 const imgValid = await isImgValid(url);
                 if (!imgValid) return `\n${url} (This image could not be loaded)\n`;
                 return `\n${url}\n<img width="100%" alt="Issue asset" src=${url} />\n`;
-            case 'video':
+            }
+            case 'video': {
                 const videoValid = await isVideoValid(url);
                 if (!videoValid) return `\n${url} (This video could not be loaded)\n`;
                 return `\n${url}\n<video class="report-default-video" width="100%" controls alt="Issue asset" src=${url} />\n`;
-            case 'audio':
+            }
+            case 'audio': {
                 const audioValid = await isAudioValid(url);
                 if (!audioValid) return `\n${url} (This audio could not be loaded)\n`;
                 return `\n${url}\n<audio src=${url} controls />\n`;
+            }
+            default:
         }
+        return undefined;
     };
 
     // Loads an image and returns a promise that resolves as whether the image is valid or not
@@ -270,7 +283,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
         <div className="issue-view">
             <span className="issue-label">
                 Issue{' '}
-                <a className="issue-link" href={issue.html_url} target="_blank">
+                <a className="issue-link" href={issue.html_url} target="_blank" rel="noreferrer">
                     #{issue.number}
                 </a>
             </span>
@@ -292,7 +305,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
             <ReactMarkdown children={issueBody} className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
         </div>
     );
-};
+}
 
 interface TagProps {
     text: string;
@@ -305,7 +318,7 @@ interface TagProps {
 }
 
 // Small tag for labels of the issue
-export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) => {
+export function Tag({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) {
     return (
         <div
             onClick={onClick ?? (() => {})}
@@ -314,14 +327,14 @@ export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColo
             {text}
         </div>
     );
-};
+}
 
 interface FormInputProps {
     value: string;
     placeholder: string;
     onChange: (val: string) => void;
 }
-export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => {
+export function FormInput({ value, placeholder, onChange }: FormInputProps) {
     const [inputBorderColor, setInputBorderColor] = React.useState('');
 
     return (
@@ -349,9 +362,9 @@ export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => {
             }}
         />
     );
-};
+}
 
-export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) => {
+export function FormTextArea({ value, placeholder, onChange }: FormInputProps) {
     const [textAreaBorderColor, setTextAreaBorderColor] = React.useState('');
 
     return (
@@ -378,4 +391,4 @@ export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) =
             }}
         />
     );
-};
+}
diff --git a/src/client/util/reportManager/reportManagerSchema.ts b/src/client/util/reportManager/reportManagerSchema.ts
index 9a1c7c3e9..171c24393 100644
--- a/src/client/util/reportManager/reportManagerSchema.ts
+++ b/src/client/util/reportManager/reportManagerSchema.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-use-before-define */
 /**
  * Issue interface schema from Github.
  */
diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts
index 22e5eebbb..f14967e0a 100644
--- a/src/client/util/reportManager/reportManagerUtils.ts
+++ b/src/client/util/reportManager/reportManagerUtils.ts
@@ -63,9 +63,8 @@ export const getAllIssues = async (octokit: Octokit): Promise<any[]> => {
     // 200 status means success
     if (res.status === 200) {
         return res.data;
-    } else {
-        throw new Error('Error getting issues');
     }
+    throw new Error('Error getting issues');
 };
 
 /**
@@ -104,9 +103,7 @@ export const fileLinktoServerLink = (fileLink: string): string => {
  * @param link response from file upload
  * @returns server file path
  */
-export const getServerPath = (link: any): string => {
-    return link.result.accessPaths.agnostic.server as string;
-};
+export const getServerPath = (link: any): string => link.result.accessPaths.agnostic.server as string;
 
 /**
  * Uploads media files to the server.
@@ -124,6 +121,7 @@ export const uploadFilesToServer = async (mediaFiles: FileData[]): Promise<strin
             alert(err);
         }
     }
+    return undefined;
 };
 
 // helper functions
@@ -141,18 +139,16 @@ export const passesTagFilter = (issue: Issue, priorityFilter: string | null, bug
         passesPriority = issue.labels.some(label => {
             if (typeof label === 'string') {
                 return label === priorityFilter;
-            } else {
-                return label.name === priorityFilter;
             }
+            return label.name === priorityFilter;
         });
     }
     if (bugFilter) {
         passesBug = issue.labels.some(label => {
             if (typeof label === 'string') {
                 return label === bugFilter;
-            } else {
-                return label.name === bugFilter;
             }
+            return label.name === bugFilter;
         });
     }
     return passesPriority && passesBug;
@@ -217,7 +213,8 @@ export const bugColors: { [key: string]: string[] } = {
 export const getLabelColors = (label: string): string[] => {
     if (prioritySet.has(label as Priority)) {
         return priorityColors[label];
-    } else if (bugSet.has(label as BugType)) {
+    }
+    if (bugSet.has(label as BugType)) {
         return bugColors[label];
     }
     return ['#0f73f6', '#ffffff'];
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index f03e316a2..57498f39f 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -47,13 +47,13 @@ import { Track } from './Track';
 
 @observer
 export class Timeline extends ObservableReactComponent<FieldViewProps> {
-    //readonly constants
+    // readonly constants
     private readonly DEFAULT_TICK_SPACING: number = 50;
     private readonly MAX_TITLE_HEIGHT = 75;
     private readonly MAX_CONTAINER_HEIGHT: number = 800;
     private readonly DEFAULT_TICK_INCREMENT: number = 1000;
 
-    //height variables
+    // height variables
     private DEFAULT_CONTAINER_HEIGHT: number = 330;
     private MIN_CONTAINER_HEIGHT: number = 205;
 
@@ -62,7 +62,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         makeObservable(this);
     }
 
-    //react refs
+    // react refs
     @observable private _trackbox = React.createRef<HTMLDivElement>();
     @observable private _titleContainer = React.createRef<HTMLDivElement>();
     @observable private _timelineContainer = React.createRef<HTMLDivElement>();
@@ -70,7 +70,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
     @observable private _roundToggleRef = React.createRef<HTMLDivElement>();
     @observable private _roundToggleContainerRef = React.createRef<HTMLDivElement>();
 
-    //boolean vars and instance vars
+    // boolean vars and instance vars
     @observable private _currentBarX: number = 0;
     @observable private _windSpeed: number = 1;
     @observable private _totalLength: number = 0;
@@ -79,11 +79,11 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
     @observable private _containerHeight: number = this.DEFAULT_CONTAINER_HEIGHT;
     @observable private _tickSpacing = this.DEFAULT_TICK_SPACING;
     @observable private _tickIncrement = this.DEFAULT_TICK_INCREMENT;
-    @observable private _time = 100000; //DEFAULT
+    @observable private _time = 100000; // DEFAULT
     @observable private _playButton = faPlayCircle;
     @observable private _titleHeight = 0;
 
-    @observable public IsPlaying: boolean = false; //scrubber playing
+    @observable public IsPlaying: boolean = false; // scrubber playing
 
     /**
      * collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit.
@@ -97,30 +97,30 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         return DocListCast(this._props.Document[this._props.fieldKey]);
     }
 
-    /////////lifecycle functions////////////
+    /// //////lifecycle functions////////////
     @action
     componentDidMount() {
-        const relativeHeight = window.innerHeight / 20; //sets height to arbitrary size, relative to innerHeight
-        this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //check if relHeight is less than Maxheight. Else, just set relheight to max
-        this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; //offset
-        this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; //twice the titleheight + offset
+        const relativeHeight = window.innerHeight / 20; // sets height to arbitrary size, relative to innerHeight
+        this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; // check if relHeight is less than Maxheight. Else, just set relheight to max
+        this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; // offset
+        this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; // twice the titleheight + offset
         if (!this._props.Document.AnimationLength) {
-            //if animation length did not exist
-            this._props.Document.AnimationLength = this._time; //set it to default time
+            // if animation length did not exist
+            this._props.Document.AnimationLength = this._time; // set it to default time
         } else {
-            this._time = NumCast(this._props.Document.AnimationLength); //else, set time to animationlength stored from before
+            this._time = NumCast(this._props.Document.AnimationLength); // else, set time to animationlength stored from before
         }
-        this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); //the entire length of the timeline div (actual div part itself)
-        this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see)
-        this._visibleStart = this._infoContainer.current!.scrollLeft; //where the div starts
-        this._props.Document.isATOn = !this._props.Document.isATOn; //turns the boolean on, saying AT (animation timeline) is on
+        this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); // the entire length of the timeline div (actual div part itself)
+        this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; // the visible length of the timeline (the length that you current see)
+        this._visibleStart = this._infoContainer.current!.scrollLeft; // where the div starts
+        this._props.Document.isATOn = !this._props.Document.isATOn; // turns the boolean on, saying AT (animation timeline) is on
         this.toggleHandle();
     }
 
     componentWillUnmount() {
-        this._props.Document.AnimationLength = this._time; //save animation length
+        this._props.Document.AnimationLength = this._time; // save animation length
     }
-    /////////////////////////////////////////////////
+    /// //////////////////////////////////////////////
 
     /**
      * React Functional Component
@@ -148,7 +148,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         pixel <= 0 ? (this._currentBarX = 0) : pixel >= this._totalLength ? (this._currentBarX = this._totalLength) : (this._currentBarX = pixel);
     };
 
-    //for playing
+    // for playing
     onPlay = (e: React.MouseEvent) => {
         e.stopPropagation();
         this.play();
@@ -181,7 +181,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         e.preventDefault();
         e.stopPropagation();
         if (this._windSpeed < 64) {
-            //max speed is 32
+            // max speed is 32
             this._windSpeed = this._windSpeed * 2;
         }
     };
@@ -215,7 +215,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         const scrubberbox = this._infoContainer.current!;
         const left = scrubberbox.getBoundingClientRect().left;
         const offsetX = Math.round(e.clientX - left) * this._props.ScreenToLocalTransform().Scale;
-        this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position
+        this.changeCurrentBarX(offsetX + this._visibleStart); // changes scrubber to clicked scrubber position
         return false;
     };
 
@@ -355,12 +355,12 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
      * tool box includes the toggle buttons at the top of the timeline (both editing mode and play mode)
      */
     private timelineToolBox = (scale: number, totalTime: number) => {
-        const size = 40 * scale; //50 is default
+        const size = 40 * scale; // 50 is default
         const iconSize = 25;
         const width: number = this._props.PanelWidth();
         const modeType = this._props.Document.isATOn ? 'Author' : 'Play';
 
-        //decides if information should be omitted because the timeline is very small
+        // decides if information should be omitted because the timeline is very small
         // if its less than 950 pixels then it's going to be overlapping
         let modeString = modeType,
             overviewString = '',
@@ -469,7 +469,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
 
         this._props.Document.isATOn = !this._props.Document.isATOn;
         if (!BoolCast(this._props.Document.isATOn)) {
-            //turning on playmode...
+            // turning on playmode...
             roundToggle.style.transform = 'translate(0px, 0px)';
             roundToggle.style.animationName = 'turnoff';
             roundToggleContainer.style.animationName = 'turnoff';
@@ -477,7 +477,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
             timelineContainer.style.top = `${-this._containerHeight}px`;
             this.toPlay();
         } else {
-            //turning on authoring mode...
+            // turning on authoring mode...
             roundToggle.style.transform = 'translate(20px, 0px)';
             roundToggle.style.animationName = 'turnon';
             roundToggleContainer.style.animationName = 'turnon';
@@ -490,8 +490,8 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
     @action.bound
     changeLengths() {
         if (this._infoContainer.current) {
-            this._visibleLength = this._infoContainer.current.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see)
-            this._visibleStart = this._infoContainer.current.scrollLeft; //where the div starts
+            this._visibleLength = this._infoContainer.current.getBoundingClientRect().width; // the visible length of the timeline (the length that you current see)
+            this._visibleStart = this._infoContainer.current.scrollLeft; // where the div starts
         }
     }
 
@@ -515,7 +515,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
                     }
                 }
             } else {
-                //TODO: remove undefineds and duplicates
+                // TODO: remove undefineds and duplicates
             }
         });
         return longestTime;
diff --git a/src/client/views/animationtimeline/TimelineOverview.tsx b/src/client/views/animationtimeline/TimelineOverview.tsx
index 489c4dcde..7bf685c9e 100644
--- a/src/client/views/animationtimeline/TimelineOverview.tsx
+++ b/src/client/views/animationtimeline/TimelineOverview.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/no-unused-prop-types */
 import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -32,7 +33,6 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
     @observable private visibleTime: number = 0;
     @observable private currentX: number = 0;
     @observable private visibleStart: number = 0;
-    private readonly DEFAULT_HEIGHT = 50;
     private readonly DEFAULT_WIDTH = 300;
 
     componentDidMount() {
@@ -50,9 +50,9 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
         );
     }
 
-    componentWillUnmount = () => {
+    componentWillUnmount() {
         this._authoringReaction && this._authoringReaction();
-    };
+    }
 
     @action
     setOverviewWidth() {
@@ -109,7 +109,7 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
         e.preventDefault();
         e.stopPropagation();
         const scrubberRef = this._scrubberRef.current!;
-        const left = scrubberRef.getBoundingClientRect().left;
+        const { left } = scrubberRef.getBoundingClientRect();
         const offsetX = Math.round(e.clientX - left);
         this.props.changeCurrentBarX((offsetX / this.activeOverviewWidth) * this.props.totalLength + this.props.currentBarX);
     };
@@ -152,17 +152,17 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
         const timeline = this.props.isAuthoring
             ? [
                   <div key="timeline-overview-container" className="timeline-overview-container overviewBar" id="timelineOverview" ref={this.authoringContainer}>
-                      <div ref={this._visibleRef} key="1" className="timeline-overview-visible" style={{ left: `${barStart}px`, width: `${visibleBarWidth}px` }} onPointerDown={this.onPointerDown}></div>,
+                      <div ref={this._visibleRef} key="1" className="timeline-overview-visible" style={{ left: `${barStart}px`, width: `${visibleBarWidth}px` }} onPointerDown={this.onPointerDown} />,
                       <div ref={this._scrubberRef} key="2" className="timeline-overview-scrubber-container" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown}>
-                          <div key="timeline-overview-scrubber-head" className="timeline-overview-scrubber-head"></div>
+                          <div key="timeline-overview-scrubber-head" className="timeline-overview-scrubber-head" />
                       </div>
                   </div>,
               ]
             : [
                   <div key="1" className="timeline-play-bar overviewBar" id="timelinePlay" ref={this.playbackContainer}>
-                      <div ref={this._scrubberRef} className="timeline-play-head" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown}></div>
+                      <div ref={this._scrubberRef} className="timeline-play-head" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown} />
                   </div>,
-                  <div key="2" className="timeline-play-tail" style={{ width: `${playWidth}px` }}></div>,
+                  <div key="2" className="timeline-play-tail" style={{ width: `${playWidth}px` }} />,
               ];
         return (
             <div className="timeline-flex">
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index 578ce77a5..43e5a68eb 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -25,16 +25,13 @@ export class CollectionCalendarView extends CollectionSubView() {
 
     removeCalendar = () => {};
 
-    addCalendar = (doc: Doc | Doc[], annotationKey?: string | undefined): boolean => {
+    addCalendar = (/* doc: Doc | Doc[], annotationKey?: string | undefined */): boolean =>
         // bring up calendar modal with option to create a calendar
-        return true;
-    };
+        true;
 
     _stackRef = React.createRef<CollectionStackingView>();
 
-    panelHeight = () => {
-        return this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title
-    };
+    panelHeight = () => this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title
 
     // most recent calendar should come first
     sortByMostRecentDate = (calendarA: Doc, calendarB: Doc) => {
@@ -46,18 +43,18 @@ export class CollectionCalendarView extends CollectionSubView() {
 
         if (aFromDate > bFromDate) {
             return -1; // a comes first
-        } else if (aFromDate < bFromDate) {
+        }
+        if (aFromDate < bFromDate) {
+            return 1; // b comes first
+        }
+        // start dates are the same
+        if (aToDate > bToDate) {
+            return -1; // a comes first
+        }
+        if (aToDate < bToDate) {
             return 1; // b comes first
-        } else {
-            // start dates are the same
-            if (aToDate > bToDate) {
-                return -1; // a comes first
-            } else if (aToDate < bToDate) {
-                return 1; // b comes first
-            } else {
-                return 0; // same start and end dates
-            }
         }
+        return 0; // same start and end dates
     };
 
     screenToLocalTransform = () =>
@@ -74,6 +71,7 @@ export class CollectionCalendarView extends CollectionSubView() {
         return (
             <div className="collectionCalendarView">
                 <CollectionStackingView
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props}
                     setContentViewBox={emptyFunction}
                     ref={this._stackRef}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 6dda6e545..51352d3e2 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/jsx-props-no-spreading */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -107,10 +110,10 @@ export class CollectionCarouselView extends CollectionSubView() {
         return (
             <>
                 <div key="back" className="carouselView-back" onClick={this.goback}>
-                    <FontAwesomeIcon icon={'chevron-left'} size={'2x'} />
+                    <FontAwesomeIcon icon="chevron-left" size="2x" />
                 </div>
                 <div key="fwd" className="carouselView-fwd" onClick={this.advance}>
-                    <FontAwesomeIcon icon={'chevron-right'} size={'2x'} />
+                    <FontAwesomeIcon icon="chevron-right" size="2x" />
                 </div>
             </>
         );
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 9a1781b93..fefaf6591 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -120,7 +120,7 @@ export class CollectionDockingView extends CollectionSubView() {
     @undoBatch
     public static CloseSplit(document: Opt<Doc>, panelName?: string): boolean {
         if (CollectionDockingView.Instance) {
-            const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document));
+            const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tabView => (panelName ? tabView.contentItem.config.props.panelName === panelName : tabView.DashDoc === document));
             if (tab) {
                 const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
                 if (j !== -1) {
@@ -147,7 +147,7 @@ export class CollectionDockingView extends CollectionSubView() {
             stack.contentItems[activeContentItemIndex].remove();
             return instance.layoutChanged();
         }
-        const tab = Array.from(instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName);
+        const tab = Array.from(instance.tabMap.keys()).find(tabView => tabView.contentItem.config.props.panelName === panelName);
         if (tab) {
             const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
             if (newConfig.props.documentId !== tab.header.parent.contentItems[j].config.props.documentId) {
@@ -172,7 +172,7 @@ export class CollectionDockingView extends CollectionSubView() {
     public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) {
         if (document?._type_collection === CollectionViewType.Docking && !keyValue) return DashboardView.openDashboard(document);
         if (!CollectionDockingView.Instance) return false;
-        const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document && !tab.contentItem.config.props.keyValue && !keyValue);
+        const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tabView => tabView.DashDoc === document && !tabView.contentItem.config.props.keyValue && !keyValue);
         if (tab) {
             tab.header.parent.setActiveContentItem(tab.contentItem);
             return true;
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index e00fdb50c..95aecc7d0 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -14,7 +15,7 @@ import { DocUtils, Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { undoBatch } from '../../util/UndoManager';
+import { undoBatch, undoable } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
@@ -113,13 +114,15 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
     };
 
     @action
-    headingChanged = (value: string, shiftDown?: boolean) => {
+    headingChanged = (value: string /* , shiftDown?: boolean */) => {
         const castedValue = this.getValue(value);
         if (castedValue) {
             if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
                 return false;
             }
-            this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
+            this._props.docList.forEach(d => {
+                d[this._props.pivotField] = castedValue;
+            });
             if (this._props.headingObject) {
                 this._props.headingObject.setHeading(castedValue.toString());
                 this._heading = this._props.headingObject.heading;
@@ -129,10 +132,14 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
         return false;
     };
 
-    @action pointerEntered = () => (this._hover = true);
-    @action pointerLeave = () => (this._hover = false);
-    @undoBatch
-    addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
+    @action pointerEntered = () => {
+        this._hover = true;
+    };
+    @action pointerLeave = () => {
+        this._hover = false;
+    };
+
+    addTextNote = undoable(() => this.addNewTextDoc('-typed text-', false, true), 'add text note');
 
     // addNewTextDoc is called when a user starts typing in a column to create a new node
     @action
@@ -195,6 +202,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
                             }
                             return this._props.addDocument?.(created);
                         }
+                        return undefined;
                     },
                     icon: 'compress-arrows-alt',
                 })
@@ -214,6 +222,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
                             }
                             return this._props.addDocument?.(created) || false;
                         }
+                        return undefined;
                     },
                     icon: 'compress-arrows-alt',
                 })
@@ -238,7 +247,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
         const key = this._props.pivotField;
         const heading = this._heading;
         const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin;
-        const evContents = heading ? heading : '25';
+        const evContents = heading || '25';
         const headingView = this._props.headingObject ? (
             <div
                 key={heading}
@@ -252,17 +261,16 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
                     className="collectionNoteTakingView-sectionHeader-subCont"
                     title={evContents === `No Value` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
                     style={{ background: evContents !== `No Value` ? this._color : 'inherit' }}>
-                    <EditableView GetValue={() => evContents} isEditingCallback={isEditing => isEditing && this._props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
+                    <EditableView GetValue={() => evContents} isEditingCallback={isEditing => isEditing && this._props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine />
                 </div>
                 {(this._props.colHeaderData?.length ?? 0) > 1 && (
-                    <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
+                    <button type="button" className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
                         <FontAwesomeIcon icon="trash" size="lg" />
                     </button>
                 )}
             </div>
         ) : null;
         const templatecols = this.columnWidth;
-        const type = this._props.Document.type;
         return (
             <>
                 {headingView}
@@ -270,7 +278,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
                     <div className="collectionNoteTakingView-columnStack">
                         <div
                             key={`${heading}-stack`}
-                            className={`collectionNoteTakingView-Nodes`}
+                            className="collectionNoteTakingView-Nodes"
                             style={{
                                 padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
                                 gridGap: this._props.gridGap,
@@ -282,10 +290,13 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
                         {!this._props.chromeHidden ? (
                             <div className="collectionNoteTakingView-DocumentButtons" style={{ display: this._props.isContentActive() ? 'flex' : 'none', marginBottom: 10 }}>
                                 <div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}>
-                                    <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents={'+ Node'} menuCallback={this.menuCallback} />
+                                    <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents="+ Node" menuCallback={this.menuCallback} />
                                 </div>
                                 <div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}>
-                                    <EditableView {...this._props.editableViewProps()} />
+                                    {
+                                        // eslint-disable-next-line react/jsx-props-no-spreading
+                                        <EditableView {...this._props.editableViewProps()} />
+                                    }
                                 </div>
                             </div>
                         ) : null}
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index 67070b24d..ddd4b8137 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -28,7 +28,7 @@ export class CollectionNoteTakingViewDivider extends ObservableReactComponent<Di
         setupMoveUpEvents(
             this,
             e,
-            (e, down, delta) => {
+            (moveEv, down, delta) => {
                 if (!batch) batch = UndoManager.StartBatch('resizing');
                 this._props.setColumnStartXCoords(delta[0], this._props.index);
                 return false;
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 35f330b44..cb463077c 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -157,7 +157,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         const key = this._props.pivotField;
         const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
         key && (newDoc[key] = this.getValue(this._props.heading));
-        const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
+        const maxHeading = this._props.docList.reduce((prevHeading, doc) => (NumCast(doc.heading) > prevHeading ? NumCast(doc.heading) : prevHeading), 0);
         const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
         newDoc.heading = heading;
         FormattedTextBox.SetSelectOnLoad(newDoc);
@@ -220,9 +220,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
     renderMenu = () => (
         <div className="collectionStackingView-optionPicker">
             <div className="optionOptions">
-                <div className={'optionPicker' + (true ? ' active' : '')} onClick={action(() => {})}>
-                    Add options here
-                </div>
+                <div className="optionPicker active">Add options here</div>
             </div>
         </div>
     );
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 538a6fd5e..bf81bdc7f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -177,24 +177,24 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight);
 
     @action
-    remove = (doc: Doc | Doc[]): boolean => {
-        const docs = doc instanceof Doc ? [doc] : doc;
+    remove = (docIn: Doc | Doc[]): boolean => {
+        const docs = docIn instanceof Doc ? [docIn] : docIn;
         const targetDataDoc = this.Document[DocData];
         const value = DocListCast(targetDataDoc[this._props.fieldKey]);
         const result = value.filter(v => !docs.includes(v));
-        if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
+        if (docs.some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
         if (result.length !== value.length) {
-            if (doc instanceof Doc) {
-                const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc);
+            if (docIn instanceof Doc) {
+                const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(docIn);
                 const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1];
-                this._props.removeDocument?.(doc);
+                this._props.removeDocument?.(docIn);
                 if (ind > 0 && prev) {
                     FormattedTextBox.SetSelectOnLoad(prev);
                     DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false);
                 }
                 return true;
             }
-            return this._props.removeDocument?.(doc) ?? false;
+            return this._props.removeDocument?.(docIn) ?? false;
         }
         return false;
     };
@@ -299,7 +299,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     @computed get treeViewElements() {
         TraceMobx();
         const dragAction = StrCast(this.Document.childDragAction) as any as dropActionType;
-        const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
+        const treeAddDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
         const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false;
         if (this._renderCount < this.treeChildren.length)
             setTimeout(
@@ -315,7 +315,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             this._props.TemplateDataDocument,
             undefined,
             undefined,
-            addDoc,
+            treeAddDoc,
             this.remove,
             moveDoc,
             dragAction,
@@ -419,8 +419,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
 
     addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this._props.fieldKey}_annotations`) || false;
     remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this._props.fieldKey}_annotations`) || false;
-    moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) =>
-        this.moveDocument(doc, targetCollection, addDocument, `${this._props.fieldKey}_annotations`) || false;
+    moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => this.moveDocument(doc, targetCollection, addDocument) || false;
 
     @observable _headerHeight = 0;
     @computed get content() {
diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx
index 4523a4f1e..7dc08389b 100644
--- a/src/client/views/collections/KeyRestrictionRow.tsx
+++ b/src/client/views/collections/KeyRestrictionRow.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/button-has-type */
 import { observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -35,11 +36,36 @@ export default class KeyRestrictionRow extends React.Component<IKeyRestrictionPr
 
         return (
             <div className="collectionViewBaseChrome-viewSpecsMenu-row">
-                <input className="collectionViewBaseChrome-viewSpecsMenu-rowLeft" value={this._key} onChange={e => runInAction(() => (this._key = e.target.value))} placeholder="KEY" />
-                <button className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle" style={{ background: this._contains ? '#77dd77' : '#ff6961' }} onClick={() => runInAction(() => (this._contains = !this._contains))}>
+                <input
+                    className="collectionViewBaseChrome-viewSpecsMenu-rowLeft"
+                    value={this._key}
+                    onChange={e =>
+                        runInAction(() => {
+                            this._key = e.target.value;
+                        })
+                    }
+                    placeholder="KEY"
+                />
+                <button
+                    className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
+                    style={{ background: this._contains ? '#77dd77' : '#ff6961' }}
+                    onClick={() =>
+                        runInAction(() => {
+                            this._contains = !this._contains;
+                        })
+                    }>
                     {this._contains ? 'CONTAINS' : 'DOES NOT CONTAIN'}
                 </button>
-                <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight" value={this._value} onChange={e => runInAction(() => (this._value = e.target.value))} placeholder="VALUE" />
+                <input
+                    className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
+                    value={this._value}
+                    onChange={e =>
+                        runInAction(() => {
+                            this._value = e.target.value;
+                        })
+                    }
+                    placeholder="VALUE"
+                />
             </div>
         );
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
index 0acc99360..d2ce17f99 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
@@ -10,6 +10,7 @@ export interface CollectionFreeFormViewBackgroundGridProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     color: () => string;
+    // eslint-disable-next-line react/require-default-props
     isAnnotationOverlay?: boolean;
     nativeDimScaling: () => number;
     zoomScaling: () => number;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index 73dd7fea3..fc39cafaa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -5,13 +5,13 @@ import * as React from 'react';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import './CollectionFreeFormView.scss';
-import { Doc } from '../../../../fields/Doc';
 
 /**
  * An Fsa Arc.  The first array element is a test condition function that will be observed.
  * The second array element is a function that will be invoked when the first test function
  * returns a truthy value
  */
+// eslint-disable-next-line no-use-before-define
 export type infoArc = [() => any, (res?: any) => infoState];
 
 export const StateMessage = Symbol('StateMessage');
@@ -46,6 +46,7 @@ export function InfoState(
     gif?: string,
     entryFunc?: () => any
 ) {
+    // eslint-disable-next-line new-cap
     return new infoState(msg, arcs, gif, entryFunc);
 }
 
@@ -73,14 +74,15 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
     }
 
     clearState = () => this._disposers.map(disposer => disposer());
-    initState = () => (this._disposers = 
-        this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(
-            arc => reaction(
-                arc.test,
-                res => res && this._props.next(arc.act(res)),
-                { fireImmediately: true }
-            )
-        )); // prettier-ignore
+    initState = () => {
+        this._disposers = this.Arcs
+            .map(arc => ({ test: arc[0], act: arc[1] }))
+            .map(arc => reaction(
+                    arc.test,
+                    res => res && this._props.next(arc.act(res)),
+                    { fireImmediately: true }
+                )
+        )}; // prettier-ignore
 
     componentDidMount() {
         this.initState();
@@ -97,10 +99,15 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
     render() {
         const gif = this.State?.[StateMessageGIF];
         return (
-            <div className={'collectionFreeform-infoUI'}>
+            <div className="collectionFreeform-infoUI">
                 <p className="collectionFreeform-infoUI-msg">
                     {this.State?.[StateMessage]}
-                    <button className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')} onClick={action(() => (this._expanded = !this._expanded))}>
+                    <button
+                        type="button"
+                        className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')}
+                        onClick={action(() => {
+                            this._expanded = !this._expanded;
+                        })}>
                         {this._expanded ? 'Less...' : 'More...'}
                     </button>
                 </p>
@@ -108,7 +115,7 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
                     <img src={`/assets/${gif}`} alt="state message gif" />
                 </div>
                 <div className="collectionFreeform-infoUI-close">
-                    <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(e => this.props.close())} />
+                    <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(() => this.props.close())} />
                 </div>
             </div>
         );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 3970c6807..a4496a417 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -147,10 +147,10 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
         }
         const val = Field.toString(pair.layout[pivotFieldKey] as FieldType);
         if (listValue) {
-            listValue.forEach((val, i) => {
-                !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
-                pivotColumnGroups.get(val)!.docs.push(pair.layout);
-                pivotColumnGroups.get(val)!.replicas.push(i.toString());
+            listValue.forEach((lval, i) => {
+                !pivotColumnGroups.get(lval) && pivotColumnGroups.set(lval, { docs: [], filters: [lval], replicas: [] });
+                pivotColumnGroups.get(lval)!.docs.push(pair.layout);
+                pivotColumnGroups.get(lval)!.replicas.push(i.toString());
             });
         } else if (val) {
             !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 35394016d..f64c6715b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -4,9 +4,7 @@ import * as mobxUtils from 'mobx-utils';
 import * as React from 'react';
 import * as uuid from 'uuid';
 import CursorField from '../../../../fields/CursorField';
-import { Doc, FieldResult } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
-import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { Cast } from '../../../../fields/Types';
 import { CollectionViewProps } from '../CollectionSubView';
@@ -16,14 +14,12 @@ import './CollectionFreeFormView.scss';
 export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
     @computed protected get cursors(): CursorField[] {
         const { Document } = this.props;
-
-        let cursors: FieldResult<List<CursorField>>;
-        const id = Doc.UserDoc()[Id];
-        if (!id || !(cursors = Cast(Document.cursors, listSpec(CursorField)))) {
+        const cursors = Cast(Document.cursors, listSpec(CursorField));
+        if (!cursors) {
             return [];
         }
         const now = mobxUtils.now();
-        return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== id && now - metadata.timestamp < 1000);
+        return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== Document[Id] && now - metadata.timestamp < 1000);
     }
 
     @computed get renderedCursors() {
@@ -33,46 +29,44 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
                     metadata,
                     position: { x, y },
                 },
-            }) => {
-                return (
-                    <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont" style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}>
-                        <canvas
-                            className="collectionFreeFormRemoteCursors-canvas"
-                            ref={el => {
-                                if (el) {
-                                    const ctx = el.getContext('2d');
-                                    if (ctx) {
-                                        ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22';
-                                        ctx.fillRect(0, 0, 20, 20);
+            }) => (
+                <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont" style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}>
+                    <canvas
+                        className="collectionFreeFormRemoteCursors-canvas"
+                        ref={el => {
+                            if (el) {
+                                const ctx = el.getContext('2d');
+                                if (ctx) {
+                                    ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22';
+                                    ctx.fillRect(0, 0, 20, 20);
 
-                                        ctx.fillStyle = 'black';
-                                        ctx.lineWidth = 0.5;
+                                    ctx.fillStyle = 'black';
+                                    ctx.lineWidth = 0.5;
 
-                                        ctx.beginPath();
+                                    ctx.beginPath();
 
-                                        ctx.moveTo(10, 0);
-                                        ctx.lineTo(10, 8);
+                                    ctx.moveTo(10, 0);
+                                    ctx.lineTo(10, 8);
 
-                                        ctx.moveTo(10, 20);
-                                        ctx.lineTo(10, 12);
+                                    ctx.moveTo(10, 20);
+                                    ctx.lineTo(10, 12);
 
-                                        ctx.moveTo(0, 10);
-                                        ctx.lineTo(8, 10);
+                                    ctx.moveTo(0, 10);
+                                    ctx.lineTo(8, 10);
 
-                                        ctx.moveTo(20, 10);
-                                        ctx.lineTo(12, 10);
+                                    ctx.moveTo(20, 10);
+                                    ctx.lineTo(12, 10);
 
-                                        ctx.stroke();
-                                    }
+                                    ctx.stroke();
                                 }
-                            }}
-                            width={20}
-                            height={20}
-                        />
-                        <p className="collectionFreeFormRemoteCursors-symbol">{metadata.identifier[0].toUpperCase()}</p>
-                    </div>
-                );
-            }
+                            }
+                        }}
+                        width={20}
+                        height={20}
+                    />
+                    <p className="collectionFreeFormRemoteCursors-symbol">{metadata.identifier[0].toUpperCase()}</p>
+                </div>
+            )
         );
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 79cc534dc..adac5a102 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -9,6 +9,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 
 @observer
 export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: MarqueeOptionsMenu;
 
     public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => void = unimplementedFunction;
@@ -29,14 +30,13 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
     }
 
     render() {
-        const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ width: 19 }} />;
         const buttons = (
             <>
-                <IconButton tooltip={'Create a Collection'} onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} />
-                <IconButton tooltip={'Create a Grouping'} onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} />
-                <IconButton tooltip={'Summarize Documents'} onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} />
-                <IconButton tooltip={'Delete Documents'} onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} />
-                <IconButton tooltip={'Pin selected region'} onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} />
+                <IconButton tooltip="Create a Collection" onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} />
+                <IconButton tooltip="Create a Grouping" onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} />
+                <IconButton tooltip="Summarize Documents" onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} />
+                <IconButton tooltip="Delete Documents" onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} />
+                <IconButton tooltip="Pin selected region" onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} />
             </>
         );
         return this.getElement(buttons);
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index ab6788e6f..1634daaf7 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -17,6 +17,7 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionGridView.scss';
 import Grid, { Layout } from './Grid';
+
 @observer
 export class CollectionGridView extends CollectionSubView() {
     private _containerRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -77,15 +78,13 @@ export class CollectionGridView extends CollectionSubView() {
                 pairs.forEach((pair, i) => {
                     const existing = oldLayouts.find(l => l.i === pair.layout[Id]);
                     if (existing) newLayouts.push(existing);
-                    else {
-                        if (Object.keys(this.dropLocation).length) {
-                            // external drop
-                            this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid));
-                            this.dropLocation = {};
-                        } else {
-                            // internal drop
-                            this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
-                        }
+                    else if (Object.keys(this.dropLocation).length) {
+                        // external drop
+                        this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid));
+                        this.dropLocation = {};
+                    } else {
+                        // internal drop
+                        this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
                     }
                 });
                 pairs?.length && this.setLayoutList(newLayouts);
@@ -140,9 +139,7 @@ export class CollectionGridView extends CollectionSubView() {
     /**
      * Creates a layout object for a grid item
      */
-    makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => {
-        return { i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static };
-    };
+    makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static });
 
     /**
      * Adds a layout to the list of layouts.
@@ -190,6 +187,7 @@ export class CollectionGridView extends CollectionSubView() {
     getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
         return (
             <DocumentView
+                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
@@ -219,7 +217,7 @@ export class CollectionGridView extends CollectionSubView() {
         if (this.flexGrid) {
             const savedLayouts = this.savedLayoutList;
             this.childLayoutPairs.forEach(({ layout: doc }) => {
-                const gridLayout = savedLayouts.find(gridLayout => gridLayout.i === doc[Id]);
+                const gridLayout = savedLayouts.find(layout => layout.i === doc[Id]);
                 if (gridLayout) Object.assign(gridLayout, layoutArray.find(layout => layout.i === doc[Id]) || gridLayout);
             });
 
@@ -318,7 +316,9 @@ export class CollectionGridView extends CollectionSubView() {
             e,
             returnFalse,
             action(() => {
-                undoBatch(() => (this.Document.gridRowHeight = this._rowHeight))();
+                undoBatch(() => {
+                    this.Document.gridRowHeight = this._rowHeight;
+                })();
                 this._rowHeight = undefined;
             }),
             emptyFunction,
@@ -332,8 +332,20 @@ export class CollectionGridView extends CollectionSubView() {
      */
     onContextMenu = () => {
         const displayOptionsMenu: ContextMenuProps[] = [];
-        displayOptionsMenu.push({ description: 'Toggle Content Display Style', event: () => (this.Document.display = this.Document.display ? undefined : 'contents'), icon: 'copy' });
-        displayOptionsMenu.push({ description: 'Toggle Vertical Centering', event: () => (this.Document.centerY = !this.Document.centerY), icon: 'copy' });
+        displayOptionsMenu.push({
+            description: 'Toggle Content Display Style',
+            event: () => {
+                this.Document.display = this.Document.display ? undefined : 'contents';
+            },
+            icon: 'copy',
+        });
+        displayOptionsMenu.push({
+            description: 'Toggle Vertical Centering',
+            event: () => {
+                this.Document.centerY = !this.Document.centerY;
+            },
+            icon: 'copy',
+        });
         ContextMenu.Instance.addItem({ description: 'Display', subitems: displayOptionsMenu, icon: 'tv' });
     };
 
@@ -347,14 +359,14 @@ export class CollectionGridView extends CollectionSubView() {
                 e,
                 returnFalse,
                 returnFalse,
-                (e: PointerEvent, doubleTap?: boolean) => {
-                    if (doubleTap && !e.button) {
+                (clickEv: PointerEvent, doubleTap?: boolean) => {
+                    if (doubleTap && !clickEv.button) {
                         undoBatch(
                             action(() => {
                                 const text = Docs.Create.TextDocument('', { _width: 150, _height: 50 });
                                 FormattedTextBox.SetSelectOnLoad(text); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
                                 Doc.AddDocToList(this.Document, this._props.fieldKey, text);
-                                this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
+                                this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(clickEv.clientX, clickEv.clientY))));
                             })
                         )();
                     }
@@ -387,7 +399,7 @@ export class CollectionGridView extends CollectionSubView() {
                         width={this._props.PanelWidth()}
                         nodeList={this.contents.length ? this.contents : null}
                         layout={this.contents.length ? this.renderedLayoutList : undefined}
-                        childrenDraggable={this._props.isSelected() ? true : false}
+                        childrenDraggable={!!this._props.isSelected()}
                         numCols={this.numCols}
                         rowHeight={this.rowHeight}
                         setLayout={this.setLayout}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 6635ab222..e68ed0e17 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -15,7 +15,8 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
 import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
 import { UndoStack } from '../../UndoStack';
diff --git a/src/client/views/collections/collectionLinear/index.ts b/src/client/views/collections/collectionLinear/index.ts
index ff73e14ae..ab3b4b0b5 100644
--- a/src/client/views/collections/collectionLinear/index.ts
+++ b/src/client/views/collections/collectionLinear/index.ts
@@ -1 +1 @@
-export * from "./CollectionLinearView";
\ No newline at end of file
+export * from './CollectionLinearView';
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 125dd2781..b8509a005 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { Button, IconButton } from 'browndash-components';
@@ -7,7 +9,7 @@ import * as React from 'react';
 import { FaChevronRight } from 'react-icons/fa';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch, undoable } from '../../../util/UndoManager';
@@ -16,6 +18,7 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionMulticolumnView.scss';
 import ResizeBar from './MulticolumnResizer';
 import WidthLabel from './MulticolumnWidthLabel';
+import { dropActionType } from '../../../util/DropActionTypes';
 
 interface WidthSpecifier {
     magnitude: number;
@@ -80,7 +83,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
     private get resolvedLayoutInformation(): LayoutData {
         let starSum = 0;
         const widthSpecifiers: WidthSpecifier[] = [];
-        this.childLayouts.map(layout => {
+        this.childLayouts.forEach(layout => {
             const unit = StrCast(layout._dimUnit, '*');
             const magnitude = NumCast(layout._dimMagnitude, this.minimumDim);
             if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
@@ -140,6 +143,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
             return this._props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._xMargin);
         }
+        return undefined;
     }
 
     /**
@@ -159,6 +163,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
             return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
         }
+        return undefined;
     }
 
     /**
@@ -175,7 +180,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
      * or the ratio width evaluated to a pixel value
      */
     private lookupPixels = (layout: Doc): number => {
-        const columnUnitLength = this.columnUnitLength;
+        const { columnUnitLength } = this;
         if (columnUnitLength === undefined) {
             return 0; // we're still waiting on promises to resolve
         }
@@ -193,19 +198,19 @@ export class CollectionMulticolumnView extends CollectionSubView() {
      * documents before the target.
      */
     private lookupIndividualTransform = (layout: Doc) => {
-        const columnUnitLength = this.columnUnitLength;
+        const { columnUnitLength } = this;
         if (columnUnitLength === undefined) {
             return Transform.Identity(); // we're still waiting on promises to resolve
         }
         let offset = 0;
-        var xf = Transform.Identity();
-        this.childLayouts.map(candidate => {
+        // eslint-disable-next-line no-restricted-syntax
+        for (const { layout: candidate } of this.childLayoutPairs) {
             if (candidate === layout) {
-                return (xf = this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0));
+                return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
             }
             offset += this.lookupPixels(candidate) + resizerWidth;
-        });
-        return xf;
+        }
+        return Transform.Identity();
     };
 
     @undoBatch
@@ -213,7 +218,9 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         let dropInd = -1;
         if (de.complete.docDragData && this._mainCont) {
             let curInd = -1;
-            de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d)));
+            de.complete.docDragData?.droppedDocuments.forEach(d => {
+                curInd = this.childDocs.indexOf(d);
+            });
             Array.from(this._mainCont.children).forEach((child, index) => {
                 const brect = child.getBoundingClientRect();
                 if (brect.x < de.x && brect.x + brect.width > de.x) {
@@ -265,7 +272,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
             this.lookupIndividualTransform(childLayout)
                 .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
                 .scale(this._props.NativeDimScaling?.() || 1);
-        const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(childLayout.freeform_fitContentsToBox);
         return (
             <DocumentView
                 Document={childLayout}
@@ -281,11 +287,11 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
                 onClickScript={this.onChildClickHandler}
                 onDoubleClickScript={this.onChildDoubleClickHandler}
-                suppressSetHeight={true}
+                suppressSetHeight
                 ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
-                hideResizeHandles={childLayout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
+                hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)}
                 hideDecorationTitle={this._props.childHideDecorationTitle}
                 fitContentsToBox={this._props.fitContentsToBox}
                 focus={this._props.focus}
@@ -312,17 +318,19 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         const collector: JSX.Element[] = [];
         this.childLayouts.forEach((layout, i) => {
             collector.push(
+                // eslint-disable-next-line react/no-array-index-key
                 <Tooltip title={'Tab: ' + StrCast(layout.title)} key={'wrapper' + i}>
                     <div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}>
                         {this.getDisplayDoc(layout)}
                         {this.layoutDoc._chromeHidden ? null : (
-                            <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
+                            <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(() => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
                         )}
                         <WidthLabel layout={layout} collectionDoc={this.Document} />
                     </div>
                 </Tooltip>,
                 <ResizeBar
                     width={resizerWidth}
+                    // eslint-disable-next-line react/no-array-index-key
                     key={'resizer' + i}
                     styleProvider={this._props.styleProvider}
                     isContentActive={this._props.isContentActive}
@@ -353,14 +361,29 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 {this.contents}
                 {!this._startIndex ? null : (
                     <Tooltip title="scroll back">
-                        <div style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
-                            <Button tooltip="Scroll back" icon={<FontAwesomeIcon icon="chevron-left" size="lg" />} onClick={action(e => (this._startIndex = Math.max(0, this._startIndex - this.maxShown)))} color={SettingsManager.userColor} />
+                        <div
+                            style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }}
+                            onClick={action(() => {
+                                this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown);
+                            })}>
+                            <Button
+                                tooltip="Scroll back"
+                                icon={<FontAwesomeIcon icon="chevron-left" size="lg" />}
+                                onClick={action(() => {
+                                    this._startIndex = Math.max(0, this._startIndex - this.maxShown);
+                                })}
+                                color={SettingsManager.userColor}
+                            />
                         </div>
                     </Tooltip>
                 )}
                 {this._startIndex > this.childLayoutPairs.length - 1 || !this.maxShown ? null : (
                     <Tooltip title="scroll forward">
-                        <div style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
+                        <div
+                            style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }}
+                            onClick={action(() => {
+                                this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown);
+                            })}>
                             <IconButton icon={<FaChevronRight />} color={SettingsManager.userColor} />
                         </div>
                     </Tooltip>
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 17bf3e50c..3fe3d5343 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -3,7 +3,8 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
 import { DocumentView } from '../../nodes/DocumentView';
@@ -11,6 +12,7 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionMultirowView.scss';
 import HeightLabel from './MultirowHeightLabel';
 import ResizeBar from './MultirowResizer';
+
 interface HeightSpecifier {
     magnitude: number;
     unit: string;
@@ -63,7 +65,7 @@ export class CollectionMultirowView extends CollectionSubView() {
     private get resolvedLayoutInformation(): LayoutData {
         let starSum = 0;
         const heightSpecifiers: HeightSpecifier[] = [];
-        this.childLayoutPairs.map(pair => {
+        this.childLayoutPairs.forEach(pair => {
             const unit = StrCast(pair.layout._dimUnit, '*');
             const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim);
             if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
@@ -123,6 +125,7 @@ export class CollectionMultirowView extends CollectionSubView() {
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
             return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._yMargin);
         }
+        return undefined;
     }
 
     /**
@@ -142,6 +145,7 @@ export class CollectionMultirowView extends CollectionSubView() {
         if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
             return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
         }
+        return undefined;
     }
 
     /**
@@ -158,13 +162,12 @@ export class CollectionMultirowView extends CollectionSubView() {
      * or the ratio width evaluated to a pixel value
      */
     private lookupPixels = (layout: Doc): number => {
-        const rowUnitLength = this.rowUnitLength;
-        if (rowUnitLength === undefined) {
+        if (this.rowUnitLength === undefined) {
             return 0; // we're still waiting on promises to resolve
         }
         let height = NumCast(layout._dimMagnitude, this.minimumDim);
         if (StrCast(layout._dimUnit, '*') === DimUnit.Ratio) {
-            height *= rowUnitLength;
+            height *= this.rowUnitLength;
         }
         return height;
     };
@@ -176,11 +179,11 @@ export class CollectionMultirowView extends CollectionSubView() {
      * documents before the target.
      */
     private lookupIndividualTransform = (layout: Doc) => {
-        const rowUnitLength = this.rowUnitLength;
-        if (rowUnitLength === undefined) {
+        if (this.rowUnitLength === undefined) {
             return Transform.Identity(); // we're still waiting on promises to resolve
         }
         let offset = 0;
+        // eslint-disable-next-line no-restricted-syntax
         for (const { layout: candidate } of this.childLayoutPairs) {
             if (candidate === layout) {
                 return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
@@ -195,7 +198,9 @@ export class CollectionMultirowView extends CollectionSubView() {
         let dropInd = -1;
         if (de.complete.docDragData && this._mainCont) {
             let curInd = -1;
-            de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d)));
+            de.complete.docDragData?.droppedDocuments.forEach(d => {
+                curInd = this.childDocs.indexOf(d);
+            });
             Array.from(this._mainCont.children).forEach((child, index) => {
                 const brect = child.getBoundingClientRect();
                 if (brect.y < de.y && brect.y + brect.height > de.y) {
@@ -247,7 +252,6 @@ export class CollectionMultirowView extends CollectionSubView() {
             this.lookupIndividualTransform(layout)
                 .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
                 .scale(this._props.NativeDimScaling?.() || 1);
-        const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
         return (
             <DocumentView
                 Document={layout}
@@ -266,7 +270,7 @@ export class CollectionMultirowView extends CollectionSubView() {
                 ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
-                hideResizeHandles={layout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
+                hideResizeHandles={!!(layout.layout_fitWidth || this._props.childHideResizeHandles)}
                 hideDecorationTitle={this._props.childHideDecorationTitle}
                 fitContentsToBox={this._props.fitContentsToBox}
                 focus={this._props.focus}
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index d580d9c52..49ba85524 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -52,27 +53,6 @@ export default class ResizeBar extends React.Component<ResizerProps> {
         }
     };
 
-    private get isActivated() {
-        const { toLeft, toRight } = this.props;
-        if (toLeft && toRight) {
-            if (StrCast(toLeft._dimUnit, '*') === DimUnit.Pixel && StrCast(toRight._dimUnit, '*') === DimUnit.Pixel) {
-                return false;
-            }
-            return true;
-        } else if (toLeft) {
-            if (StrCast(toLeft._dimUnit, '*') === DimUnit.Pixel) {
-                return false;
-            }
-            return true;
-        } else if (toRight) {
-            if (StrCast(toRight._dimUnit, '*') === DimUnit.Pixel) {
-                return false;
-            }
-            return true;
-        }
-        return false;
-    }
-
     @action
     private onPointerUp = () => {
         window.removeEventListener('pointermove', this.onPointerMove);
@@ -90,7 +70,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
                     width: this.props.width,
                     backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor),
                 }}>
-                <div className={'multiColumnResizer-hdl'} onPointerDown={e => this.registerResizing(e)} />
+                <div className="multiColumnResizer-hdl" onPointerDown={e => this.registerResizing(e)} />
             </div>
         );
     }
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
index a9579d931..a7a0b3457 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
@@ -19,7 +19,7 @@ export default class WidthLabel extends React.Component<WidthLabelProps> {
         const getUnit = () => StrCast(layout.dimUnit);
         const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(3));
         return (
-            <div className={'label-wrapper'}>
+            <div className="label-wrapper">
                 <EditableView
                     GetValue={getMagnitude}
                     SetValue={value => {
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
index 878c7ff3c..66215f109 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -20,7 +21,7 @@ export default class HeightLabel extends React.Component<HeightLabelProps> {
         const getUnit = () => StrCast(layout.dimUnit);
         const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3));
         return (
-            <div className={'label-wrapper'}>
+            <div className="label-wrapper">
                 <EditableView
                     GetValue={getMagnitude}
                     SetValue={value => {
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index 73d08d5ef..ad77c327d 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -33,7 +34,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
     };
 
     private onPointerMove = ({ movementY }: PointerEvent) => {
-        const { toTop: toTop, toBottom: toBottom, columnUnitLength } = this.props;
+        const { toTop, toBottom, columnUnitLength } = this.props;
         const movingDown = movementY > 0;
         const toNarrow = movingDown ? toBottom : toTop;
         const toWiden = movingDown ? toTop : toBottom;
@@ -50,27 +51,6 @@ export default class ResizeBar extends React.Component<ResizerProps> {
         }
     };
 
-    private get isActivated() {
-        const { toTop, toBottom } = this.props;
-        if (toTop && toBottom) {
-            if (StrCast(toTop._dimUnit, '*') === DimUnit.Pixel && StrCast(toBottom._dimUnit, '*') === DimUnit.Pixel) {
-                return false;
-            }
-            return true;
-        } else if (toTop) {
-            if (StrCast(toTop._dimUnit, '*') === DimUnit.Pixel) {
-                return false;
-            }
-            return true;
-        } else if (toBottom) {
-            if (StrCast(toBottom._dimUnit, '*') === DimUnit.Pixel) {
-                return false;
-            }
-            return true;
-        }
-        return false;
-    }
-
     @action
     private onPointerUp = () => {
         window.removeEventListener('pointermove', this.onPointerMove);
@@ -88,7 +68,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
                     height: this.props.height,
                     backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor),
                 }}>
-                <div className={'multiRowResizer-hdl'} onPointerDown={e => this.registerResizing(e)} />
+                <div className="multiRowResizer-hdl" onPointerDown={e => this.registerResizing(e)} />
             </div>
         );
     }
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 2823e1936..389fc66b3 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -1,5 +1,6 @@
+/* eslint-disable react/no-unused-prop-types */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
+import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { setupMoveUpEvents } from '../../../../ClientUtils';
@@ -25,8 +26,6 @@ export interface SchemaColumnHeaderProps {
 
 @observer
 export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> {
-    @observable _ref: HTMLDivElement | null = null;
-
     get fieldKey() {
         return this.props.columnKeys[this.props.columnIndex];
     }
@@ -35,9 +34,9 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
     sortClicked = (e: React.PointerEvent) => {
         e.stopPropagation();
         e.preventDefault();
-        if (this.props.sortField == this.fieldKey && this.props.sortDesc) {
+        if (this.props.sortField === this.fieldKey && this.props.sortDesc) {
             this.props.setSort(undefined);
-        } else if (this.props.sortField == this.fieldKey) {
+        } else if (this.props.sortField === this.fieldKey) {
             this.props.setSort(this.fieldKey, true);
         } else {
             this.props.setSort(this.fieldKey, false);
@@ -46,7 +45,7 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
 
     @action
     onPointerDown = (e: React.PointerEvent) => {
-        this.props.isContentActive(true) && setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction, false);
+        this.props.isContentActive(true) && setupMoveUpEvents(this, e, moveEv => this.props.dragColumn(moveEv, this.props.columnIndex), emptyFunction, emptyFunction, false);
     };
 
     render() {
@@ -59,19 +58,18 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
                 onPointerDown={this.onPointerDown}
                 ref={col => {
                     if (col) {
-                        this._ref = col;
                         this.props.setColRef(this.props.columnIndex, col);
                     }
                 }}>
-                <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)}></div>
+                <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)} />
                 <div className="schema-column-title">{this.fieldKey}</div>
 
                 <div className="schema-header-menu">
                     <div className="schema-header-button" onPointerDown={e => this.props.openContextMenu(e.clientX, e.clientY, this.props.columnIndex)}>
                         <FontAwesomeIcon icon="ellipsis-h" />
                     </div>
-                    <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField == this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}>
-                        <FontAwesomeIcon icon="caret-right" style={this.props.sortField == this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} />
+                    <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField === this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}>
+                        <FontAwesomeIcon icon="caret-right" style={this.props.sortField === this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} />
                     </div>
                 </div>
             </div>
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 27a4493cb..61afe08cf 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -63,7 +63,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
         }
     };
 
-    onPointerEnter = (e: any) => {
+    onPointerEnter = () => {
         if (SnappingManager.IsDragging && this._props.isContentActive()) {
             document.removeEventListener('pointermove', this.onPointerMove);
             document.addEventListener('pointermove', this.onPointerMove);
@@ -75,7 +75,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
 
         if (this._ref && dragIsRow) {
             const rect = this._ref.getBoundingClientRect();
-            const y = e.clientY - rect.top; //y position within the element.
+            const y = e.clientY - rect.top; // y position within the element.
             const height = this._ref.clientHeight;
             const halfLine = height / 2;
             if (y <= halfLine) {
@@ -90,7 +90,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
         }
     };
 
-    onPointerLeave = (e: any) => {
+    onPointerLeave = () => {
         if (this._ref) {
             this._ref.style.borderTop = '0px';
             this._ref.style.borderBottom = '0px';
@@ -131,15 +131,16 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
                                 e,
                                 returnFalse,
                                 emptyFunction,
-                                undoable(e => {
-                                    e.stopPropagation();
+                                undoable(clickEv => {
+                                    clickEv.stopPropagation();
                                     Doc.toggleLockedPosition(this.Document);
                                 }, 'Delete Row')
                             )
-                        }></IconButton>
+                        }
+                    />
                     <IconButton
                         tooltip="close"
-                        icon={<CgClose size={'16px'} />}
+                        icon={<CgClose size="16px" />}
                         size={Size.XSMALL}
                         onPointerDown={e =>
                             setupMoveUpEvents(
@@ -147,8 +148,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
                                 e,
                                 returnFalse,
                                 emptyFunction,
-                                undoable(e => {
-                                    e.stopPropagation();
+                                undoable(clickEv => {
+                                    clickEv.stopPropagation();
                                     this._props.removeDocument?.(this.Document);
                                 }, 'Delete Row')
                             )
@@ -164,8 +165,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
                                 e,
                                 returnFalse,
                                 emptyFunction,
-                                undoable(e => {
-                                    e.stopPropagation();
+                                undoable(clickEv => {
+                                    clickEv.stopPropagation();
                                     this._props.addDocTab(this.Document, OpenWhere.addRight);
                                 }, 'Open schema Doc preview')
                             )
diff --git a/src/client/views/global/globalEnums.tsx b/src/client/views/global/globalEnums.tsx
index 610c2b102..2cf9e4162 100644
--- a/src/client/views/global/globalEnums.tsx
+++ b/src/client/views/global/globalEnums.tsx
@@ -1,44 +1,44 @@
 export enum Colors {
-    BLACK = "#000000",
-    DARK_GRAY = "#323232",
-    MEDIUM_GRAY = "#9F9F9F",
-    LIGHT_GRAY = "#DFDFDF",
-    WHITE = "#FFFFFF",
-    MEDIUM_BLUE = "#4476F7",
-    MEDIUM_BLUE_ALT = "#4476f73d", // REDUCED OPACITY
-    LIGHT_BLUE = "#BDDDF5",
-    PINK = "#E0217D",
-    ERROR_RED = "#ff0033",
-    YELLOW = "#F5D747",
-    DROP_SHADOW = "#32323215",
+    BLACK = '#000000',
+    DARK_GRAY = '#323232',
+    MEDIUM_GRAY = '#9F9F9F',
+    LIGHT_GRAY = '#DFDFDF',
+    WHITE = '#FFFFFF',
+    MEDIUM_BLUE = '#4476F7',
+    MEDIUM_BLUE_ALT = '#4476f73d', // REDUCED OPACITY
+    LIGHT_BLUE = '#BDDDF5',
+    PINK = '#E0217D',
+    ERROR_RED = '#ff0033',
+    YELLOW = '#F5D747',
+    DROP_SHADOW = '#32323215',
 }
 
 export enum FontSizes {
-    //Bolded
-    LARGE_HEADER = "16px",
+    // Bolded
+    LARGE_HEADER = '16px',
 
-    //Bolded or unbolded
-    BODY_TEXT = "12px",
+    // Bolded or unbolded
+    BODY_TEXT = '12px',
 
-    //Bolded
-    SMALL_TEXT = "9px",
+    // Bolded
+    SMALL_TEXT = '9px',
 }
 
 export enum Padding {
-    MINIMUM_PADDING = "4px",
-    SMALL_PADDING = "8px",
-    MEDIUM_PADDING = "16px",
-    LARGE_PADDING = "32px",
+    MINIMUM_PADDING = '4px',
+    SMALL_PADDING = '8px',
+    MEDIUM_PADDING = '16px',
+    LARGE_PADDING = '32px',
 }
 
 export enum IconSizes {
-    ICON_SIZE = "28px",
+    ICON_SIZE = '28px',
 }
 
 export enum Borders {
-    STANDARD = "solid 1px #9F9F9F"
+    STANDARD = 'solid 1px #9F9F9F',
 }
 
 export enum Shadows {
-    STANDARD_SHADOW = "0px 3px 4px rgba(0, 0, 0, 0.3)"
-}
\ No newline at end of file
+    STANDARD_SHADOW = '0px 3px 4px rgba(0, 0, 0, 0.3)',
+}
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 60def5d45..f99a18db2 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,14 +1,17 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/require-default-props */
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import * as React from 'react';
 import { Doc, StrListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { Cast, DocCast } from '../../../fields/Types';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { LinkManager } from '../../util/LinkManager';
 import { DocumentView } from '../nodes/DocumentView';
 import './LinkMenu.scss';
 import { LinkMenuItem } from './LinkMenuItem';
-import * as React from 'react';
-import { DocumentType } from '../../documents/DocumentTypes';
 
 interface LinkMenuGroupProps {
     sourceDoc: Doc;
@@ -22,25 +25,24 @@ interface LinkMenuGroupProps {
 @observer
 export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
     private _menuRef = React.createRef<HTMLDivElement>();
+    @observable _collapsed = false;
 
     getBackgroundColor = (): string | undefined => {
-        const link_relationshipList = StrListCast(Doc.UserDoc().link_relationshipList);
+        const linkRelationshipList = StrListCast(Doc.UserDoc().link_relationshipList);
         const linkColorList = StrListCast(Doc.UserDoc().link_ColorList);
         let color: string | undefined;
         // if this link's relationship property is not default "link", set its color
-        if (link_relationshipList) {
-            const relationshipIndex = link_relationshipList.indexOf(this.props.groupType);
+        if (linkRelationshipList) {
+            const relationshipIndex = linkRelationshipList.indexOf(this.props.groupType);
             const RGBcolor: string = linkColorList[relationshipIndex];
             if (RGBcolor) {
-                //set opacity to 0.25 by modifiying the rgb string
+                // set opacity to 0.25 by modifiying the rgb string
                 color = RGBcolor.slice(0, RGBcolor.length - 1) + ', 0.25)';
             }
         }
         return color;
     };
 
-    @observable _collapsed = false;
-
     render() {
         const set = new Set<Doc>(this.props.group);
         const groupItems = Array.from(set.keys()).map(linkDoc => {
@@ -76,7 +78,12 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
 
         return (
             <div className="linkMenu-group" ref={this._menuRef}>
-                <div className="linkMenu-group-name" onClick={action(() => (this._collapsed = !this._collapsed))} style={{ background: this.getBackgroundColor() }}>
+                <div
+                    className="linkMenu-group-name"
+                    onClick={action(() => {
+                        this._collapsed = !this._collapsed;
+                    })}
+                    style={{ background: this.getBackgroundColor() }}>
                     <p className={this.props.groupType === '*' || this.props.groupType === '' ? '' : 'expand-one'}> {this.props.groupType}:</p>
                 </div>
                 {this._collapsed ? null : <div className="linkMenu-group-wrapper">{groupItems}</div>}
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 9be78a6cb..303ff4b98 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -92,10 +92,10 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
         setupMoveUpEvents(
             this,
             e,
-            e => {
+            moveEv => {
                 const dragData = new DragManager.DocumentDragData([this._props.linkDoc], dropActionType.embed);
                 dragData.dropPropertiesToRemove = ['hidden'];
-                DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
+                DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y);
                 return true;
             },
             emptyFunction,
@@ -125,10 +125,10 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
         setupMoveUpEvents(
             this,
             e,
-            e => {
+            moveEv => {
                 const eleClone: any = this._drag.current!.cloneNode(true);
-                eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
-                StartLinkTargetsDrag(eleClone, this._props.docView, e.x, e.y, this._props.sourceDoc, [this._props.linkDoc]);
+                eleClone.style.transform = `translate(${moveEv.x}px, ${moveEv.y}px)`;
+                StartLinkTargetsDrag(eleClone, this._props.docView, moveEv.x, moveEv.y, this._props.sourceDoc, [this._props.linkDoc]);
                 this._props.clearLinkEditor?.();
                 return true;
             },
diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx
deleted file mode 100644
index 0902d53b2..000000000
--- a/src/client/views/linking/LinkRelationshipSearch.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import './LinkEditor.scss';
-
-interface link_relationshipSearchProps {
-    results: string[] | undefined;
-    display: string;
-    //callback fn to set rel + hide dropdown upon setting
-    handleRelationshipSearchChange: (result: string) => void;
-    toggleSearch: () => void;
-}
-@observer
-export class link_relationshipSearch extends React.Component<link_relationshipSearchProps> {
-    handleResultClick = (e: React.MouseEvent) => {
-        const relationship = (e.target as HTMLParagraphElement).textContent;
-        if (relationship) {
-            this.props.handleRelationshipSearchChange(relationship);
-        }
-    };
-
-    handleMouseEnter = () => {
-        this.props.toggleSearch();
-    };
-
-    handleMouseLeave = () => {
-        this.props.toggleSearch();
-    };
-
-    /**
-     * Render an empty div to increase the height of LinkEditor to accommodate 2+ results
-     */
-    emptyDiv = () => {
-        if (this.props.results && this.props.results.length > 2 && this.props.display === 'block') {
-            return <div style={{ height: '50px' }} />;
-        }
-    };
-
-    render() {
-        return (
-            <div className="linkEditor-relationship-dropdown-container">
-                <div className="linkEditor-relationship-dropdown" style={{ display: this.props.display }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
-                    {
-                        // return a dropdown of relationship results if there exist results
-                        this.props.results ? (
-                            this.props.results.map(result => {
-                                return (
-                                    <p key={result} onClick={this.handleResultClick}>
-                                        {result}
-                                    </p>
-                                );
-                            })
-                        ) : (
-                            <p>No matching relationships</p>
-                        )
-                    }
-                </div>
-
-                {/*Render an empty div to increase the height of LinkEditor to accommodate 2+ results */}
-                {this.emptyDiv()}
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index c37466914..9251dca6d 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -532,7 +532,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 e,
                 returnFalse,
                 returnFalse,
-                action((e: PointerEvent, doubleTap?: boolean) => {
+                action((moveEv: PointerEvent, doubleTap?: boolean) => {
                     if (doubleTap) {
                         this.startTrim(TrimScope.All);
                     } else if (this.timeline) {
@@ -572,14 +572,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     setupTimelineDrop = (r: HTMLDivElement | null) => {
         if (r && this.timeline) {
             this._dropDisposer?.();
-            this._dropDisposer = DragManager.MakeDropTarget(
-                r,
-                (e, de) => {
-                    const [xp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
-                    de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
-                },
-                this.layoutDoc
-            );
+            this._dropDisposer = DragManager.MakeDropTarget(r, (e, de) => de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData), this.layoutDoc);
         }
     };
 
diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
index 0084d7394..60bc8df18 100644
--- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
+++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/alt-text */
 import { IconButton } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -15,56 +17,52 @@ interface SchemaCSVPopUpProps {}
 
 @observer
 export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: SchemaCSVPopUp;
 
-    @observable
-    public dataVizDoc: Doc | undefined = undefined;
+    @observable public dataVizDoc: Doc | undefined = undefined;
+    @observable public view: DocumentView | undefined = undefined;
+    @observable public target: Doc | undefined = undefined;
+    @observable public visible: boolean = false;
+
+    constructor(props: SchemaCSVPopUpProps) {
+        super(props);
+        makeObservable(this);
+        SchemaCSVPopUp.Instance = this;
+    }
+
     @action
     public setDataVizDoc = (doc: Doc) => {
         this.dataVizDoc = doc;
     };
 
-    @observable
-    public view: DocumentView | undefined = undefined;
     @action
     public setView = (docView: DocumentView) => {
         this.view = docView;
     };
 
-    @observable
-    public target: Doc | undefined = undefined;
     @action
     public setTarget = (doc: Doc) => {
         this.target = doc;
     };
 
-    @observable
-    public visible: boolean = false;
     @action
     public setVisible = (vis: boolean) => {
         this.visible = vis;
     };
 
-    constructor(props: SchemaCSVPopUpProps) {
-        super(props);
-        makeObservable(this);
-        SchemaCSVPopUp.Instance = this;
-    }
-
-    dataBox = () => {
-        return (
-            <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
-                {this.heading('Schema Table as Data Visualization Doc')}
-                <div className="image-content-wrapper">
-                    <div className="img-wrapper">
-                        <div className="img-container" onPointerDown={e => this.drag(e)}>
-                            <img width={150} height={150} src={'/assets/dataVizBox.png'} />
-                        </div>
+    dataBox = () => (
+        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+            {this.heading('Schema Table as Data Visualization Doc')}
+            <div className="image-content-wrapper">
+                <div className="img-wrapper">
+                    <div className="img-container" onPointerDown={e => this.drag(e)}>
+                        <img width={150} height={150} src="/assets/dataVizBox.png" />
                     </div>
                 </div>
             </div>
-        );
-    };
+        </div>
+    );
 
     heading = (headingText: string) => (
         <div className="summary-heading">
@@ -79,24 +77,22 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
         setupMoveUpEvents(
             {},
             e,
-            e => {
+            moveEv => {
                 const sourceAnchorCreator = () => this.dataVizDoc!;
-                const targetCreator = (annotationOn: Doc | undefined) => {
+                const targetCreator = () => {
                     const embedding = Doc.MakeEmbedding(this.dataVizDoc!);
                     return embedding;
                 };
-                if (this.view && sourceAnchorCreator && !ClientUtils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
-                    DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
-                        dragComplete: e => {
-                            this.setVisible(false);
-                        },
+                if (this.view && sourceAnchorCreator && !ClientUtils.isClick(moveEv.clientX, moveEv.clientY, downX, downY, Date.now())) {
+                    DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
+                        dragComplete: () => this.setVisible(false),
                     });
                     return true;
                 }
                 return false;
             },
             emptyFunction,
-            action(e => {})
+            action(() => {})
         );
     };
 
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 2735a40d5..910504e7c 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -75,8 +75,8 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
     }
 
     @computed get defaultGraphTitle() {
-        var ax0 = this._props.axes[0];
-        var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+        const ax0 = this._props.axes[0];
+        const ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
         if (this._props.axes.length < 2 || !/\d/.test(this._props.records[0][ax0]) || !ax1) {
             return ax0 + ' Pie Chart';
         }
@@ -101,8 +101,6 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
         );
     }
 
-    @action
-    restoreView = (data: Doc) => {};
     // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
     getAnchor = (pinProps?: PinProps) => {
         const anchor = Docs.Create.ConfigDocument({
@@ -130,23 +128,23 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
             : validData.map((d: { [x: string]: any }) =>
                   this.byCategory
                       ? d[field] //
-                      : +d[field].replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '')
+                      : +d[field].replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '')
               );
     };
 
     // outlines the slice selected / hovered over
     highlightSelectedSlice = (changeSelectedVariables: boolean, svg: any, arc: any, radius: any, pointer: any, pieDataSet: any) => {
-        var index = -1;
-        var sameAsCurrent: boolean;
+        let index = -1;
+        let sameAsCurrent: boolean;
         const selected = svg.selectAll('.slice').filter((d: any) => {
             index++;
-            var p1 = [0, 0]; // center of pie
-            var p3 = [arc.centroid(d)[0] * 2, arc.centroid(d)[1] * 2]; // outward peak of arc
-            var p2 = [radius * Math.sin(d.startAngle), -radius * Math.cos(d.startAngle)]; // start of arc
-            var p4 = [radius * Math.sin(d.endAngle), -radius * Math.cos(d.endAngle)]; // end of arc
+            const p1 = [0, 0]; // center of pie
+            const p3 = [arc.centroid(d)[0] * 2, arc.centroid(d)[1] * 2]; // outward peak of arc
+            const p2 = [radius * Math.sin(d.startAngle), -radius * Math.cos(d.startAngle)]; // start of arc
+            const p4 = [radius * Math.sin(d.endAngle), -radius * Math.cos(d.endAngle)]; // end of arc
 
             // draw an imaginary horizontal line from the pointer to see how many times it crosses a slice edge
-            var lineCrossCount = 0;
+            let lineCrossCount = 0;
             // if for all 4 lines
             if (Math.min(p1[1], p2[1]) <= pointer[1] && pointer[1] <= Math.max(p1[1], p2[1])) {
                 // within y bounds
@@ -161,13 +159,13 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
             if (Math.min(p4[1], p1[1]) <= pointer[1] && pointer[1] <= Math.max(p4[1], p1[1])) {
                 if (pointer[0] <= ((pointer[1] - p4[1]) * (p1[0] - p4[0])) / (p1[1] - p4[1]) + p4[0]) lineCrossCount++;
             }
-            if (lineCrossCount % 2 != 0) {
+            if (lineCrossCount % 2 !== 0) {
                 // inside the slice of it crosses an odd number of edges
-                var showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index];
+                const showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index];
                 if (changeSelectedVariables) {
                     // for when a bar is selected - not just hovered over
                     sameAsCurrent = this._currSelected
-                        ? showSelected[Object.keys(showSelected)[0]] == this._currSelected![Object.keys(showSelected)[0]] && showSelected[Object.keys(showSelected)[1]] == this._currSelected![Object.keys(showSelected)[1]]
+                        ? showSelected[Object.keys(showSelected)[0]] === this._currSelected![Object.keys(showSelected)[0]] && showSelected[Object.keys(showSelected)[1]] === this._currSelected![Object.keys(showSelected)[1]]
                         : this._currSelected === showSelected;
                     this._currSelected = sameAsCurrent ? undefined : showSelected;
                     this.selectedData = sameAsCurrent ? undefined : d;
@@ -187,104 +185,100 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
         d3.select(this._piechartRef.current).select('svg').remove();
         d3.select(this._piechartRef.current).select('.tooltip').remove();
 
-        var percentField = Object.keys(dataSet[0])[0];
-        var descriptionField = Object.keys(dataSet[0])[1]!;
-        var radius = Math.min(width, height - this._props.margin.top - this._props.margin.bottom) / 2;
+        let percentField = Object.keys(dataSet[0])[0];
+        let descriptionField = Object.keys(dataSet[0])[1]!;
+        const radius = Math.min(width, height - this._props.margin.top - this._props.margin.bottom) / 2;
 
         // converts data into Objects
-        var data = this.data(dataSet);
-        var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
+        let data = this.data(dataSet);
+        let pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
         if (this.byCategory) {
-            let uniqueCategories = [...new Set(data)];
-            var pieStringDataSet: { frequency: number }[] = [];
+            const uniqueCategories = [...new Set(data)];
+            const pieStringDataSet: { frequency: number }[] = [];
             for (let i = 0; i < uniqueCategories.length; i++) {
                 pieStringDataSet.push({ frequency: 0, [percentField]: uniqueCategories[i] });
             }
             for (let i = 0; i < data.length; i++) {
-                let sliceData = pieStringDataSet.filter((each: any) => each[percentField] == data[i]);
-                sliceData[0].frequency = sliceData[0].frequency + 1;
+                // eslint-disable-next-line no-loop-func
+                const sliceData = pieStringDataSet.filter((each: any) => each[percentField] === data[i]);
+                sliceData[0].frequency += 1;
             }
             pieDataSet = pieStringDataSet;
-            percentField = Object.keys(pieDataSet[0])[0];
-            descriptionField = Object.keys(pieDataSet[0])[1]!;
+            [percentField, descriptionField] = Object.keys(pieDataSet[0]);
             data = this.data(pieStringDataSet);
         }
-        var trackDuplicates: { [key: string]: any } = {};
-        data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null));
+        let trackDuplicates: { [key: string]: any } = {};
+        data.forEach((eachData: any) => {
+            !trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null;
+        });
 
         // initial chart
-        var svg = (this._piechartSvg = d3
+        const svg = (this._piechartSvg = d3
             .select(this._piechartRef.current)
             .append('svg')
             .attr('class', 'graph')
             .attr('width', width + this._props.margin.right + this._props.margin.left)
             .attr('height', height + this._props.margin.top + this._props.margin.bottom)
             .append('g'));
-        let g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this._props.margin.left) + ',' + height / 2 + ')');
-        var pie = d3.pie();
-        var arc = d3.arc().innerRadius(0).outerRadius(radius);
+        const g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this._props.margin.left) + ',' + height / 2 + ')');
+        const pie = d3.pie();
+        const arc = d3.arc().innerRadius(0).outerRadius(radius);
 
+        const updateHighlights = () => {
+            const hoverOverSlice = this.hoverOverData;
+            const { selectedData } = this;
+            svg.selectAll('path').attr('class', (d: any) =>
+                (selectedData && d.startAngle === selectedData.startAngle && d.endAngle === selectedData.endAngle) || (hoverOverSlice && d.startAngle === hoverOverSlice.startAngle && d.endAngle === hoverOverSlice.endAngle) ? 'slice hover' : 'slice'
+            );
+        };
         // click/hover
         const onPointClick = action((e: any) => this.highlightSelectedSlice(true, svg, arc, radius, d3.pointer(e), pieDataSet));
         const onHover = action((e: any) => {
             this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet);
             updateHighlights();
         });
-        const mouseOut = action((e: any) => {
+        const mouseOut = action(() => {
             this.hoverOverData = undefined;
             updateHighlights();
         });
-        const updateHighlights = () => {
-            const hoverOverSlice = this.hoverOverData;
-            const selectedData = this.selectedData;
-            svg.selectAll('path').attr('class', function (d: any) {
-                return (selectedData && d.startAngle == selectedData.startAngle && d.endAngle == selectedData.endAngle) || (hoverOverSlice && d.startAngle == hoverOverSlice.startAngle && d.endAngle == hoverOverSlice.endAngle)
-                    ? 'slice hover'
-                    : 'slice';
-            });
-        };
 
         // drawing the slices
-        var selected = this.selectedData;
-        var arcs = g.selectAll('arc').data(pie(data)).enter().append('g');
+        const selected = this.selectedData;
+        const arcs = g.selectAll('arc').data(pie(data)).enter().append('g');
         const possibleDataPointVals: { [x: string]: any }[] = [];
         pieDataSet.forEach((each: { [x: string]: any | { valueOf(): number } }) => {
-            var dataPointVal: { [x: string]: any } = {};
+            const dataPointVal: { [x: string]: any } = {};
             dataPointVal[percentField] = each[percentField];
             if (descriptionField) dataPointVal[descriptionField] = each[descriptionField];
             try {
-                dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, ''));
-            } catch (error) {}
+                dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, ''));
+            } catch (error) {
+                /* empty */
+            }
             possibleDataPointVals.push(dataPointVal);
         });
         const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
         arcs.append('path')
             .attr('fill', (d, i) => {
-                var dataPoint;
+                let dataPoint;
                 const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
-                if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0];
+                if (possibleDataPoints.length === 1) [dataPoint] = possibleDataPoints;
                 else {
                     dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]];
                     trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1;
                 }
-                var sliceColor;
+                let sliceColor;
                 if (dataPoint) {
                     const sliceTitle = dataPoint[this._props.axes[0]];
-                    const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
-                    sliceColors.forEach(each => each[0] == accessByName && (sliceColor = each[1]));
+                    const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
+                    sliceColors.forEach(each => {
+                        // eslint-disable-next-line prefer-destructuring
+                        each[0] === accessByName && (sliceColor = each[1]);
+                    });
                 }
                 return sliceColor ? StrCast(sliceColor) : d3.schemeSet3[i] ? d3.schemeSet3[i] : d3.schemeSet3[i % d3.schemeSet3.length];
             })
-            .attr(
-                'class',
-                selected
-                    ? function (d) {
-                          return selected && d.startAngle == selected.startAngle && d.endAngle == selected.endAngle ? 'slice hover' : 'slice';
-                      }
-                    : function (d) {
-                          return 'slice';
-                      }
-            )
+            .attr('class', selected ? d => (selected && d.startAngle === selected.startAngle && d.endAngle === selected.endAngle ? 'slice hover' : 'slice') : () => 'slice')
             // @ts-ignore
             .attr('d', arc)
             .on('click', onPointClick)
@@ -293,20 +287,22 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
 
         // adding labels
         trackDuplicates = {};
-        data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null));
+        data.forEach((eachData: any) => {
+            !trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null;
+        });
         arcs.size() < 100 &&
             arcs
                 .append('text')
-                .attr('transform', function (d) {
-                    var centroid = arc.centroid(d as unknown as d3.DefaultArcObject);
-                    var heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]);
+                .attr('transform', d => {
+                    const centroid = arc.centroid(d as unknown as d3.DefaultArcObject);
+                    const heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]);
                     return 'translate(' + (centroid[0] + centroid[0] / (radius * 0.02)) + ',' + (centroid[1] + heightOffset) + ')';
                 })
                 .attr('text-anchor', 'middle')
-                .text(function (d) {
-                    var dataPoint;
+                .text(d => {
+                    let dataPoint;
                     const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
-                    if (possibleDataPoints.length == 1) dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[0])];
+                    if (possibleDataPoints.length === 1) dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[0])];
                     else {
                         dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[trackDuplicates[d.data.toString()]])];
                         trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1;
@@ -318,11 +314,11 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
     @action changeSelectedColor = (color: string) => {
         this.curSliceSelected.attr('fill', color);
         const sliceTitle = this._currSelected[this._props.axes[0]];
-        const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
+        const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
 
         const sliceColors = Cast(this._props.layoutDoc.dataViz_pie_sliceColors, listSpec('string'), null);
-        sliceColors.map(each => {
-            if (each.split('::')[0] == sliceName) sliceColors.splice(sliceColors.indexOf(each), 1);
+        sliceColors.forEach(each => {
+            if (each.split('::')[0] === sliceName) sliceColors.splice(sliceColors.indexOf(each), 1);
         });
         sliceColors.push(StrCast(sliceName + '::' + color));
     };
@@ -333,38 +329,39 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
     };
 
     render() {
-        var titleAccessor: any = 'dataViz_pie_title';
-        if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
-        else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
+        let titleAccessor: any = 'dataViz_pie_title';
+        if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+        else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
         if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
         if (!this._props.layoutDoc.dataViz_pie_sliceColors) this._props.layoutDoc.dataViz_pie_sliceColors = new List<string>();
-        var selected: string;
-        var curSelectedSliceName = '';
+        let selected: string;
+        let curSelectedSliceName = '';
         if (this._currSelected) {
             selected = '{ ';
             const sliceTitle = this._currSelected[this._props.axes[0]];
-            curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
-            Object.keys(this._currSelected).map(key => {
-                key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
+            curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
+            Object.keys(this._currSelected).forEach(key => {
+                key !== '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
             });
             selected = selected.substring(0, selected.length - 2);
             selected += ' }';
-            if (this._props.titleCol != '' && (!this._currSelected['frequency'] || this._currSelected['frequency'] < 10)) {
+            if (this._props.titleCol !== '' && (!this._currSelected.frequency || this._currSelected.frequency < 10)) {
                 selected += '\n' + this._props.titleCol + ': ';
                 this._tableData.forEach(each => {
-                    if (this._currSelected[this._props.axes[0]] == each[this._props.axes[0]]) {
+                    if (this._currSelected[this._props.axes[0]] === each[this._props.axes[0]]) {
                         if (this._props.axes[1]) {
-                            if (this._currSelected[this._props.axes[1]] == each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', ';
+                            if (this._currSelected[this._props.axes[1]] === each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', ';
                         } else selected += each[this._props.titleCol] + ', ';
                     }
                 });
                 selected = selected.slice(0, -1).slice(0, -1);
             }
         } else selected = 'none';
-        var selectedSliceColor;
-        var sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
+        let selectedSliceColor;
+        const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
         sliceColors.forEach(each => {
-            if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1];
+            // eslint-disable-next-line prefer-destructuring
+            if (each[0] === curSelectedSliceName!) selectedSliceColor = each[1];
         });
 
         if (this._pieChartData.length > 0 || !this.parentViz) {
@@ -374,30 +371,32 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
                         <EditableText
                             val={StrCast(this._props.layoutDoc[titleAccessor])}
                             setVal={undoable(
-                                action(val => (this._props.layoutDoc[titleAccessor] = val as string)),
+                                action(val => {
+                                    this._props.layoutDoc[titleAccessor] = val as string;
+                                }),
                                 'Change Graph Title'
                             )}
-                            color={'black'}
+                            color="black"
                             size={Size.LARGE}
                             fillWidth
                         />
                     </div>
                     {this._props.axes.length === 1 && /\d/.test(this._props.records[0][this._props.axes[0]]) ? (
-                        <div className={'asHistogram-checkBox'} style={{ width: this._props.width }}>
+                        <div className="asHistogram-checkBox" style={{ width: this._props.width }}>
                             <Checkbox color="primary" onChange={this.changeHistogramCheckBox} checked={this._props.layoutDoc.dataViz_pie_asHistogram as boolean} />
                             Organize data as histogram
                         </div>
                     ) : null}
                     <div ref={this._piechartRef} />
-                    {selected != 'none' ? (
-                        <div className={'selected-data'}>
+                    {selected !== 'none' ? (
+                        <div className="selected-data">
                             Selected: {selected}
                             &nbsp; &nbsp;
                             <ColorPicker
-                                tooltip={'Change Slice Color'}
+                                tooltip="Change Slice Color"
                                 type={Type.SEC}
                                 icon={<FaFillDrip />}
-                                selectedColor={selectedSliceColor ? selectedSliceColor : this.curSliceSelected.attr('fill')}
+                                selectedColor={selectedSliceColor || this.curSliceSelected.attr('fill')}
                                 setFinalColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Slice Color')}
                                 setSelectedColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Slice Color')}
                                 size={Size.XSMALL}
@@ -406,12 +405,12 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
                     ) : null}
                 </div>
             ) : (
-                <span className="chart-container"> {'first use table view to select a column to graph'}</span>
-            );
-        } else
-            return (
-                // when it is a brushed table and the incoming table doesn't have any rows selected
-                <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+                <span className="chart-container"> first use table view to select a column to graph</span>
             );
+        }
+        return (
+            // when it is a brushed table and the incoming table doesn't have any rows selected
+            <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+        );
     }
 }
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
index 336935d23..be05c3529 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -5,11 +5,11 @@ import { DataPoint } from '../components/LineChart';
 
 export const minMaxRange = (dataPts: DataPoint[][]) => {
     // find the max and min of all the data points
-    const yMin = d3.min(dataPts, d => d3.min(d, d => Number(d.y)));
-    const yMax = d3.max(dataPts, d => d3.max(d, d => Number(d.y)));
+    const yMin = d3.min(dataPts, d => d3.min(d, m => Number(m.y)));
+    const yMax = d3.max(dataPts, d => d3.max(d, m => Number(m.y)));
 
-    const xMin = d3.min(dataPts, d => d3.min(d, d => Number(d.x)));
-    const xMax = d3.max(dataPts, d => d3.max(d, d => Number(d.x)));
+    const xMin = d3.min(dataPts, d => d3.min(d, m => Number(m.x)));
+    const xMax = d3.max(dataPts, d => d3.max(d, m => Number(m.x)));
 
     return { xMin, xMax, yMin, yMax };
 };
@@ -20,18 +20,15 @@ export const scaleCreatorCategorical = (labels: string[], range: number[]) => {
     return scale;
 };
 
-export const scaleCreatorNumerical = (domA: number, domB: number, rangeA: number, rangeB: number) => {
-    return d3.scaleLinear().domain([domA, domB]).range([rangeA, rangeB]);
-};
+export const scaleCreatorNumerical = (domA: number, domB: number, rangeA: number, rangeB: number) => d3.scaleLinear().domain([domA, domB]).range([rangeA, rangeB]);
 
-export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) => {
+export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) =>
     // TODO: nda - look into the different types of curves
-    return d3
+    d3
         .line<DataPoint>()
         .x(d => xScale(d.x))
         .y(d => yScale(d.y))
         .curve(d3.curveMonotoneX);
-};
 
 export const xAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, height: number, xScale: d3.ScaleLinear<number, number, never>) => {
     g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15));
@@ -48,7 +45,7 @@ export const xGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, he
             d3
                 .axisBottom(scale)
                 .tickSize(-height)
-                .tickFormat((a, b) => '')
+                .tickFormat((/* a, b */) => '')
         );
 };
 
@@ -57,10 +54,16 @@ export const yGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, wi
         d3
             .axisLeft(scale)
             .tickSize(-width)
-            .tickFormat((a, b) => '')
+            .tickFormat((/* a, b */) => '')
     );
 };
 
 export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>, extra: boolean) => {
-    p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('stroke', extra? 'blue' : 'black').attr('class', 'line').attr('d', lineGen);
+    p.datum(dataPts)
+        .attr('fill', 'none')
+        .attr('stroke', 'rgba(53, 162, 235, 0.5)')
+        .attr('stroke-width', 2)
+        .attr('stroke', extra ? 'blue' : 'black')
+        .attr('class', 'line')
+        .attr('d', lineGen);
 };
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index a029b3761..9b4e36509 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -92,7 +92,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
             e,
             this.onLinkButtonMoved,
             emptyFunction,
-            action((e, doubleTap) => {
+            action((clickEv, doubleTap) => {
                 doubleTap && DocumentView.showBackLinks(this._props.View.Document);
             }),
             undefined,
@@ -109,7 +109,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
             e,
             this.onLinkButtonMoved,
             emptyFunction,
-            action((e, doubleTap) => {
+            action((clickEv, doubleTap) => {
                 if (doubleTap && this._props.InMenu && this._props.StartLink) {
                     // action(() => Doc.BrushDoc(this._props.View.Document));
                     if (DocumentLinksButton.StartLink === this._props.View.Document) {
@@ -146,7 +146,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
             e,
             returnFalse,
             emptyFunction,
-            action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View))
+            action(clickEv => DocumentLinksButton.finishLinkClick(clickEv.clientX, clickEv.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View))
         );
     };
 
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 9be66ba4a..9c216cba4 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { action, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -42,7 +43,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     this._ref.current!.mathField.latex(text);
                 }
             }
-            //{ fireImmediately: true }
+            // { fireImmediately: true }
         );
         reaction(
             () => this._props.isSelected(),
@@ -88,7 +89,9 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (e.key === 'Backspace' && !this.dataDoc.text) this._props.removeDocument?.(this.Document);
     };
     @undoBatch
-    onChange = (str: string) => (this.dataDoc.text = str);
+    onChange = (str: string) => {
+        this.dataDoc.text = str;
+    };
 
     updateSize = () => {
         const style = this._ref.current && getComputedStyle(this._ref.current.element.current);
@@ -111,7 +114,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
         return (
             <div
-                ref={r => this.updateSize()}
+                ref={() => this.updateSize()}
                 className="equationBox-cont"
                 onPointerDown={e => !e.ctrlKey && e.stopPropagation()}
                 style={{
@@ -122,7 +125,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     fontSize: StrCast(this.layoutDoc._text_fontSize),
                 }}
                 onKeyDown={e => e.stopPropagation()}>
-                <EquationEditor ref={this._ref} value={StrCast(this.dataDoc.text, 'x')} spaceBehavesLikeTab={true} onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" />
+                <EquationEditor ref={this._ref} value={StrCast(this.dataDoc.text, 'x')} spaceBehavesLikeTab onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" />
             </div>
         );
     }
diff --git a/src/client/views/nodes/FaceRectangle.tsx b/src/client/views/nodes/FaceRectangle.tsx
index 46bc6eb03..2b66b83fe 100644
--- a/src/client/views/nodes/FaceRectangle.tsx
+++ b/src/client/views/nodes/FaceRectangle.tsx
@@ -8,11 +8,17 @@ export default class FaceRectangle extends React.Component<{ rectangle: Rectangl
     @observable private opacity = 0;
 
     componentDidMount() {
-        setTimeout(() => runInAction(() => (this.opacity = 1)), 500);
+        setTimeout(
+            () =>
+                runInAction(() => {
+                    this.opacity = 1;
+                }),
+            500
+        );
     }
 
     render() {
-        const rectangle = this.props.rectangle;
+        const { rectangle } = this.props;
         return (
             <div
                 style={{
diff --git a/src/client/views/nodes/FontIconBox/ButtonInterface.ts b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
index 1c034bfbe..0d0d7b1c3 100644
--- a/src/client/views/nodes/FontIconBox/ButtonInterface.ts
+++ b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
@@ -1,5 +1,5 @@
-import { Doc } from '../../../../fields/Doc';
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { Doc } from '../../../../fields/Doc';
 import { ButtonType } from './FontIconBox';
 
 export interface IButtonProps {
diff --git a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx
index 09fd6e3ae..76f00b2f4 100644
--- a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx
+++ b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx
@@ -1,10 +1,11 @@
 import * as React from 'react';
 
-const TrailsIcon = (fill: string) => (
-    <svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 1080.000000 1080.000000" preserveAspectRatio="xMidYMid meet">
-        <g transform="translate(0.000000,1080.000000) scale(0.100000,-0.100000)" fill={fill} stroke="none">
-            <path
-                d="M665 9253 c-74 -10 -157 -38 -240 -81 -74 -37 -107 -63 -186 -141
+function TrailsIcon(fill: string) {
+    return (
+        <svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 1080.000000 1080.000000" preserveAspectRatio="xMidYMid meet">
+            <g transform="translate(0.000000,1080.000000) scale(0.100000,-0.100000)" fill={fill} stroke="none">
+                <path
+                    d="M665 9253 c-74 -10 -157 -38 -240 -81 -74 -37 -107 -63 -186 -141
 -104 -104 -156 -191 -201 -334 l-23 -72 0 -3215 c0 -3072 1 -3218 18 -3280 10
 -36 39 -108 64 -160 40 -82 59 -107 142 -190 81 -81 111 -103 191 -143 52 -26
 122 -55 155 -65 57 -16 322 -17 4775 -20 3250 -2 4736 1 4784 8 256 39 486
@@ -14,68 +15,69 @@ const TrailsIcon = (fill: string) => (
 -62 -101 -108 -126 l-42 -22 -4435 -3 c-3954 -2 -4440 0 -4481 13 -26 9 -63
 33 -87 56 -79 79 -72 -205 -72 3012 0 2156 3 2889 12 2918 20 70 91 136 168
 160 14 4 2010 8 4436 8 3710 1 4418 -1 4456 -13z"
-            />
-            <path
-                d="M7692 7839 c-46 -14 -109 -80 -122 -128 -7 -27 -9 -472 -8 -1443 l3
+                />
+                <path
+                    d="M7692 7839 c-46 -14 -109 -80 -122 -128 -7 -27 -9 -472 -8 -1443 l3
 -1403 24 -38 c13 -21 42 -50 64 -65 l41 -27 816 0 816 0 41 27 c22 15 51 44
 64 65 l24 38 0 1425 0 1425 -24 38 c-13 21 -42 50 -64 65 l-41 27 -800 2
 c-488 1 -814 -2 -834 -8z"
-            />
-            <path
-                d="M1982 7699 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -308 -8 -893 l3
+                />
+                <path
+                    d="M1982 7699 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -308 -8 -893 l3
 -853 24 -38 c13 -21 42 -50 64 -65 l41 -27 1386 0 1386 0 41 27 c22 15 51 44
 64 65 l24 38 0 876 0 875 -27 41 c-15 22 -44 51 -65 64 l-38 24 -1370 2 c-847
 1 -1383 -2 -1403 -8z"
-            />
-            <path
-                d="M6413 7093 c-13 -2 -23 -9 -23 -15 0 -24 21 -307 26 -343 l5 -40 182
+                />
+                <path
+                    d="M6413 7093 c-13 -2 -23 -9 -23 -15 0 -24 21 -307 26 -343 l5 -40 182
 -1 c200 -1 307 -15 484 -65 57 -16 107 -29 112 -29 5 0 36 75 69 168 33 92 63
 175 67 184 6 14 -10 22 -92 48 -126 39 -308 76 -447 89 -106 11 -337 13 -383
 4z"
-            />
-            <path
-                d="M5840 7033 c-63 -8 -238 -29 -388 -47 -150 -18 -274 -35 -276 -37 -2
+                />
+                <path
+                    d="M5840 7033 c-63 -8 -238 -29 -388 -47 -150 -18 -274 -35 -276 -37 -2
 -2 8 -89 23 -194 22 -163 29 -190 44 -193 10 -2 91 6 180 17 89 12 258 32 376
 46 118 14 216 27 218 28 7 8 -43 391 -52 392 -5 1 -62 -4 -125 -12z"
-            />
-            <path
-                d="M4762 4789 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -323 -8 -943 l3
+                />
+                <path
+                    d="M4762 4789 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -323 -8 -943 l3
 -903 24 -38 c13 -21 42 -50 64 -65 l41 -27 926 0 926 0 41 27 c22 15 51 44 64
 65 l24 38 0 926 0 925 -27 41 c-15 22 -44 51 -65 64 l-38 24 -910 2 c-557 1
 -923 -2 -943 -8z"
-            />
-            <path
-                d="M8487 4297 c-26 -215 -161 -474 -307 -585 -27 -20 -49 -40 -49 -44
+                />
+                <path
+                    d="M8487 4297 c-26 -215 -161 -474 -307 -585 -27 -20 -49 -40 -49 -44
 -1 -3 49 -79 110 -167 l110 -161 44 31 c176 126 333 350 418 594 30 86 77 282
 77 320 0 8 -57 19 -167 34 -93 13 -182 25 -199 28 -31 5 -31 5 -37 -50z"
-            />
-            <path
-                d="M3965 4233 c-106 -9 -348 -36 -415 -47 -55 -8 -75 -15 -74 -26 1 -20
+                />
+                <path
+                    d="M3965 4233 c-106 -9 -348 -36 -415 -47 -55 -8 -75 -15 -74 -26 1 -20
 56 -374 59 -377 1 -2 46 4 101 12 159 24 409 45 526 45 l108 0 0 200 0 200
 -132 -2 c-73 -1 -151 -3 -173 -5z"
-            />
-            <path
-                d="M3020 4079 c-85 -23 -292 -94 -368 -125 -97 -40 -298 -140 -305 -151
+                />
+                <path
+                    d="M3020 4079 c-85 -23 -292 -94 -368 -125 -97 -40 -298 -140 -305 -151
 -5 -7 172 -315 192 -336 4 -4 41 10 82 32 103 55 272 123 414 165 66 20 125
 38 132 41 11 4 -4 70 -78 348 -10 39 -14 41 -69 26z"
-            />
-            <path
-                d="M6955 3538 c-21 -91 -74 -362 -72 -364 7 -7 260 -44 367 -54 146 -13
+                />
+                <path
+                    d="M6955 3538 c-21 -91 -74 -362 -72 -364 7 -7 260 -44 367 -54 146 -13
 359 -13 475 0 49 6 90 12 91 13 2 1 -12 90 -29 197 -26 155 -36 194 -47 192
 -8 -2 -85 -6 -170 -9 -160 -6 -357 7 -505 33 -103 18 -104 18 -110 -8z"
-            />
-            <path
-                d="M1993 3513 c-52 -67 -71 -106 -98 -198 -35 -122 -44 -284 -21 -415 9
+                />
+                <path
+                    d="M1993 3513 c-52 -67 -71 -106 -98 -198 -35 -122 -44 -284 -21 -415 9
 -51 18 -96 21 -98 4 -5 360 79 375 88 7 4 7 24 0 60 -21 109 -7 244 31 307
 l20 31 -146 131 c-80 72 -147 131 -149 131 -2 0 -17 -17 -33 -37z"
-            />
-            <path
-                d="M2210 2519 c-91 -50 -166 -92 -168 -94 -2 -1 11 -26 28 -54 l32 -51
+                />
+                <path
+                    d="M2210 2519 c-91 -50 -166 -92 -168 -94 -2 -1 11 -26 28 -54 l32 -51
 244 0 c134 0 244 2 244 5 0 3 -23 33 -51 67 -28 35 -72 98 -97 140 -26 43 -51
 77 -57 77 -5 0 -84 -41 -175 -90z"
-            />
-        </g>
-    </svg>
-);
+                />
+            </g>
+        </svg>
+    );
+}
 
 export default TrailsIcon;
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index bff6d53da..d43241de0 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -45,7 +45,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     onPointerDown = (e: React.PointerEvent) => {
         const { linkSource } = this;
         linkSource &&
-            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
+            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (clickEv, doubleTap) => {
                 if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false);
                 else this._props.select(false);
             });
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 2593491cc..f01905ee1 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -50,15 +51,19 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this._props.setContentViewBox?.(this);
         this._disposers.deleting = reaction(
             () => !this.anchor1 && !this.anchor2 && this.DocumentView?.() && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView!())),
-            empty => empty && ((this._hackToSeeIfDeleted = setTimeout(() => 
-                (!this.anchor1 && !this.anchor2) && this._props.removeDocument?.(this.Document)
-            )), 1000) // prettier-ignore
+            empty => {
+                if (empty) {
+                    this._hackToSeeIfDeleted = setTimeout(() => {
+                        !this.anchor1 && !this.anchor2 && this._props.removeDocument?.(this.Document);
+                    }, 1000);
+                }
+            }
         );
         this._disposers.dragging = reaction(
             () => SnappingManager.IsDragging,
             () => setTimeout( action(() => {// need to wait for drag manager to set 'hidden' flag on dragged DOM elements
-                const a = this.anchor1,
-                      b = this.anchor2;
+                const a = this.anchor1;
+                const b = this.anchor2;
                 let a1 = a && document.getElementById(a.ViewGuid);
                 let a2 = b && document.getElementById(b.ViewGuid);
                 // test whether the anchors themselves are hidden,...
@@ -67,7 +72,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     // .. or whether any of their DOM parents are hidden
                     for (; a1 && !a1.hidden; a1 = a1.parentElement);
                     for (; a2 && !a2.hidden; a2 = a2.parentElement);
-                    this._hide = a1 || a2 ? true : false;
+                    this._hide = !!(a1 || a2);
                 }
             })) // prettier-ignore
         );
@@ -92,24 +97,25 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             a.Document[DocCss];
             b.Document[DocCss];
 
+            // eslint-disable-next-line @typescript-eslint/no-unused-vars
             const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove)
             const bxf = b.screenToViewTransform();
             const scale = docView?.screenToViewTransform().Scale ?? 1;
             const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
             const bt = b.getBounds?.transition; //    inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
 
-            var foundParent = false;
+            let foundParent = false;
             const getAnchor = (field: FieldResult): Element[] => {
                 const docField = DocCast(field);
                 const doc = docField?.layout_unrendered ? DocCast(docField.annotationOn, docField) : docField;
                 const ele = document.getElementById(DocumentView.UniquifyId(LightboxView.Contains(this.DocumentView?.()), doc[Id]));
                 if (ele?.className === 'linkBox-label') foundParent = true;
                 if (ele?.getBoundingClientRect().width) return [ele];
-                const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(ele => ele?.getBoundingClientRect().width);
+                const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(el => el?.getBoundingClientRect().width);
                 const annoOn = DocCast(doc.annotationOn);
                 if (eles.length || !annoOn) return eles;
                 const pareles = getAnchor(annoOn);
-                foundParent = pareles.length ? true : false;
+                foundParent = !!pareles.length;
                 return pareles;
             };
             //   if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>),
@@ -122,26 +128,38 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const aid = targetAhyperlinks?.find(alink => container?.contains(alink))?.id ?? targetAhyperlinks?.lastElement()?.id;
             const bid = targetBhyperlinks?.find(blink => container?.contains(blink))?.id ?? targetBhyperlinks?.lastElement()?.id;
             if (!aid || !bid) {
-                setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
+                setTimeout(
+                    action(() => {
+                        this._forceAnimate += 0.01;
+                    })
+                );
                 return null;
             }
             if (foundParent) {
                 setTimeout(
-                    action(() => (this._forceAnimate = this._forceAnimate + 0.01)),
+                    action(() => {
+                        this._forceAnimate += 0.01;
+                    }),
                     1
                 );
             }
 
-            if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
+            if (at || bt)
+                setTimeout(
+                    action(() => {
+                        this._forceAnimate += 0.01;
+                    })
+                ); // this forces an update during a transition animation
             const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
             const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
             const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
             const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
             const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
             const fontColor = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor));
-            const { stroke_markerScale, stroke_width, stroke_startMarker, stroke_endMarker, stroke_dash } = this.Document;
+            // eslint-disable-next-line camelcase
+            const { stroke_markerScale: strokeMarkerScale, stroke_width: strokeRawWidth, stroke_startMarker: strokeStartMarker, stroke_endMarker: strokeEndMarker, stroke_dash: strokeDash } = this.Document;
 
-            const strokeWidth = NumCast(stroke_width, 4);
+            const strokeWidth = NumCast(strokeRawWidth, 4);
             const linkDesc = StrCast(this.dataDoc.link_description) || ' ';
             const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '');
             return (
@@ -152,12 +170,12 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             start={aid}
                             end={bid} //
                             strokeWidth={strokeWidth + Math.max(2, strokeWidth * 0.1)}
-                            showHead={stroke_startMarker ? true : false}
-                            showTail={stroke_endMarker ? true : false}
-                            headSize={NumCast(stroke_markerScale, 3)}
-                            tailSize={NumCast(stroke_markerScale, 3)}
-                            tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
-                            headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+                            showHead={!!strokeStartMarker}
+                            showTail={!!strokeEndMarker}
+                            headSize={NumCast(strokeMarkerScale, 3)}
+                            tailSize={NumCast(strokeMarkerScale, 3)}
+                            tailShape={strokeEndMarker === 'dot' ? 'circle' : 'arrow1'}
+                            headShape={strokeStartMarker === 'dot' ? 'circle' : 'arrow1'}
                             color={highlightColor}
                         />
                     )}
@@ -166,23 +184,23 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         start={aid}
                         end={bid} //
                         strokeWidth={strokeWidth}
-                        dashness={Number(stroke_dash) ? true : false}
-                        showHead={stroke_startMarker ? true : false}
-                        showTail={stroke_endMarker ? true : false}
-                        headSize={NumCast(stroke_markerScale, 3)}
-                        tailSize={NumCast(stroke_markerScale, 3)}
-                        tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
-                        headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+                        dashness={!!Number(strokeDash)}
+                        showHead={!!strokeStartMarker}
+                        showTail={!!strokeEndMarker}
+                        headSize={NumCast(strokeMarkerScale, 3)}
+                        tailSize={NumCast(strokeMarkerScale, 3)}
+                        tailShape={strokeEndMarker === 'dot' ? 'circle' : 'arrow1'}
+                        headShape={strokeStartMarker === 'dot' ? 'circle' : 'arrow1'}
                         color={color}
                         labels={
                             <div
                                 id={this.DocumentView?.().DocUniqueId}
-                                className={'linkBox-label'}
+                                className="linkBox-label"
                                 style={{
                                     borderRadius: '8px',
                                     pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
                                     fontSize,
-                                    fontFamily /*, fontStyle: 'italic'*/,
+                                    fontFamily /* , fontStyle: 'italic' */,
                                     color: fontColor || lightOrDark(DashColor(color).fade(0.5).toString()),
                                     paddingLeft: 4,
                                     paddingRight: 4,
@@ -223,16 +241,19 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
 
         setTimeout(
-            action(() => (this._forceAnimate = this._forceAnimate + 1)),
+            action(() => {
+                this._forceAnimate += 1;
+            }),
             2
         );
         return (
             <div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
                 <ComparisonBox
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this.props} //
                     fieldKey="link_anchor"
                     setHeight={emptyFunction}
-                    dontRegisterView={true}
+                    dontRegisterView
                     renderDepth={this._props.renderDepth + 1}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 2a96ce458..23cb25962 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -9,6 +9,7 @@ import { StrCast } from '../../../fields/Types';
 
 @observer
 export class LinkDescriptionPopup extends React.Component<{}> {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: LinkDescriptionPopup;
     @observable public display: boolean = false;
     @observable public showDescriptions: string = 'ON';
@@ -23,6 +24,20 @@ export class LinkDescriptionPopup extends React.Component<{}> {
         LinkDescriptionPopup.Instance = this;
     }
 
+    componentDidMount() {
+        document.addEventListener('pointerdown', this.onClick, true);
+        reaction(
+            () => this.display,
+            display => {
+                display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description));
+            }
+        );
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener('pointerdown', this.onClick, true);
+    }
+
     @action
     descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.description = e.currentTarget.value;
@@ -39,25 +54,13 @@ export class LinkDescriptionPopup extends React.Component<{}> {
 
     @action
     onClick = (e: PointerEvent) => {
-        if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
+        if (this.popupRef && !this.popupRef.current?.contains(e.target as any)) {
             this.display = false;
             this.description = '';
             TaskCompletionBox.taskCompleted = false;
         }
     };
 
-    componentDidMount() {
-        document.addEventListener('pointerdown', this.onClick, true);
-        reaction(
-            () => this.display,
-            display => display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description))
-        );
-    }
-
-    componentWillUnmount() {
-        document.removeEventListener('pointerdown', this.onClick, true);
-    }
-
     render() {
         return !this.display ? null : (
             <div
@@ -78,11 +81,11 @@ export class LinkDescriptionPopup extends React.Component<{}> {
                     onChange={e => this.descriptionChanged(e)}
                 />
                 <div className="linkDescriptionPopup-btn">
-                    <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={e => this.onDismiss(false)}>
+                    <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={() => this.onDismiss(false)}>
                         {' '}
                         Dismiss{' '}
                     </div>
-                    <div className="linkDescriptionPopup-btn-add" onPointerDown={e => this.onDismiss(true)}>
+                    <div className="linkDescriptionPopup-btn-add" onPointerDown={() => this.onDismiss(true)}>
                         {' '}
                         Add{' '}
                     </div>
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 539daf0bd..a9cfe6c0e 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -23,35 +23,41 @@ import { DocumentView, OpenWhere } from './DocumentView';
 import { StyleProviderFuncType } from './FieldView';
 import './LinkDocPreview.scss';
 
+interface LinkDocPreviewProps {
+    linkDoc?: Doc;
+    linkSrc?: Doc;
+    DocumentView?: () => DocumentView;
+    styleProvider?: StyleProviderFuncType;
+    location: number[];
+    hrefs?: string[];
+    showHeader?: boolean;
+    noPreview?: boolean;
+}
 export class LinkInfo {
+    // eslint-disable-next-line no-use-before-define
     private static _instance: Opt<LinkInfo>;
     constructor() {
         LinkInfo._instance = this;
         makeObservable(this);
     }
+    // eslint-disable-next-line no-use-before-define
     @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));
+        runInAction(() => {
+            LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = undefined);
+        });
     }
     public static SetLinkInfo(info?: LinkDocPreviewProps) {
-        runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info));
+        runInAction(() => {
+            LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info);
+        });
     }
 }
 
-interface LinkDocPreviewProps {
-    linkDoc?: Doc;
-    linkSrc?: Doc;
-    DocumentView?: () => DocumentView;
-    styleProvider?: StyleProviderFuncType;
-    location: number[];
-    hrefs?: string[];
-    showHeader?: boolean;
-    noPreview?: boolean;
-}
 @observer
 export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps> {
     _infoRef = React.createRef<HTMLDivElement>();
@@ -69,13 +75,13 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
 
     @action
     init() {
-        var linkTarget = this._props.linkDoc;
+        let 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) {
-            linkTarget = Doc.AreProtosEqual(link_anchor_1, this._linkSrc) || Doc.AreProtosEqual(link_anchor_1?.annotationOn as Doc, this._linkSrc) ? link_anchor_2 : link_anchor_1;
+        const linkAnchor1 = DocCast(this._linkDoc?.link_anchor_1);
+        const linkAnchor2 = DocCast(this._linkDoc?.link_anchor_2);
+        if (linkAnchor1 && linkAnchor2) {
+            linkTarget = Doc.AreProtosEqual(linkAnchor1, this._linkSrc) || Doc.AreProtosEqual(linkAnchor1?.annotationOn as Doc, this._linkSrc) ? linkAnchor2 : linkAnchor1;
         }
         if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) {
             linkTarget = DocCast(linkTarget.annotationOn); // want to show annotation embedContainer document if annotation is not text
@@ -111,7 +117,13 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                 if (href.startsWith('https://en.wikipedia.org/wiki/')) {
                     wiki()
                         .page(href.replace('https://en.wikipedia.org/wiki/', ''))
-                        .then(page => page.summary().then(action(summary => (this._toolTipText = summary.substring(0, 500)))));
+                        .then(page =>
+                            page.summary().then(
+                                action(summary => {
+                                    this._toolTipText = summary.substring(0, 500);
+                                })
+                            )
+                        );
                 } else {
                     this._toolTipText = 'url => ' + href;
                 }
@@ -133,7 +145,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                                 this._linkSrc = anchor;
                                 const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
                                 this._markerTargetDoc = linkTarget;
-                                this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+                                this._targetDoc = /* linkTarget?.type === DocumentType.MARKER && */ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
                             }
                             if (LinkInfo.Instance?.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink();
                         }
@@ -194,7 +206,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                 willPan: true,
                 zoomTime: 500,
             });
-            //this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
+            // this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
         }
     };
 
@@ -249,9 +261,9 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                         setupMoveUpEvents(
                             this,
                             e,
-                            (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);
+                            (moveEv, down) => {
+                                if (Math.abs(moveEv.clientX - down[0]) + Math.abs(moveEv.clientY - down[1]) > 100) {
+                                    DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), moveEv.pageX, moveEv.pageY);
                                     LinkInfo.Clear();
                                     return true;
                                 }
@@ -284,18 +296,18 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                             removeDocument={returnFalse}
                             addDocTab={returnFalse}
                             pinToPres={returnFalse}
-                            dontRegisterView={true}
+                            dontRegisterView
                             childFilters={returnEmptyFilter}
                             childFiltersByRanges={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
                             renderDepth={0}
-                            suppressSetHeight={true}
+                            suppressSetHeight
                             PanelWidth={this.width}
                             PanelHeight={this.height}
                             pointerEvents={returnNone}
                             focus={emptyFunction}
                             whenChildContentsActiveChanged={returnFalse}
-                            ignoreAutoHeight={true} // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size.
+                            ignoreAutoHeight // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size.
                             NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
                             NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
                         />
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index adccc9db6..501831bca 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -60,7 +60,9 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         } else {
             const updateFunc = async () => {
                 const result = await Networking.QueryYoutubeProgress(StrCast(this.Document[Id])); // We use the guid of the overwriteDoc to track file uploads.
-                runInAction(() => (this.progress = result.progress));
+                runInAction(() => {
+                    this.progress = result.progress;
+                });
                 !this.Document.loadingError && this._timer && (this._timer = setTimeout(updateFunc, 1000));
             };
             this._timer = setTimeout(updateFunc, 1000);
diff --git a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
index d54a175b2..f4ece627f 100644
--- a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
+++ b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
@@ -1,35 +1,44 @@
-import * as React from "react";
+import * as React from 'react';
 
 export const slowSpeedIcon: JSX.Element = (
     <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 435.62">
         <defs>
-        <style type="text/css">
-            {`
+            <style type="text/css">
+                {`
                 .fil0 { fill: black; fill-rule: nonzero; }
                 .fil1 { fill: #FE0000; fill-rule: nonzero; }
             `}
-        </style>
+            </style>
         </defs>
-        <path className="fil0" d="M174.84 343.06c-7.31,-13.12 -13.03,-27.28 -16.89,-42.18 -3.76,-14.56 -5.77,-29.71 -5.77,-45.17 0,-11.94 1.19,-23.66 3.43,-35.03 2.29,-11.57 5.74,-22.83 10.2,-33.63 13.7,-33.14 37.01,-61.29 66.42,-80.96 25.38,-16.96 55.28,-27.66 87.45,-29.87l0 -30.17c0,-0.46 0.02,-0.92 0.06,-1.37l-33.7 0c-5.53,0 -10.05,-4.52 -10.05,-10.04l0 -24.59c0,-5.53 4.52,-10.05 10.05,-10.05l101.27 0c5.53,0 10.05,4.52 10.05,10.05l0 24.59c0,5.52 -4.52,10.04 -10.05,10.04l-33.69 0c0.03,0.45 0.05,0.91 0.05,1.37l0 31.03 -0.1 0c41.1,4.89 77.94,23.63 105.73,51.42 32.56,32.55 52.7,77.54 52.7,127.21 0,49.67 -20.14,94.66 -52.7,127.21 -32.55,32.55 -77.54,52.7 -127.21,52.7 -33.16,0 -64.29,-9.04 -91.05,-24.78 -27.66,-16.27 -50.59,-39.73 -66.2,-67.78zm148.42 -36.62l-80.33 0 0 -25.71 28.6 0 0 -42.57 -28.6 1.93 0 -25.71 36.95 -8.35 25.38 0 0 74.7 18 0 0 25.71zm44.34 -100.41l11.08 26.83 1.61 0 11.09 -26.83 34.86 0 -22.33 48.52 22.33 51.89 -35.67 0 -12.05 -28.92 -1.44 0 -11.89 28.92 -34.06 0 21.85 -50.93 -21.85 -49.48 36.47 0zm126.08 -74.6c6.98,-16.66 6.15,-34.13 -3.84,-45.82 -12,-14.03 -33.67,-15.64 -53.8,-5.77 21.32,14.62 40.68,31.63 57.64,51.59zm-323.17 0c-6.98,-16.66 -6.16,-34.13 3.84,-45.82 11.99,-14.03 33.67,-15.64 53.79,-5.77 -21.32,14.62 -40.68,31.63 -57.63,51.59zm15.31 162.23c3.23,12.5 8.04,24.39 14.18,35.42 13.13,23.58 32.39,43.29 55.6,56.94 22.37,13.16 48.52,20.71 76.49,20.71 41.71,0 79.47,-16.9 106.8,-44.23 27.32,-27.32 44.23,-65.08 44.23,-106.79 0,-41.71 -16.91,-79.47 -44.23,-106.8 -27.33,-27.32 -65.09,-44.23 -106.8,-44.23 -31.07,0 -59.91,9.34 -83.84,25.33 -24.74,16.54 -44.33,40.19 -55.82,67.98 -3.68,8.91 -6.56,18.35 -8.5,28.22 -1.87,9.49 -2.86,19.36 -2.86,29.5 0,13.24 1.65,25.96 4.75,37.95z"/>
-        <path className="fil1" d="M55.23 188.52c-7.98,0 -14.45,-6.47 -14.45,-14.44 0,-7.98 6.47,-14.45 14.45,-14.45l63.94 0c7.98,0 14.45,6.47 14.45,14.45 0,7.97 -6.47,14.44 -14.45,14.44l-63.94 0zm0.72 167.68c-7.97,0 -14.44,-6.47 -14.44,-14.45 0,-7.97 6.47,-14.45 14.44,-14.45l64.58 0c7.97,0 14.45,6.48 14.45,14.45 0,7.98 -6.48,14.45 -14.45,14.45l-64.58 0zm-41.5 -84.94c-7.98,0 -14.45,-6.47 -14.45,-14.45 0,-7.97 6.47,-14.44 14.45,-14.44l89.12 0c7.98,0 14.45,6.47 14.45,14.44 0,7.98 -6.47,14.45 -14.45,14.45l-89.12 0z"/>
+        <path
+            className="fil0"
+            d="M174.84 343.06c-7.31,-13.12 -13.03,-27.28 -16.89,-42.18 -3.76,-14.56 -5.77,-29.71 -5.77,-45.17 0,-11.94 1.19,-23.66 3.43,-35.03 2.29,-11.57 5.74,-22.83 10.2,-33.63 13.7,-33.14 37.01,-61.29 66.42,-80.96 25.38,-16.96 55.28,-27.66 87.45,-29.87l0 -30.17c0,-0.46 0.02,-0.92 0.06,-1.37l-33.7 0c-5.53,0 -10.05,-4.52 -10.05,-10.04l0 -24.59c0,-5.53 4.52,-10.05 10.05,-10.05l101.27 0c5.53,0 10.05,4.52 10.05,10.05l0 24.59c0,5.52 -4.52,10.04 -10.05,10.04l-33.69 0c0.03,0.45 0.05,0.91 0.05,1.37l0 31.03 -0.1 0c41.1,4.89 77.94,23.63 105.73,51.42 32.56,32.55 52.7,77.54 52.7,127.21 0,49.67 -20.14,94.66 -52.7,127.21 -32.55,32.55 -77.54,52.7 -127.21,52.7 -33.16,0 -64.29,-9.04 -91.05,-24.78 -27.66,-16.27 -50.59,-39.73 -66.2,-67.78zm148.42 -36.62l-80.33 0 0 -25.71 28.6 0 0 -42.57 -28.6 1.93 0 -25.71 36.95 -8.35 25.38 0 0 74.7 18 0 0 25.71zm44.34 -100.41l11.08 26.83 1.61 0 11.09 -26.83 34.86 0 -22.33 48.52 22.33 51.89 -35.67 0 -12.05 -28.92 -1.44 0 -11.89 28.92 -34.06 0 21.85 -50.93 -21.85 -49.48 36.47 0zm126.08 -74.6c6.98,-16.66 6.15,-34.13 -3.84,-45.82 -12,-14.03 -33.67,-15.64 -53.8,-5.77 21.32,14.62 40.68,31.63 57.64,51.59zm-323.17 0c-6.98,-16.66 -6.16,-34.13 3.84,-45.82 11.99,-14.03 33.67,-15.64 53.79,-5.77 -21.32,14.62 -40.68,31.63 -57.63,51.59zm15.31 162.23c3.23,12.5 8.04,24.39 14.18,35.42 13.13,23.58 32.39,43.29 55.6,56.94 22.37,13.16 48.52,20.71 76.49,20.71 41.71,0 79.47,-16.9 106.8,-44.23 27.32,-27.32 44.23,-65.08 44.23,-106.79 0,-41.71 -16.91,-79.47 -44.23,-106.8 -27.33,-27.32 -65.09,-44.23 -106.8,-44.23 -31.07,0 -59.91,9.34 -83.84,25.33 -24.74,16.54 -44.33,40.19 -55.82,67.98 -3.68,8.91 -6.56,18.35 -8.5,28.22 -1.87,9.49 -2.86,19.36 -2.86,29.5 0,13.24 1.65,25.96 4.75,37.95z"
+        />
+        <path
+            className="fil1"
+            d="M55.23 188.52c-7.98,0 -14.45,-6.47 -14.45,-14.44 0,-7.98 6.47,-14.45 14.45,-14.45l63.94 0c7.98,0 14.45,6.47 14.45,14.45 0,7.97 -6.47,14.44 -14.45,14.44l-63.94 0zm0.72 167.68c-7.97,0 -14.44,-6.47 -14.44,-14.45 0,-7.97 6.47,-14.45 14.44,-14.45l64.58 0c7.97,0 14.45,6.48 14.45,14.45 0,7.98 -6.48,14.45 -14.45,14.45l-64.58 0zm-41.5 -84.94c-7.98,0 -14.45,-6.47 -14.45,-14.45 0,-7.97 6.47,-14.44 14.45,-14.44l89.12 0c7.98,0 14.45,6.47 14.45,14.44 0,7.98 -6.47,14.45 -14.45,14.45l-89.12 0z"
+        />
     </svg>
 );
 
 export const mediumSpeedIcon: JSX.Element = (
     <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 104.55">
-        <defs><style>{`.cls-1{fill:#fe0000;}`}</style></defs>
-        <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.45l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.09l5.36,12.45H93.74L90.85,66.6H90.5l-2.85,6.94H79.47l5.25-12.22L79.47,49.45ZM58.65,56.08l-1-5.75a33.58,33.58,0,0,1,9.68-1.46c1.28,0,2.35,0,3.22.11a11.77,11.77,0,0,1,2.67.58,5.41,5.41,0,0,1,2.2,1.28c1.24,1.23,1.85,3.12,1.85,5.66s-.72,4.42-2.16,5.63S70.64,64.73,66,66.3v1.08H76.89v6.16H57.11V68.72a10.73,10.73,0,0,1,.81-4.12,8.4,8.4,0,0,1,2.43-2.7,12.13,12.13,0,0,1,2.79-1.7l3.32-1.52c1-.47,1.88-.87,2.52-1.17V55.42a28.59,28.59,0,0,0-3.2-.19,30.66,30.66,0,0,0-7.13.85Zm59.83-24.54c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z"/>
-        <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Z"/>
-        <path className="cls-1" d="M3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z"/>
-        <path className="cls-1" d="M13.43,85.49a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94Z"/>
+        <defs>
+            <style>{`.cls-1{fill:#fe0000;}`}</style>
+        </defs>
+        <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.45l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.09l5.36,12.45H93.74L90.85,66.6H90.5l-2.85,6.94H79.47l5.25-12.22L79.47,49.45ZM58.65,56.08l-1-5.75a33.58,33.58,0,0,1,9.68-1.46c1.28,0,2.35,0,3.22.11a11.77,11.77,0,0,1,2.67.58,5.41,5.41,0,0,1,2.2,1.28c1.24,1.23,1.85,3.12,1.85,5.66s-.72,4.42-2.16,5.63S70.64,64.73,66,66.3v1.08H76.89v6.16H57.11V68.72a10.73,10.73,0,0,1,.81-4.12,8.4,8.4,0,0,1,2.43-2.7,12.13,12.13,0,0,1,2.79-1.7l3.32-1.52c1-.47,1.88-.87,2.52-1.17V55.42a28.59,28.59,0,0,0-3.2-.19,30.66,30.66,0,0,0-7.13.85Zm59.83-24.54c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z" />
+        <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Z" />
+        <path className="cls-1" d="M3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z" />
+        <path className="cls-1" d="M13.43,85.49a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94Z" />
     </svg>
 );
 
 export const fastSpeedIcon: JSX.Element = (
     <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 104.55">
-        <defs><style>{`.cls-1{fill:#fe0000;`}</style></defs>
-        <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.61l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.26l5.36,12.45H93.74l-2.9-6.94H90.5l-2.86,6.94H79.47l5.24-12.22L79.47,49.61Zm-19,8.48v-2.5a24.92,24.92,0,0,0-3.74-.2A33.25,33.25,0,0,0,59,56.2l-1-5.7A30.47,30.47,0,0,1,67.13,49a22.86,22.86,0,0,1,5.48.47,6.91,6.91,0,0,1,2.5,1.11,5.62,5.62,0,0,1,1.78,4.55,5.84,5.84,0,0,1-3.2,5.56v.19a5.73,5.73,0,0,1,3.81,5.74,8.67,8.67,0,0,1-.63,3.49,6,6,0,0,1-1.6,2.24,7.15,7.15,0,0,1-2.55,1.25,25.64,25.64,0,0,1-6.61.66,37.78,37.78,0,0,1-8.54-1l1.08-6.37a27.22,27.22,0,0,0,6.21.89,35.79,35.79,0,0,0,4.35-.23V65.11l-6.63-.65V58.87l6.63-.78Zm49.27-26.55c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z"/>
-        <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Zm.18,40.24a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94ZM3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z"/>
+        <defs>
+            <style>{`.cls-1{fill:#fe0000;`}</style>
+        </defs>
+        <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.61l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.26l5.36,12.45H93.74l-2.9-6.94H90.5l-2.86,6.94H79.47l5.24-12.22L79.47,49.61Zm-19,8.48v-2.5a24.92,24.92,0,0,0-3.74-.2A33.25,33.25,0,0,0,59,56.2l-1-5.7A30.47,30.47,0,0,1,67.13,49a22.86,22.86,0,0,1,5.48.47,6.91,6.91,0,0,1,2.5,1.11,5.62,5.62,0,0,1,1.78,4.55,5.84,5.84,0,0,1-3.2,5.56v.19a5.73,5.73,0,0,1,3.81,5.74,8.67,8.67,0,0,1-.63,3.49,6,6,0,0,1-1.6,2.24,7.15,7.15,0,0,1-2.55,1.25,25.64,25.64,0,0,1-6.61.66,37.78,37.78,0,0,1-8.54-1l1.08-6.37a27.22,27.22,0,0,0,6.21.89,35.79,35.79,0,0,0,4.35-.23V65.11l-6.63-.65V58.87l6.63-.78Zm49.27-26.55c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z" />
+        <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Zm.18,40.24a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94ZM3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z" />
     </svg>
 );
-
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
index 35153f439..cf5315da3 100644
--- a/src/client/views/nodes/MapBox/AnimationUtility.ts
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -87,25 +87,24 @@ export class AnimationUtility {
     @computed get currentPitch(): number {
         if (!this.isStreetViewAnimation) return 50;
         if (!this.terrainDisplayed) return 80;
-        else {
-            // const groundElevation = 0;
-            const heightAboveGround = this.currentAnimationAltitude;
-            const horizontalDistance = 500;
-
-            let pitch;
-            if (heightAboveGround >= 0) {
-                pitch = 90 - Math.atan(heightAboveGround / horizontalDistance) * (180 / Math.PI);
-            } else {
-                pitch = 80;
-            }
 
-            console.log(Math.max(50, Math.min(pitch, 85)));
+        // const groundElevation = 0;
+        const heightAboveGround = this.currentAnimationAltitude;
+        const horizontalDistance = 500;
 
-            if (this.previousPitch) {
-                return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02);
-            }
-            return Math.max(50, Math.min(pitch, 85));
+        let pitch;
+        if (heightAboveGround >= 0) {
+            pitch = 90 - Math.atan(heightAboveGround / horizontalDistance) * (180 / Math.PI);
+        } else {
+            pitch = 80;
+        }
+
+        console.log(Math.max(50, Math.min(pitch, 85)));
+
+        if (this.previousPitch) {
+            return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02);
         }
+        return Math.max(50, Math.min(pitch, 85));
     }
 
     @computed get flyInEndPitch() {
@@ -214,8 +213,8 @@ export class AnimationUtility {
         currentAnimationPhase: number;
         updateAnimationPhase: (newAnimationPhase: number) => void;
         updateFrameId: (newFrameId: number) => void;
-    }) => {
-        return new Promise<void>(async resolve => {
+    }) =>
+        new Promise<void>(async resolve => {
             let startTime: number | null = null;
 
             const frame = async (currentTime: number) => {
@@ -257,7 +256,7 @@ export class AnimationUtility {
                 updateAnimationPhase(animationPhase);
 
                 // compute corrected camera ground position, so that he leading edge of the path is in view
-                var correctedPosition = this.computeCameraPosition(
+                const correctedPosition = this.computeCameraPosition(
                     this.isStreetViewAnimation,
                     this.currentPitch,
                     bearing,
@@ -277,7 +276,7 @@ export class AnimationUtility {
                 map.setFreeCameraOptions(camera);
 
                 this.previousAltitude = this.currentAnimationAltitude;
-                this.previousPitch = this.previousPitch;
+                // this.previousPitch = this.previousPitch;
 
                 // repeat!
                 const innerFrameId = await window.requestAnimationFrame(frame);
@@ -287,15 +286,14 @@ export class AnimationUtility {
             const outerFrameId = await window.requestAnimationFrame(frame);
             updateFrameId(outerFrameId);
         });
-    };
 
-    public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) => {
-        return new Promise<{ bearing: number; altitude: number }>(async resolve => {
+    public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) =>
+        new Promise<{ bearing: number; altitude: number }>(async resolve => {
             let start: number | null;
 
-            var currentAltitude;
-            var currentBearing;
-            var currentPitch;
+            let currentAltitude;
+            let currentBearing;
+            let currentPitch;
 
             // the animation frame will run as many times as necessary until the duration has been reached
             const frame = async (time: number) => {
@@ -319,7 +317,7 @@ export class AnimationUtility {
                 currentPitch = this.FLY_IN_START_PITCH + (this.flyInEndPitch - this.FLY_IN_START_PITCH) * d3.easeCubicOut(animationPhase);
 
                 // compute corrected camera ground position, so the start of the path is always in view
-                var correctedPosition = this.computeCameraPosition(false, currentPitch, currentBearing, this.FIRST_LNG_LAT, currentAltitude);
+                const correctedPosition = this.computeCameraPosition(false, currentPitch, currentBearing, this.FIRST_LNG_LAT, currentAltitude);
 
                 // set the pitch and bearing of the camera
                 const camera = map.getFreeCameraOptions();
@@ -349,13 +347,10 @@ export class AnimationUtility {
             const outerFrameId = await window.requestAnimationFrame(frame);
             updateFrameId(outerFrameId);
         });
-    };
 
     previousCameraPosition: { lng: number; lat: number } | null = null;
 
-    lerp = (start: number, end: number, amt: number) => {
-        return (1 - amt) * start + amt * end;
-    };
+    lerp = (start: number, end: number, amt: number) => (1 - amt) * start + amt * end;
 
     computeCameraPosition = (isStreetViewAnimation: boolean, pitch: number, bearing: number, targetPosition: { lng: number; lat: number }, altitude: number, smooth = false) => {
         const bearingInRadian = (bearing * Math.PI) / 180;
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
index fe7f8d8b3..f176317d2 100644
--- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -14,6 +14,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 
 @observer
 export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: DirectionsAnchorMenu;
 
     private _disposer: IReactionDisposer | undefined;
@@ -24,8 +25,8 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public OnClick: (e: PointerEvent) => void = unimplementedFunction;
     // public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
-    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
-    public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = () => undefined;
+    public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
     public Delete: () => void = unimplementedFunction;
     // public MakeTargetToggle: () => void = unimplementedFunction;
     // public ShowTargetTrail: () => void = unimplementedFunction;
@@ -56,7 +57,7 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     componentDidMount() {
         this._disposer = reaction(
             () => SelectionManager.Views.slice(),
-            sel => DirectionsAnchorMenu.Instance.fadeOut(true)
+            () => DirectionsAnchorMenu.Instance.fadeOut(true)
         );
     }
     // audioDown = (e: React.PointerEvent) => {
@@ -104,8 +105,8 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                     color={SettingsManager.userColor}
                 />
 
-                <IconButton tooltip="Animate route" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />
-                <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
+                <IconButton tooltip="Animate route" onPointerDown={this.Delete} /* *TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />
+                <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /* *TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
             </div>
         );
 
diff --git a/src/client/views/nodes/MapBox/GeocoderControl.tsx b/src/client/views/nodes/MapBox/GeocoderControl.tsx
index e4ba51316..e118c57d9 100644
--- a/src/client/views/nodes/MapBox/GeocoderControl.tsx
+++ b/src/client/views/nodes/MapBox/GeocoderControl.tsx
@@ -3,8 +3,6 @@
 // import { ControlPosition, MarkerProps, useControl } from "react-map-gl";
 
 // import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css'
-
-
 // export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
 //     mapboxAccessToken: string;
 //     marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
@@ -31,7 +29,6 @@
 //           ctrl.on('results', props.onResults);
 //           ctrl.on('result', evt => {
 //             props.onResult(evt);
-    
 //             // const {result} = evt;
 //             // const location =
 //             //   result &&
@@ -49,8 +46,6 @@
 //           position: props.position
 //         }
 //       );
-
-
 //     // @ts-ignore (TS2339) private member
 //     if (geocoder._map) {
 //         if (geocoder.getProximity() !== props.proximity && props.proximity !== undefined) {
@@ -104,4 +99,4 @@
 //   onLoading: noop,
 //   onResults: noop,
 //   onError: noop
-// };
\ No newline at end of file
+// };
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index d17c4298c..174511e1a 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/button-has-type */
 import { IconLookup, faAdd, faArrowDown, faArrowLeft, faArrowsRotate, faBicycle, faCalendarDays, faCar, faDiamondTurnRight, faEdit, faPersonWalking, faRoute } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
@@ -24,6 +25,7 @@ type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize'
 
 @observer
 export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: MapAnchorMenu;
 
     private _disposer: IReactionDisposer | undefined;
@@ -36,8 +38,8 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public OnClick: (e: PointerEvent) => void = unimplementedFunction;
     // public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
-    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
-    public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = () => undefined;
+    public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
     public Delete: () => void = unimplementedFunction;
     // public MakeTargetToggle: () => void = unimplementedFunction;
     // public ShowTargetTrail: () => void = unimplementedFunction;
@@ -125,7 +127,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     componentDidMount() {
         this._disposer = reaction(
             () => SelectionManager.Views.slice(),
-            sel => MapAnchorMenu.Instance.fadeOut(true)
+            () => MapAnchorMenu.Instance.fadeOut(true)
         );
     }
     // audioDown = (e: React.PointerEvent) => {
@@ -148,12 +150,12 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         setupMoveUpEvents(
             this,
             e,
-            (e: PointerEvent) => {
-                this.StartDrag(e, this._commentRef.current!);
+            moveEv => {
+                this.StartDrag(moveEv, this._commentRef.current!);
                 return true;
             },
             returnFalse,
-            e => this.OnClick(e)
+            clickEv => this.OnClick(clickEv)
         );
     };
 
@@ -275,7 +277,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     HandleAddRouteClick = () => {
         if (this.currentRouteInfoMap && this.selectedTransportationType && this.selectedDestinationFeature) {
-            const coordinates = this.currentRouteInfoMap[this.selectedTransportationType].coordinates;
+            const { coordinates } = this.currentRouteInfoMap[this.selectedTransportationType];
             console.log(coordinates);
             console.log(this.selectedDestinationFeature);
             this.AddNewRouteToMap(coordinates, this.title ?? '', this.selectedDestinationFeature, this.createPinForDestination);
@@ -294,34 +296,30 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     getDirectionsButton: JSX.Element = (<IconButton tooltip="Get directions" onPointerDown={this.DirectionsClick} icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} color={SettingsManager.userColor} />);
 
-    getAddToCalendarButton = (docType: string): JSX.Element => {
-        return (
-            <IconButton
-                tooltip="Add to calendar"
-                onPointerDown={() => {
-                    CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc);
-                }}
-                icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />}
-                color={SettingsManager.userColor}
-            />
-        );
-    };
+    getAddToCalendarButton = (docType: string): JSX.Element => (
+        <IconButton
+            tooltip="Add to calendar"
+            onPointerDown={() => {
+                CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc);
+            }}
+            icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />}
+            color={SettingsManager.userColor}
+        />
+    );
     addToCalendarButton: JSX.Element = (
         <IconButton tooltip="Add to calendar" onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)} icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
     );
 
-    getLinkNoteToDocButton = (docType: string): JSX.Element => {
-        return (
-            <div ref={this._commentRef}>
-                <IconButton
-                    tooltip={`Link Note to ${docType === 'pin' ? 'Pin' : 'Route'}`} //
-                    onPointerDown={this.notePointerDown}
-                    icon={<FontAwesomeIcon icon="sticky-note" />}
-                    color={SettingsManager.userColor}
-                />
-            </div>
-        );
-    };
+    getLinkNoteToDocButton = (docType: string): JSX.Element => (
+        <div ref={this._commentRef}>
+            <IconButton
+                tooltip={`Link Note to ${docType === 'pin' ? 'Pin' : 'Route'}`} //
+                onPointerDown={this.notePointerDown}
+                icon={<FontAwesomeIcon icon="sticky-note" />}
+                color={SettingsManager.userColor}
+            />
+        </div>
+    );
 
     linkNoteToPinOrRoutenButton: JSX.Element = (
         <div ref={this._commentRef}>
@@ -363,16 +361,14 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         />
     );
 
-    getDeleteButton = (type: string) => {
-        return (
-            <IconButton
-                tooltip={`Delete ${type === 'pin' ? 'Pin' : 'Route'}`} //
-                onPointerDown={this.Delete}
-                icon={<FontAwesomeIcon icon="trash-alt" />}
-                color={SettingsManager.userColor}
-            />
-        );
-    };
+    getDeleteButton = (type: string) => (
+        <IconButton
+            tooltip={`Delete ${type === 'pin' ? 'Pin' : 'Route'}`} //
+            onPointerDown={this.Delete}
+            icon={<FontAwesomeIcon icon="trash-alt" />}
+            color={SettingsManager.userColor}
+        />
+    );
 
     animateRouteButton: JSX.Element = (<IconButton tooltip="Animate route" onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />);
 
@@ -453,18 +449,17 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             }}
                             options={this.destinationFeatures.filter(feature => feature.place_name).map(feature => feature)}
                             getOptionLabel={(feature: any) => feature.place_name}
+                            // eslint-disable-next-line react/jsx-props-no-spreading
                             renderInput={(params: any) => <TextField {...params} placeholder="Enter a destination" />}
                         />
-                        {this.selectedDestinationFeature && (
-                            <>
-                                {!this.allMapPinDocs.some(pinDoc => pinDoc.title === this.selectedDestinationFeature.place_name) && (
-                                    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '5px' }}>
-                                        <FormControlLabel label="Create pin for destination?" control={<Checkbox color="success" checked={this.createPinForDestination} onChange={this.toggleCreatePinForDestinationCheckbox} />} />
-                                    </div>
-                                )}
-                            </>
-                        )}
-                        <button id="get-routes-button" disabled={this.selectedDestinationFeature ? false : true} onClick={() => this.getRoutes(this.selectedDestinationFeature)}>
+                        {!this.selectedDestinationFeature
+                            ? null
+                            : !this.allMapPinDocs.some(pinDoc => pinDoc.title === this.selectedDestinationFeature.place_name) && (
+                                  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '5px' }}>
+                                      <FormControlLabel label="Create pin for destination?" control={<Checkbox color="success" checked={this.createPinForDestination} onChange={this.toggleCreatePinForDestinationCheckbox} />} />
+                                  </div>
+                              )}
+                        <button id="get-routes-button" disabled={!this.selectedDestinationFeature} onClick={() => this.getRoutes(this.selectedDestinationFeature)}>
                             Get routes
                         </button>
 
@@ -517,7 +512,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                                 </div>
                             ))}
                         </div>
-                        <div style={{ width: '100%', height: '3px', color: 'white' }}></div>
+                        <div style={{ width: '100%', height: '3px', color: 'white' }} />
                     </div>
                 )}
                 {this.menuType === 'route' && this.routeDoc && <div>{StrCast(this.routeDoc.title)}</div>}
diff --git a/src/client/views/nodes/MapBox/MapboxApiUtility.ts b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
index 592330ac2..5c5192372 100644
--- a/src/client/views/nodes/MapBox/MapboxApiUtility.ts
+++ b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
@@ -1,4 +1,3 @@
-
 const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
 const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
 const MAPBOX_DIRECTIONS_BASE_URL = 'https://api.mapbox.com/directions/v5/mapbox';
@@ -7,92 +6,79 @@ const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3
 export type TransportationType = 'driving' | 'cycling' | 'walking';
 
 export class MapboxApiUtility {
-
     static forwardGeocodeForFeatures = async (searchText: string) => {
         try {
-            const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`;
+            const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) + `.json?access_token=${MAPBOX_ACCESS_TOKEN}`;
             const response = await fetch(url);
             const data = await response.json();
             return data.features;
-        } catch (error: any){
-            // TODO: handle error in better way 
+        } catch (error: any) {
+            // TODO: handle error in better way
             return null;
         }
-    }
+    };
 
     static reverseGeocodeForFeatures = async (longitude: number, latitude: number) => {
         try {
-            const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + "," + latitude.toString()) + '.json' +
-                `?access_token=${MAPBOX_ACCESS_TOKEN}`;
+            const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + ',' + latitude.toString()) + `.json?access_token=${MAPBOX_ACCESS_TOKEN}`;
             const response = await fetch(url);
             const data = await response.json();
             return data.features;
-        } catch (error: any){
+        } catch (error: any) {
             return null;
         }
-    }
+    };
 
     static getDirections = async (origin: number[], destination: number[]): Promise<Record<TransportationType, any> | undefined> => {
         try {
-
             const directionsPromises: Promise<any>[] = [];
             const transportationTypes: TransportationType[] = ['driving', 'cycling', 'walking'];
 
-            transportationTypes.forEach((type) => {
-                directionsPromises.push(
-                  fetch(
-                    `${MAPBOX_DIRECTIONS_BASE_URL}/${type}/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`
-                  ).then((response) => response.json())
-                );
-              });
+            transportationTypes.forEach(type => {
+                directionsPromises.push(fetch(`${MAPBOX_DIRECTIONS_BASE_URL}/${type}/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`).then(response => response.json()));
+            });
 
             const results = await Promise.all(directionsPromises);
 
             const routeInfoMap: Record<TransportationType, any> = {
-                'driving': {},
-                'cycling': {},
-                'walking': {},
+                driving: {},
+                cycling: {},
+                walking: {},
             };
 
             transportationTypes.forEach((type, index) => {
                 const routeData = results[index].routes[0];
                 if (routeData) {
-                  const geometry = routeData.geometry;
-                  const coordinates = geometry.coordinates;
-          
-                  routeInfoMap[type] = {
-                    duration: this.secondsToMinutesHours(routeData.duration),
-                    distance: this.metersToMiles(routeData.distance),
-                    coordinates: coordinates,
-                  };
+                    const { geometry } = routeData;
+                    const { coordinates } = geometry;
+
+                    routeInfoMap[type] = {
+                        duration: this.secondsToMinutesHours(routeData.duration),
+                        distance: this.metersToMiles(routeData.distance),
+                        coordinates: coordinates,
+                    };
                 }
-              });
+            });
 
             return routeInfoMap;
 
-            // return current route info, and the temporary route 
-
-        } catch (error: any){
+            // return current route info, and the temporary route
+        } catch (error: any) {
             return undefined;
-            console.log("Error: ", error);
         }
-    }
+    };
 
     private static secondsToMinutesHours = (seconds: number) => {
         const hours = Math.floor(seconds / 3600);
         const minutes = Math.floor((seconds % 3600) / 60).toFixed(2);
 
-        if (hours === 0){
-            return `${minutes} min`
-        } else {
-            return `${hours} hr ${minutes} min`
+        if (hours === 0) {
+            return `${minutes} min`;
         }
-    }
-
-    private static metersToMiles = (meters: number) => {
-        return `${parseFloat((meters/1609.34).toFixed(2))} mi`;
-    }   
+        return `${hours} hr ${minutes} min`;
+    };
 
+    private static metersToMiles = (meters: number) => `${parseFloat((meters / 1609.34).toFixed(2))} mi`;
 }
 
 // const drivingQuery = await fetch(
@@ -136,4 +122,4 @@ export class MapboxApiUtility {
 //         distance: this.metersToMiles(routeData.distance),
 //         coordinates: coordinates
 //     }
-// })
\ No newline at end of file
+// })
diff --git a/src/client/views/nodes/MapBox/MarkerIcons.tsx b/src/client/views/nodes/MapBox/MarkerIcons.tsx
index a580fcaa0..087472112 100644
--- a/src/client/views/nodes/MapBox/MarkerIcons.tsx
+++ b/src/client/views/nodes/MapBox/MarkerIcons.tsx
@@ -17,8 +17,6 @@ import {
     faHouse,
     faLandmark,
     faLocationDot,
-    faLocationPin,
-    faMapPin,
     faMasksTheater,
     faMugSaucer,
     faPersonHiking,
@@ -65,6 +63,7 @@ export class MarkerIcons {
             iconProps.color = color;
         }
 
+        // eslint-disable-next-line react/jsx-props-no-spreading
         return <FontAwesomeIcon {...iconProps} size={size} />;
     }
 
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 189e2105d..fce52ef4b 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -118,9 +118,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
      * @param sidebarKey
      * @returns
      */
-    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+    sidebarAddDocument = (docsIn: Doc | Doc[], sidebarKey?: string) => {
         if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
-        const docs = doc instanceof Doc ? [doc] : doc;
+        const docs = docsIn instanceof Doc ? [docsIn] : docsIn;
         docs.forEach(doc => {
             let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPin;
             if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) {
@@ -139,7 +139,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
             }
         }); // add to annotation list
 
-        return this.addDocument(doc, sidebarKey); // add to sidebar list
+        return this.addDocument(docs, sidebarKey); // add to sidebar list
     };
 
     removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => {
@@ -168,7 +168,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         setupMoveUpEvents(
             this,
             e,
-            (e, down, delta) =>
+            (moveEv, down, delta) =>
                 runInAction(() => {
                     const localDelta = this._props
                         .ScreenToLocalTransform()
@@ -243,10 +243,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         const docView = this.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
-                dragComplete: e => {
-                    if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
-                        e.annoDragData.linkSourceDoc.followLinkZoom = false;
+                dragComplete: dragEv => {
+                    if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
+                        dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.Document;
+                        dragEv.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
             });
@@ -680,23 +680,23 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         setupMoveUpEvents(
             e,
             e,
-            e => {
+            moveEv => {
                 if (!dragClone) {
                     dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement;
                     dragClone.style.position = 'absolute';
                     dragClone.style.zIndex = '10000';
                     DragManager.Root().appendChild(dragClone);
                 }
-                dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
+                dragClone.style.transform = `translate(${moveEv.clientX - 15}px, ${moveEv.clientY - 15}px)`;
                 return false;
             },
-            e => {
+            upEv => {
                 if (!dragClone) return;
                 DragManager.Root().removeChild(dragClone);
-                let target = document.elementFromPoint(e.x, e.y);
+                let target = document.elementFromPoint(upEv.x, upEv.y);
                 while (target) {
                     if (target === this._ref.current) {
-                        const cpt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY);
+                        const cpt = this.ScreenToLocalBoxXf().transformPoint(upEv.clientX, upEv.clientY);
                         const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
                         const y = cpt[1] - 32 /* height of search bar */ - this._props.PanelHeight() / 2;
                         const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
@@ -706,7 +706,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                     target = target.parentElement;
                 }
             },
-            e => {
+            () => {
                 const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map);
                 if (this.bingSearchBarContents) {
                     this.bingSearch().then(createPin);
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index 16450c359..48da4937a 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -6,22 +6,57 @@ import { RadialMenuItem, RadialMenuProps } from './RadialMenuItem';
 
 @observer
 export class RadialMenu extends React.Component {
+    // eslint-disable-next-line no-use-before-define
     static Instance: RadialMenu;
     static readonly buffer = 20;
 
+    @observable private _mouseX: number = -1;
+    @observable private _mouseY: number = -1;
+    @observable private _shouldDisplay: boolean = false;
+    @observable private _mouseDown: boolean = false;
+    @observable private _closest: number = -1;
+    @observable private _pageX: number = 0;
+    @observable private _pageY: number = 0;
+    @observable _display: boolean = false;
+    @observable private _yRelativeToTop: boolean = true;
+    @observable private _items: Array<RadialMenuProps> = [];
+    private _reactionDisposer?: IReactionDisposer;
+
     constructor(props: any) {
         super(props);
         makeObservable(this);
         RadialMenu.Instance = this;
     }
 
-    @observable private _mouseX: number = -1;
-    @observable private _mouseY: number = -1;
-    @observable private _shouldDisplay: boolean = false;
-    @observable private _mouseDown: boolean = false;
-    private _reactionDisposer?: IReactionDisposer;
+    componentDidMount() {
+        document.addEventListener('pointerdown', this.onPointerDown);
+        document.addEventListener('pointerup', this.onPointerUp);
+        this.previewcircle();
+        this._reactionDisposer = reaction(
+            () => this._shouldDisplay,
+            () =>
+                this._shouldDisplay &&
+                !this._mouseDown &&
+                runInAction(() => {
+                    this._display = true;
+                })
+        );
+    }
 
-    public used: boolean = false;
+    componentDidUpdate() {
+        this.previewcircle();
+    }
+    componentWillUnmount() {
+        document.removeEventListener('pointerdown', this.onPointerDown);
+
+        document.removeEventListener('pointerup', this.onPointerUp);
+        this._reactionDisposer && this._reactionDisposer();
+    }
+
+    @computed get menuItems() {
+        // eslint-disable-next-line react/jsx-props-no-spreading
+        return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />);
+    }
 
     catchTouch = (te: React.TouchEvent) => {
         te.stopPropagation();
@@ -33,13 +68,9 @@ export class RadialMenu extends React.Component {
         this._mouseDown = true;
         this._mouseX = e.clientX;
         this._mouseY = e.clientY;
-        this.used = false;
         document.addEventListener('pointermove', this.onPointerMove);
     };
 
-    @observable
-    private _closest: number = -1;
-
     @action
     onPointerMove = (e: PointerEvent) => {
         const curX = e.clientX;
@@ -65,7 +96,6 @@ export class RadialMenu extends React.Component {
     };
     @action
     onPointerUp = (e: PointerEvent) => {
-        this.used = true;
         this._mouseDown = false;
         const curX = e.clientX;
         const curY = e.clientY;
@@ -78,86 +108,6 @@ export class RadialMenu extends React.Component {
             this._items[this._closest].event();
         }
     };
-    componentWillUnmount() {
-        document.removeEventListener('pointerdown', this.onPointerDown);
-
-        document.removeEventListener('pointerup', this.onPointerUp);
-        this._reactionDisposer && this._reactionDisposer();
-    }
-
-    @action
-    componentDidMount() {
-        document.addEventListener('pointerdown', this.onPointerDown);
-        document.addEventListener('pointerup', this.onPointerUp);
-        this.previewcircle();
-        this._reactionDisposer = reaction(
-            () => this._shouldDisplay,
-            () => this._shouldDisplay && !this._mouseDown && runInAction(() => (this._display = true))
-        );
-    }
-
-    componentDidUpdate = () => {
-        this.previewcircle();
-    };
-
-    @observable private _pageX: number = 0;
-    @observable private _pageY: number = 0;
-    @observable _display: boolean = false;
-    @observable private _yRelativeToTop: boolean = true;
-
-    @observable private _width: number = 0;
-    @observable private _height: number = 0;
-
-    getItems() {
-        return this._items;
-    }
-
-    @action
-    addItem(item: RadialMenuProps) {
-        if (this._items.indexOf(item) === -1) {
-            this._items.push(item);
-        }
-    }
-
-    @observable
-    private _items: Array<RadialMenuProps> = [];
-
-    @action
-    displayMenu = (x: number, y: number) => {
-        //maxX and maxY will change if the UI/font size changes, but will work for any amount
-        //of items added to the menu
-        this._mouseX = x;
-        this._mouseY = y;
-        this._shouldDisplay = true;
-    };
-    // @computed
-    // get pageX() {
-    //     const x = this._pageX;
-    //     if (x < 0) {
-    //         return 0;
-    //     }
-    //     const width = this._width;
-    //     if (x + width > window.innerWidth - RadialMenu.buffer) {
-    //         return window.innerWidth - RadialMenu.buffer - width;
-    //     }
-    //     return x;
-    // }
-    // @computed
-    // get pageY() {
-    //     const y = this._pageY;
-    //     if (y < 0) {
-    //         return 0;
-    //     }
-    //     const height = this._height;
-    //     if (y + height > window.innerHeight - RadialMenu.buffer) {
-    //         return window.innerHeight - RadialMenu.buffer - height;
-    //     }
-    //     return y;
-    // }
-
-    @computed get menuItems() {
-        return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />);
-    }
 
     @action
     closeMenu = () => {
@@ -166,14 +116,6 @@ export class RadialMenu extends React.Component {
         this._shouldDisplay = false;
     };
 
-    @action
-    openMenu = (x: number, y: number) => {
-        this._pageX = x;
-        this._pageY = y;
-        this._shouldDisplay;
-        this._display = true;
-    };
-
     @action
     clearItems() {
         this._items = [];
diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx
index 91dc37d34..6f10e7b65 100644
--- a/src/client/views/nodes/RadialMenuItem.tsx
+++ b/src/client/views/nodes/RadialMenuItem.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { observer } from 'mobx-react';
@@ -53,6 +54,7 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
                 case 2:
                     color = 'lightgray';
                     break;
+                default:
             }
             if (circlemax % 3 === 1 && circlemin === circlemax - 1) {
                 color = '#c2c2c5';
@@ -80,7 +82,6 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
         const avg = (circlemin / circlemax + (circlemin + 1) / circlemax) / 2;
         const degrees = 360 * avg;
         const x = 100 * Math.cos((degrees * Math.PI) / 180);
-        const y = -125 * Math.sin((degrees * Math.PI) / 180);
         return x;
     }
 
@@ -91,7 +92,6 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
         this.props.max ? (circlemax = this.props.max) : null;
         const avg = (circlemin / circlemax + (circlemin + 1) / circlemax) / 2;
         const degrees = 360 * avg;
-        const x = 125 * Math.cos((degrees * Math.PI) / 180);
         const y = -100 * Math.sin((degrees * Math.PI) / 180);
         return y;
     }
diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.tsx b/src/client/views/nodes/RecordingBox/ProgressBar.tsx
index 1bb2b7c84..62798bc2f 100644
--- a/src/client/views/nodes/RecordingBox/ProgressBar.tsx
+++ b/src/client/views/nodes/RecordingBox/ProgressBar.tsx
@@ -1,31 +1,33 @@
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable react/require-default-props */
 import * as React from 'react';
-import { useEffect, useState, useCallback, useRef } from "react"
-import "./ProgressBar.scss"
+import { useEffect, useState, useRef } from 'react';
+import './ProgressBar.scss';
 import { MediaSegment } from './RecordingView';
 
 interface ProgressBarProps {
-    videos: MediaSegment[],
-    setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>,
-    orderVideos: boolean,
-    progress: number,
-    recording: boolean,
-    doUndo: boolean,
-    setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>,
+    videos: MediaSegment[];
+    setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>;
+    orderVideos: boolean;
+    progress: number;
+    recording: boolean;
+    doUndo: boolean;
+    setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>;
 }
 
 interface SegmentBox {
-    endTime: number,
-    startTime: number,
-    order: number,
+    endTime: number;
+    startTime: number;
+    order: number;
 }
 interface CurrentHover {
-    index: number,
-    minX: number,
-    maxX: number
+    index: number;
+    minX: number;
+    maxX: number;
 }
 
 export function ProgressBar(props: ProgressBarProps) {
-    const progressBarRef = useRef<HTMLDivElement | null>(null)
+    const progressBarRef = useRef<HTMLDivElement | null>(null);
 
     // the actual list of JSX elements rendered as segments
     const [segments, setSegments] = useState<JSX.Element[]>([]);
@@ -47,8 +49,6 @@ export function ProgressBar(props: ProgressBarProps) {
     // update the canUndo props based on undo stack
     useEffect(() => props.setCanUndo?.(undoStack.length > 0), [undoStack.length]);
 
-    // useEffect for undo - brings back the most recently deleted segment
-    useEffect(() => handleUndo(), [props.doUndo])
     const handleUndo = () => {
         // get the last element from the undo if it exists
         if (undoStack.length === 0) return;
@@ -59,27 +59,36 @@ export function ProgressBar(props: ProgressBarProps) {
         // update the removed time and place element back into ordered
         setTotalRemovedTime(prevRemoved => prevRemoved - (last.endTime - last.startTime));
         setOrdered(prevOrdered => [...prevOrdered, last]);
-    }
+    };
+    // useEffect for undo - brings back the most recently deleted segment
+    useEffect(() => handleUndo(), [props.doUndo]);
 
     // useEffect for recording changes - changes style to disabled and adds the "expanding-segment"
     useEffect(() => {
         // get segments segment's html using it's id -> make them appeared disabled (or enabled)
-        segments.forEach((seg) => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording));
+        segments.forEach(seg => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording));
         progressBarRef.current?.classList.toggle('progressbar-disabled', props.recording);
 
         if (props.recording)
-            setSegments(prevSegments => [...prevSegments, <div key='segment-expanding' id='segment-expanding' className='segment segment-expanding blink' style={{ width: 'fit-content' }}>{props.videos.length + 1}</div>]);
-    }, [props.recording])
-
+            setSegments(prevSegments => [
+                ...prevSegments,
+                <div key="segment-expanding" id="segment-expanding" className="segment segment-expanding blink" style={{ width: 'fit-content' }}>
+                    {props.videos.length + 1}
+                </div>,
+            ]);
+    }, [props.recording]);
 
     // useEffect that updates the segmentsJSX, which is rendered
     // only updated when ordered is updated or if the user is dragging around a segment
     useEffect(() => {
         const totalTime = props.progress * 1000 - totalRemovedTime;
-        const segmentsJSX = ordered.map((seg, i) =>
-            <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>{seg.order + 1}</div>);
+        const segmentsJSX = ordered.map((seg, i) => (
+            <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>
+                {seg.order + 1}
+            </div>
+        ));
 
-        setSegments(segmentsJSX)
+        setSegments(segmentsJSX);
     }, [dragged, ordered]);
 
     // useEffect for dragged - update the cursor to be grabbing while grabbing
@@ -89,14 +98,14 @@ export function ProgressBar(props: ProgressBarProps) {
 
     // to imporve performance, only want to update the CSS width, not re-render the whole JSXList
     useEffect(() => {
-        if (!props.recording) return
+        if (!props.recording) return;
         const totalTime = props.progress * 1000 - totalRemovedTime;
         let remainingTime = totalTime;
         segments.forEach((seg, i) => {
             // for the last segment, we need to set that directly
             if (i === segments.length - 1) return;
             // update remaining time
-            remainingTime -= (ordered[i].endTime - ordered[i].startTime);
+            remainingTime -= ordered[i].endTime - ordered[i].startTime;
 
             // update the width for this segment
             const htmlId = seg.props.id;
@@ -106,8 +115,7 @@ export function ProgressBar(props: ProgressBarProps) {
 
         // update the width of the expanding segment using the remaining time
         const segExapandHtml = document.getElementById('segment-expanding');
-        if (segExapandHtml)
-            segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`;
+        if (segExapandHtml) segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`;
     }, [props.progress]);
 
     // useEffect for props.videos - update the ordered array when a new video is added
@@ -120,9 +128,7 @@ export function ProgressBar(props: ProgressBarProps) {
         // in this case, a new video is added -> push it onto ordered
         if (order >= ordered.length) {
             const { endTime, startTime } = props.videos.lastElement();
-            setOrdered(prevOrdered => {
-                return [...prevOrdered, { endTime, startTime, order }];
-            });
+            setOrdered(prevOrdered => [...prevOrdered, { endTime, startTime, order }]);
         }
 
         // in this case, a video is removed
@@ -132,7 +138,7 @@ export function ProgressBar(props: ProgressBarProps) {
     }, [props.videos]);
 
     // useEffect for props.orderVideos - matched the order array with the videos array before the export
-    useEffect(() => props.setVideos(vids => ordered.map((seg) => vids[seg.order])), [props.orderVideos]);
+    useEffect(() => props.setVideos(vids => ordered.map(seg => vids[seg.order])), [props.orderVideos]);
 
     // useEffect for removed - handles logic for removing a segment
     useEffect(() => {
@@ -151,36 +157,68 @@ export function ProgressBar(props: ProgressBarProps) {
     // returns the new currentHover based on the new index
     const updateCurrentHover = (segId: number): CurrentHover | null => {
         // get the segId of the segment that will become the new bounding area
-        const rect = progressBarRef.current?.children[segId].getBoundingClientRect()
-        if (rect == null) return null
+        const rect = progressBarRef.current?.children[segId].getBoundingClientRect();
+        if (rect == null) return null;
         return {
             index: segId,
             minX: rect.x,
             maxX: rect.x + rect.width,
-        }
-    }
+        };
+    };
+
+    const swapSegments = (oldIndex: number, newIndex: number) => {
+        if (newIndex == null) return;
+        setOrdered(prevOrdered => {
+            const temp = { ...prevOrdered[oldIndex] };
+            prevOrdered[oldIndex] = prevOrdered[newIndex];
+            prevOrdered[newIndex] = temp;
+            return prevOrdered;
+        });
+        // update visually where the segment is hovering over
+        setDragged(newIndex);
+    };
+
+    // functions for the floating segment that tracks the cursor while grabbing it
+    const initDetachSegment = (dot: HTMLDivElement, rect: DOMRect) => {
+        dot.classList.add('segment-selected');
+        dot.style.transitionDuration = '0s';
+        dot.style.position = 'absolute';
+        dot.style.zIndex = '999';
+        dot.style.width = `${rect.width}px`;
+        dot.style.height = `${rect.height}px`;
+        dot.style.left = `${rect.x}px`;
+        dot.style.top = `${rect.y}px`;
+        dot.draggable = false;
+        document.body.append(dot);
+    };
+    const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => {
+        // event.stopPropagation()
+        const { width, height } = dot.getBoundingClientRect();
+        dot.style.left = `${event.clientX - width / 2}px`;
+        dot.style.top = `${event.clientY - height / 2}px`;
+    };
 
     // pointerdown event for the progress bar
-  const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
-           // don't move the videobox element
-    e.stopPropagation();  
+    const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+        // don't move the videobox element
+        e.stopPropagation();
 
         // if recording, do nothing
-      if (props.recording) return;
+        if (props.recording) return;
 
         // get the segment the user clicked on to be dragged
-        const clickedSegment = e.target as HTMLDivElement & EventTarget
+        const clickedSegment = e.target as HTMLDivElement & EventTarget;
 
         // get the profess bar ro add event listeners
         // don't do anything if null
-        const progressBar = progressBarRef.current
-        if (progressBar == null || clickedSegment.id === progressBar.id) return
+        const progressBar = progressBarRef.current;
+        if (progressBar == null || clickedSegment.id === progressBar.id) return;
 
         // if holding shift key, let's remove that segment
         if (e.shiftKey) {
             const segId = parseInt(clickedSegment.id.split('-')[1]);
             setRemoved(segId);
-            return
+            return;
         }
 
         // if holding ctrl key and click, let's undo that segment #hiddenfeature lol
@@ -192,26 +230,26 @@ export function ProgressBar(props: ProgressBarProps) {
         // if we're here, the user is dragging a segment around
         // let the progress bar capture all the pointer events until the user releases (pointerUp)
         const ptrId = e.pointerId;
-        progressBar.setPointerCapture(ptrId)
+        progressBar.setPointerCapture(ptrId);
 
-        const rect = clickedSegment.getBoundingClientRect()
-        // id for segment is like 'segment-1' or 'segment-10', 
+        const rect = clickedSegment.getBoundingClientRect();
+        // id for segment is like 'segment-1' or 'segment-10',
         // so this works to get the id
-        const segId = parseInt(clickedSegment.id.split('-')[1])
+        const segId = parseInt(clickedSegment.id.split('-')[1]);
         // set the selected segment to be the one dragged
-        setDragged(segId)
+        setDragged(segId);
 
-        // this is the logic for storing the lower X bound and upper X bound to know 
+        // this is the logic for storing the lower X bound and upper X bound to know
         // whether a swap is needed between two segments
         let currentHover: CurrentHover = {
             index: segId,
             minX: rect.x,
             maxX: rect.x + rect.width,
-        }
+        };
 
         // create the floating segment that tracks the cursor
-        const detchedSegment = document.createElement("div")
-        initDeatchSegment(detchedSegment, rect);
+        const detchedSegment = document.createElement('div');
+        initDetachSegment(detchedSegment, rect);
 
         const updateSegmentOrder = (event: PointerEvent): void => {
             event.stopPropagation();
@@ -219,6 +257,7 @@ export function ProgressBar(props: ProgressBarProps) {
 
             // this fixes a bug where pointerup doesn't fire while cursor is upped while being dragged
             if (!progressBar.hasPointerCapture(ptrId)) {
+                // eslint-disable-next-line no-use-before-define
                 placeSegmentandCleanup();
                 return;
             }
@@ -228,24 +267,23 @@ export function ProgressBar(props: ProgressBarProps) {
             const curX = event.clientX;
             // handle the left bound
             if (curX < currentHover.minX && currentHover.index > 0) {
-                swapSegments(currentHover.index, currentHover.index - 1)
-                currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover
+                swapSegments(currentHover.index, currentHover.index - 1);
+                currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover;
             }
             // handle the right bound
             else if (curX > currentHover.maxX && currentHover.index < segments.length - 1) {
-                swapSegments(currentHover.index, currentHover.index + 1)
-                currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover
+                swapSegments(currentHover.index, currentHover.index + 1);
+                currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover;
             }
-        }
+        };
 
         // handles when the user is done dragging the segment (pointerUp)
         const placeSegmentandCleanup = (event?: PointerEvent): void => {
             event?.stopPropagation();
             event?.preventDefault();
             // if they put the segment outside of the bounds, remove it
-            if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight))
-                setRemoved(currentHover.index);
-            
+            if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight)) setRemoved(currentHover.index);
+
             // remove the update event listener for pointermove
             progressBar.removeEventListener('pointermove', updateSegmentOrder);
             // remove the floating segment from the DOM
@@ -253,49 +291,16 @@ export function ProgressBar(props: ProgressBarProps) {
             // dragged is -1 is equiv to nothing being dragged, so the normal state
             // so this will place the segment in it's location and update the segment bar
             setDragged(-1);
-        }
+        };
 
         // event listeners that allow the user to drag and release the floating segment
         progressBar.addEventListener('pointermove', updateSegmentOrder);
         progressBar.addEventListener('pointerup', placeSegmentandCleanup, { once: true });
-    }
-
-    const swapSegments = (oldIndex: number, newIndex: number) => {
-        if (newIndex == null) return;
-        setOrdered(prevOrdered => {
-            const temp = { ...prevOrdered[oldIndex] }
-            prevOrdered[oldIndex] = prevOrdered[newIndex]
-            prevOrdered[newIndex] = temp
-            return prevOrdered
-        });
-        // update visually where the segment is hovering over
-        setDragged(newIndex);
-    }
-
-    // functions for the floating segment that tracks the cursor while grabbing it  
-    const initDeatchSegment = (dot: HTMLDivElement, rect: DOMRect) => {
-        dot.classList.add("segment-selected");
-        dot.style.transitionDuration = '0s';
-        dot.style.position = 'absolute';
-        dot.style.zIndex = '999';
-        dot.style.width = `${rect.width}px`;
-        dot.style.height = `${rect.height}px`;
-        dot.style.left = `${rect.x}px`;
-        dot.style.top = `${rect.y}px`;
-        dot.draggable = false;
-        document.body.append(dot);
-    }
-    const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => {
-        // event.stopPropagation()
-        const { width, height } = dot.getBoundingClientRect();
-        dot.style.left = `${event.clientX - width / 2}px`;
-        dot.style.top = `${event.clientY - height / 2}px`;
-    }
-
+    };
 
     return (
         <div className="progressbar" id="progressbar" onPointerDown={onPointerDown} ref={progressBarRef}>
             {segments}
         </div>
-    )
-}
\ No newline at end of file
+    );
+}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 7d123d90c..2bc749e1b 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -44,18 +44,11 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @observable result: Upload.AccessPathInfo | undefined = undefined;
-    @observable videoDuration: number | undefined = undefined;
-
-    @action
-    setVideoDuration = (duration: number) => {
-        this.videoDuration = duration;
-    };
 
     @action
     setResult = (info: Upload.AccessPathInfo, presentation?: Presentation) => {
         this.result = info;
         this.dataDoc.type = DocumentType.VID;
-        this.dataDoc[this.fieldKey + '_duration'] = this.videoDuration;
 
         this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey);
         this.dataDoc[this._props.fieldKey] = new VideoField(this.result.accessPaths.client);
@@ -194,15 +187,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     render() {
         return (
             <div className="recordingBox" style={{ width: '100%' }} ref={this._ref}>
-                {!this.result && (
-                    <RecordingView
-                        forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])}
-                        getControls={this.getControls}
-                        setResult={this.setResult}
-                        setDuration={this.setVideoDuration}
-                        id={DocCast(this.Document.proto)?.[Id] || ''}
-                    />
-                )}
+                {!this.result && <RecordingView forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])} getControls={this.getControls} setResult={this.setResult} id={DocCast(this.Document.proto)?.[Id] || ''} />}
             </div>
         );
     }
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index 9c05a3e94..b8451fe60 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable react/button-has-type */
+/* eslint-disable jsx-a11y/control-has-associated-label */
 import * as React from 'react';
 import { useEffect, useRef, useState } from 'react';
 import { IconContext } from 'react-icons';
@@ -19,19 +22,18 @@ export interface MediaSegment {
 
 interface IRecordingViewProps {
     setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void;
-    setDuration: (seconds: number) => void;
     id: string;
     getControls: (record: () => void, pause: () => void, finish: () => void) => void;
     forceTrackScreen: boolean;
 }
 
 const MAXTIME = 100000;
+const iconVals = { color: '#cc1c08', className: 'video-edit-buttons' };
 
 export function RecordingView(props: IRecordingViewProps) {
     const [recording, setRecording] = useState(false);
     const recordingTimerRef = useRef<number>(0);
     const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second
-    const [playing, setPlaying] = useState(false);
     const [progress, setProgress] = useState(0);
 
     // acts as a "refresh state" to tell progressBar when to undo
@@ -62,7 +64,7 @@ export function RecordingView(props: IRecordingViewProps) {
     useEffect(() => {
         if (finished) {
             // make the total presentation that'll match the concatted video
-            let concatPres = (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));
+            const concatPres = (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));
 
             // this async function uses the server to create the concatted video and then sets the result to it's accessPaths
             (async () => {
@@ -100,16 +102,16 @@ export function RecordingView(props: IRecordingViewProps) {
         return () => clearInterval(interval);
     }, [recording]);
 
+    const setVideoProgressHelper = (curProgrss: number) => {
+        const newProgress = (curProgrss / MAXTIME) * 100;
+        setProgress(newProgress);
+    };
+
     useEffect(() => {
         setVideoProgressHelper(recordingTimer);
         recordingTimerRef.current = recordingTimer;
     }, [recordingTimer]);
 
-    const setVideoProgressHelper = (progress: number) => {
-        const newProgress = (progress / MAXTIME) * 100;
-        setProgress(newProgress);
-    };
-
     const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => {
         const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
 
@@ -131,7 +133,7 @@ export function RecordingView(props: IRecordingViewProps) {
             if (event.data.size > 0) videoChunks.push(event.data);
         };
 
-        videoRecorder.current.onstart = (event: any) => {
+        videoRecorder.current.onstart = () => {
             setRecording(true);
             // start the recording api when the video recorder starts
             (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.start();
@@ -149,7 +151,7 @@ export function RecordingView(props: IRecordingViewProps) {
 
                 // depending on if a presenation exists, add it to the video
                 const presentation = TrackMovements.Instance.yieldPresentation();
-                setVideos(videos => [...videos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]);
+                setVideos(theVideos => [...theVideos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]);
             }
 
             // reset the temporary chunks
@@ -186,7 +188,7 @@ export function RecordingView(props: IRecordingViewProps) {
             e,
             returnTrue,
             returnFalse,
-            e => {
+            () => {
                 // start recording if not already recording
                 if (!videoRecorder.current || videoRecorder.current.state === 'inactive') record();
 
@@ -202,14 +204,8 @@ export function RecordingView(props: IRecordingViewProps) {
         setDoUndo(prev => !prev);
     };
 
-    const handleOnTimeUpdate = () => {
-        playing && setVideoProgressHelper(videoElementRef.current!.currentTime);
-    };
-
     const millisecondToMinuteSecond = (milliseconds: number) => {
-        const toTwoDigit = (digit: number) => {
-            return String(digit).length == 1 ? '0' + digit : digit;
-        };
+        const toTwoDigit = (digit: number) => (String(digit).length === 1 ? '0' + digit : digit);
         const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
         const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
         return toTwoDigit(minutes) + ' : ' + toTwoDigit(seconds);
@@ -219,10 +215,11 @@ export function RecordingView(props: IRecordingViewProps) {
         props.getControls(record, pause, finish);
     }, []);
 
+    const iconUndoVals = React.useMemo(() => ({ color: 'grey', className: 'video-edit-buttons', style: { display: canUndo ? 'inherit' : 'none' } }), []);
     return (
         <div className="recording-container">
             <div className="video-wrapper">
-                <video id={`video-${props.id}`} autoPlay muted onTimeUpdate={() => handleOnTimeUpdate()} ref={videoElementRef} />
+                <video id={`video-${props.id}`} autoPlay muted ref={videoElementRef} />
                 <div className="recording-sign">
                     <span className="dot" />
                     <p className="timer">{millisecondToMinuteSecond(recordingTimer * 10)}</p>
@@ -246,10 +243,10 @@ export function RecordingView(props: IRecordingViewProps) {
                         {!recording &&
                             (videos.length > 0 ? (
                                 <div className="options-wrapper video-edit-wrapper">
-                                    <IconContext.Provider value={{ color: 'grey', className: 'video-edit-buttons', style: { display: canUndo ? 'inherit' : 'none' } }}>
+                                    <IconContext.Provider value={iconUndoVals}>
                                         <MdBackspace onPointerDown={undoPrevious} />
                                     </IconContext.Provider>
-                                    <IconContext.Provider value={{ color: '#cc1c08', className: 'video-edit-buttons' }}>
+                                    <IconContext.Provider value={iconVals}>
                                         <FaCheckCircle
                                             onPointerDown={e => {
                                                 e.stopPropagation();
@@ -268,7 +265,7 @@ export function RecordingView(props: IRecordingViewProps) {
                                                 setTrackScreen(e.target.checked);
                                             }}
                                         />
-                                        <span className="checkmark"></span>
+                                        <span className="checkmark" />
                                         Track Screen
                                     </label>
                                 </div>
diff --git a/src/client/views/nodes/RecordingBox/index.ts b/src/client/views/nodes/RecordingBox/index.ts
index ff21eaed6..e4f9b5e55 100644
--- a/src/client/views/nodes/RecordingBox/index.ts
+++ b/src/client/views/nodes/RecordingBox/index.ts
@@ -1,2 +1,2 @@
-export * from './RecordingView'
-export * from './RecordingBox'
\ No newline at end of file
+export * from './RecordingView';
+export * from './RecordingBox';
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 60d7e4b00..5ebc50a1b 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -1,4 +1,5 @@
-let ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
+/* eslint-disable react/button-has-type */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -17,10 +18,12 @@ import { ContextMenu } from '../ContextMenu';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { OverlayView } from '../OverlayView';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { FieldView, FieldViewProps } from './FieldView';
 import { DocumentIconContainer } from './DocumentIcon';
 import './ScriptingBox.scss';
+
 const _global = (window /* browser */ || global) /* node */ as any;
+const ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
 
 @observer
 export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@@ -79,26 +82,24 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     @computed({ keepAlive: true }) get rawScript() {
         return ScriptCast(this.dataDoc[this.fieldKey])?.script.originalScript ?? '';
     }
-    @computed({ keepAlive: true }) get functionName() {
-        return StrCast(this.dataDoc[this.fieldKey + '-functionName'], '');
-    }
-    @computed({ keepAlive: true }) get functionDescription() {
-        return StrCast(this.dataDoc[this.fieldKey + '-functionDescription'], '');
-    }
-    @computed({ keepAlive: true }) get compileParams() {
-        return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []);
-    }
-
     set rawScript(value) {
         this.dataDoc[this.fieldKey] = new ScriptField(undefined, undefined, value);
     }
+    @computed({ keepAlive: true }) get functionName() {
+        return StrCast(this.dataDoc[this.fieldKey + '-functionName'], '');
+    }
     set functionName(value) {
         this.dataDoc[this.fieldKey + '-functionName'] = value;
     }
+    @computed({ keepAlive: true }) get functionDescription() {
+        return StrCast(this.dataDoc[this.fieldKey + '-functionDescription'], '');
+    }
     set functionDescription(value) {
         this.dataDoc[this.fieldKey + '-functionDescription'] = value;
     }
-
+    @computed({ keepAlive: true }) get compileParams() {
+        return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []);
+    }
     set compileParams(value) {
         this.dataDoc[this.fieldKey + '-params'] = new List<string>(value);
     }
@@ -107,9 +108,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         if (typeof result === 'object') {
             const text = descrip ? result[1] : result[2];
             return text !== undefined ? text : '';
-        } else {
-            return '';
         }
+        return '';
     }
 
     onClickScriptDisable = returnAlways;
@@ -118,19 +118,18 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     componentDidMount() {
         this._props.setContentViewBox?.(this);
         this.rawText = this.rawScript;
-        const observer = new _global.ResizeObserver(
-            action((entries: any) => {
+        const resizeObserver = new _global.ResizeObserver(
+            action(() => {
                 const area = document.querySelector('textarea');
                 if (area) {
-                    for (const {} of entries) {
-                        const getCaretCoordinates = require('textarea-caret');
-                        const caret = getCaretCoordinates(area, this._selection);
-                        this.resetSuggestionPos(caret);
-                    }
+                    // eslint-disable-next-line global-require
+                    const getCaretCoordinates = require('textarea-caret');
+                    const caret = getCaretCoordinates(area, this._selection);
+                    this.resetSuggestionPos(caret);
                 }
             })
         );
-        observer.observe(document.getElementsByClassName('scriptingBox-outerDiv')[0]);
+        resizeObserver.observe(document.getElementsByClassName('scriptingBox-outerDiv')[0]);
     }
 
     @action
@@ -138,12 +137,12 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         if (!this._suggestionRef.current || !this._scriptTextRef.current) return;
         const suggestionWidth = this._suggestionRef.current.offsetWidth;
         const scriptWidth = this._scriptTextRef.current.offsetWidth;
-        const top = caret.top;
-        const x = this.dataDoc.x;
-        let left = caret.left;
+        const { top } = caret;
+        const { x } = this.dataDoc;
+        let { left } = caret;
         if (left + suggestionWidth > x + scriptWidth) {
             const diff = left + suggestionWidth - (x + scriptWidth);
-            left = left - diff;
+            left -= diff;
         }
 
         this._suggestionBoxX = left;
@@ -155,7 +154,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     }
 
     protected createDashEventsTarget = (ele: HTMLDivElement, dropFunc: (e: Event, de: DragManager.DropEvent) => void) => {
-        //used for stacking and masonry view
+        // used for stacking and masonry view
         if (ele) {
             this.dropDisposer?.();
             this.dropDisposer = DragManager.MakeDropTarget(ele, dropFunc, this.layoutDoc);
@@ -164,7 +163,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
     // only included in buttons, transforms scripting UI to a button
     @action
-    onFinish = () => (this.layoutDoc.layout_fieldKey = 'layout');
+    onFinish = () => {
+        this.layoutDoc.layout_fieldKey = 'layout';
+    };
 
     // displays error message
     @action
@@ -176,7 +177,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     @action
     onCompile = () => {
         const params: ScriptParam = {};
-        this.compileParams.forEach(p => (params[p.split(':')[0].trim()] = p.split(':')[1].trim()));
+        this.compileParams.forEach(p => {
+            params[p.split(':')[0].trim()] = p.split(':')[1].trim();
+        });
 
         const result = !this.rawText.trim()
             ? ({ compiled: false, errors: undefined } as any)
@@ -196,7 +199,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     onRun = () => {
         if (this.onCompile()) {
             const bindings: { [name: string]: any } = {};
-            this.paramsNames.forEach(key => (bindings[key] = this.dataDoc[key]));
+            this.paramsNames.forEach(key => {
+                bindings[key] = this.dataDoc[key];
+            });
             // binds vars so user doesnt have to refer to everything as this.<var>
             ScriptCast(this.dataDoc[this.fieldKey], null)?.script.run({ ...bindings, this: this.Document }, this.onError);
         }
@@ -247,7 +252,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
         this.dataDoc.name = this.functionName;
         this.dataDoc.description = this.functionDescription;
-        //this.dataDoc.parameters = this.compileParams;
+        // this.dataDoc.parameters = this.compileParams;
         this.dataDoc.script = this.rawScript;
 
         ScriptManager.Instance.addScript(this.dataDoc);
@@ -255,6 +260,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         this._scriptKeys = ScriptingGlobals.getGlobals();
         this._scriptingDescriptions = ScriptingGlobals.getDescriptions();
         this._scriptingParams = ScriptingGlobals.getParameters();
+        return undefined;
     };
 
     // overlays document numbers (ex. d32) over all documents when clicked on
@@ -267,7 +273,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     @action
     onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => {
         if (de.complete.docDragData) {
-            de.complete.docDragData.droppedDocuments.forEach(doc => (this.dataDoc[fieldKey] = doc));
+            de.complete.docDragData.droppedDocuments.forEach(doc => {
+                this.dataDoc[fieldKey] = doc;
+            });
             e.stopPropagation();
             return true;
         }
@@ -285,8 +293,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     // sets field of the param name to the selected value in drop down box
     @action
     viewChanged = (e: React.ChangeEvent, name: string) => {
-        //@ts-ignore
-        const val = e.target.selectedOptions[0].value;
+        const val = (e.target as any).selectedOptions[0].value;
         this.dataDoc[name] = val[0] === 'S' ? val.substring(1) : val[0] === 'N' ? parseInt(val.substring(1)) : val.substring(1) === 'true';
     };
 
@@ -306,8 +313,26 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     };
 
     renderFunctionInputs() {
-        const descriptionInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionDescription = e.target.value)} placeholder="enter description here" value={this.functionDescription} />;
-        const nameInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionName = e.target.value)} placeholder="enter name here" value={this.functionName} />;
+        const descriptionInput = (
+            <textarea
+                className="scriptingBox-textarea-inputs"
+                onChange={e => {
+                    this.functionDescription = e.target.value;
+                }}
+                placeholder="enter description here"
+                value={this.functionDescription}
+            />
+        );
+        const nameInput = (
+            <textarea
+                className="scriptingBox-textarea-inputs"
+                onChange={e => {
+                    this.functionName = e.target.value;
+                }}
+                placeholder="enter name here"
+                value={this.functionName}
+            />
+        );
 
         return (
             <div className="scriptingBox-inputDiv" onPointerDown={e => this._props.isSelected() && e.stopPropagation()}>
@@ -339,7 +364,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         return (
             <div className="scriptingBox-paramInputs" onFocus={this.onFocus} onBlur={() => this._overlayDisposer?.()} ref={ele => ele && this.createDashEventsTarget(ele, (e, de) => this.onDrop(e, de, parameter))}>
                 <EditableView
-                    display={'block'}
+                    display="block"
                     maxHeight={72}
                     height={35}
                     fontSize={14}
@@ -371,7 +396,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         return (
             <div className="scriptingBox-paramInputs" style={{ overflowY: 'hidden' }}>
                 <EditableView
-                    display={'block'}
+                    display="block"
                     maxHeight={72}
                     height={35}
                     fontSize={14}
@@ -404,6 +429,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             onChange={e => this.viewChanged(e, parameter)}
                             value={typeof this.dataDoc[parameter] === 'string' ? 'S' + StrCast(this.dataDoc[parameter]) : typeof this.dataDoc[parameter] === 'number' ? 'N' + NumCast(this.dataDoc[parameter]) : 'B' + BoolCast(this.dataDoc[parameter])}>
                             {types.map((type, i) => (
+                                // eslint-disable-next-line react/no-array-index-key
                                 <option key={i} className="scriptingBox-viewOption" value={(typeof type === 'string' ? 'S' : typeof type === 'number' ? 'N' : 'B') + type}>
                                     {' '}
                                     {type.toString()}{' '}
@@ -496,6 +522,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
     @action
     suggestionPos = () => {
+        // eslint-disable-next-line global-require
         const getCaretCoordinates = require('textarea-caret');
         const This = this;
         document.querySelector('textarea')?.addEventListener('input', function () {
@@ -509,7 +536,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     keyHandler(e: any, pos: number) {
         e.stopPropagation();
         if (this._lastChar === 'Enter') {
-            this.rawText = this.rawText + ' ';
+            this.rawText += ' ';
         }
         if (e.key === '(') {
             this.suggestionPos();
@@ -524,20 +551,18 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             }
         } else if (e.key === ')') {
             this._paramSuggestion = false;
-        } else {
-            if (e.key === 'Backspace') {
-                if (this._lastChar === '(') {
-                    this._paramSuggestion = false;
-                } else if (this._lastChar === ')') {
-                    if (this.rawText.slice(0, this.rawText.length - 1).split('(').length - 1 > this.rawText.slice(0, this.rawText.length - 1).split(')').length - 1) {
-                        if (this._scriptParamsText.length > 0) {
-                            this._paramSuggestion = true;
-                        }
+        } else if (e.key === 'Backspace') {
+            if (this._lastChar === '(') {
+                this._paramSuggestion = false;
+            } else if (this._lastChar === ')') {
+                if (this.rawText.slice(0, this.rawText.length - 1).split('(').length - 1 > this.rawText.slice(0, this.rawText.length - 1).split(')').length - 1) {
+                    if (this._scriptParamsText.length > 0) {
+                        this._paramSuggestion = true;
                     }
                 }
-            } else if (this.rawText.split('(').length - 1 <= this.rawText.split(')').length - 1) {
-                this._paramSuggestion = false;
             }
+        } else if (this.rawText.split('(').length - 1 <= this.rawText.split(')').length - 1) {
+            this._paramSuggestion = false;
         }
         this._lastChar = e.key === 'Backspace' ? this.rawText[this.rawText.length - 2] : e.key;
 
@@ -559,9 +584,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
             parameters.forEach((element: string, i: number) => {
                 if (i < numEntered - 1) {
-                    first = first + element;
+                    first += element;
                 } else if (i > numEntered - 1) {
-                    last = last + element;
+                    last += element;
                 }
             });
 
@@ -598,16 +623,16 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                     placeholder="write your script here"
                     onFocus={this.onFocus}
                     onBlur={() => this._overlayDisposer?.()}
-                    onChange={action((e: any) => (this.rawText = e.target.value))}
+                    onChange={action((e: any) => {
+                        this.rawText = e.target.value;
+                    })}
                     value={this.rawText}
-                    movePopupAsYouType={true}
+                    movePopupAsYouType
                     loadingComponent={() => <span>Loading</span>}
                     trigger={{
                         ' ': {
                             dataProvider: (token: any) => this.handleToken(token),
-                            component: (blob: any) => {
-                                return this.renderFuncListElement(blob.entity);
-                            },
+                            component: (blob: any) => this.renderFuncListElement(blob.entity),
                             output: (item: any, trigger: any) => {
                                 this._spaced = true;
                                 return trigger + item.trim();
@@ -615,9 +640,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         },
                         '.': {
                             dataProvider: (token: any) => this.handleToken(token),
-                            component: (blob: any) => {
-                                return this.renderFuncListElement(blob.entity);
-                            },
+                            component: (blob: any) => this.renderFuncListElement(blob.entity),
                             output: (item: any, trigger: any) => {
                                 this._spaced = true;
                                 return trigger + item.trim();
@@ -653,16 +676,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         // params box on bottom
         const parameterInput = (
             <div className="scriptingBox-params">
-                <EditableView
-                    display={'block'}
-                    maxHeight={72}
-                    height={35}
-                    fontSize={22}
-                    contents={''}
-                    GetValue={returnEmptyString}
-                    SetValue={value => (value && value !== ' ' ? this.compileParam(value) : false)}
-                    placeholder={'enter parameters here'}
-                />
+                <EditableView display="block" maxHeight={72} height={35} fontSize={22} contents="" GetValue={returnEmptyString} SetValue={value => (value && value !== ' ' ? this.compileParam(value) : false)} placeholder="enter parameters here" />
             </div>
         );
 
@@ -670,13 +684,14 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         const definedParameters = !this.compileParams.length ? null : (
             <div className="scriptingBox-plist" style={{ width: '30%' }}>
                 {this.compileParams.map((parameter, i) => (
+                    // eslint-disable-next-line react/no-array-index-key
                     <div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
                         <EditableView
-                            display={'block'}
+                            display="block"
                             maxHeight={72}
                             height={35}
                             fontSize={12}
-                            background-color={'beige'}
+                            background-color="beige"
                             contents={parameter}
                             GetValue={() => parameter}
                             SetValue={value => (value && value !== ' ' ? this.compileParam(value, i) : this.onDelete(i))}
@@ -749,6 +764,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 {!this.compileParams.length || !this.paramsNames ? null : (
                     <div className="scriptingBox-plist">
                         {this.paramsNames.map((parameter: string, i: number) => (
+                            // eslint-disable-next-line react/no-array-index-key
                             <div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
                                 <div className="scriptingBox-wrapper" style={{ maxHeight: '40px' }}>
                                     <div className="scriptingBox-paramNames"> {`${parameter}:${this.paramsTypes[i]} = `} </div>
diff --git a/src/client/views/nodes/SliderBox-components.tsx b/src/client/views/nodes/SliderBox-components.tsx
index e19f28f08..b9f215d77 100644
--- a/src/client/views/nodes/SliderBox-components.tsx
+++ b/src/client/views/nodes/SliderBox-components.tsx
@@ -1,6 +1,6 @@
-import * as React from "react";
-import { SliderItem } from "react-compound-slider";
-import "./SliderBox-tooltip.css";
+import * as React from 'react';
+import { SliderItem } from 'react-compound-slider';
+import './SliderBox-tooltip.css';
 
 const { Component, Fragment } = React;
 
@@ -8,24 +8,24 @@ const { Component, Fragment } = React;
 // TOOLTIP RAIL
 // *******************************************************
 const railStyle: React.CSSProperties = {
-    position: "absolute",
-    width: "100%",
+    position: 'absolute',
+    width: '100%',
     height: 40,
     borderRadius: 7,
-    cursor: "pointer",
+    cursor: 'pointer',
     opacity: 0.3,
     zIndex: 300,
-    border: "1px solid grey"
+    border: '1px solid grey',
 };
 
 const railCenterStyle: React.CSSProperties = {
-    position: "absolute",
-    width: "100%",
+    position: 'absolute',
+    width: '100%',
     height: 14,
     borderRadius: 7,
-    cursor: "pointer",
-    pointerEvents: "none",
-    backgroundColor: "rgb(155,155,155)"
+    cursor: 'pointer',
+    pointerEvents: 'none',
+    backgroundColor: 'rgb(155,155,155)',
 };
 
 interface TooltipRailProps {
@@ -37,21 +37,21 @@ interface TooltipRailProps {
 export class TooltipRail extends Component<TooltipRailProps> {
     state = {
         value: null,
-        percent: null
+        percent: null,
     };
 
     static defaultProps = {
-        disabled: false
+        disabled: false,
     };
 
     onMouseEnter = () => {
-        document.addEventListener("mousemove", this.onMouseMove);
-    }
+        document.addEventListener('mousemove', this.onMouseMove);
+    };
 
     onMouseLeave = () => {
         this.setState({ value: null, percent: null });
-        document.removeEventListener("mousemove", this.onMouseMove);
-    }
+        document.removeEventListener('mousemove', this.onMouseMove);
+    };
 
     onMouseMove = (e: Event) => {
         const { activeHandleID, getEventData } = this.props;
@@ -61,7 +61,7 @@ export class TooltipRail extends Component<TooltipRailProps> {
         } else {
             this.setState(getEventData(e));
         }
-    }
+    };
 
     render() {
         const { value, percent } = this.state;
@@ -73,11 +73,10 @@ export class TooltipRail extends Component<TooltipRailProps> {
                     <div
                         style={{
                             left: `${percent}%`,
-                            position: "absolute",
-                            marginLeft: "-11px",
-                            marginTop: "-35px"
-                        }}
-                    >
+                            position: 'absolute',
+                            marginLeft: '-11px',
+                            marginTop: '-35px',
+                        }}>
                         <div className="tooltip">
                             <span className="tooltiptext">Value: {value}</span>
                         </div>
@@ -87,7 +86,7 @@ export class TooltipRail extends Component<TooltipRailProps> {
                     style={railStyle}
                     {...getRailProps({
                         onMouseEnter: this.onMouseEnter,
-                        onMouseLeave: this.onMouseLeave
+                        onMouseLeave: this.onMouseLeave,
                     })}
                 />
                 <div style={railCenterStyle} />
@@ -110,20 +109,20 @@ interface HandleProps {
 
 export class Handle extends Component<HandleProps> {
     static defaultProps = {
-        disabled: false
+        disabled: false,
     };
 
     state = {
-        mouseOver: false
+        mouseOver: false,
     };
 
     onMouseEnter = () => {
         this.setState({ mouseOver: true });
-    }
+    };
 
     onMouseLeave = () => {
         this.setState({ mouseOver: false });
-    }
+    };
 
     render() {
         const {
@@ -131,7 +130,7 @@ export class Handle extends Component<HandleProps> {
             handle: { id, value, percent },
             isActive,
             disabled,
-            getHandleProps
+            getHandleProps,
         } = this.props;
         const { mouseOver } = this.state;
 
@@ -141,11 +140,10 @@ export class Handle extends Component<HandleProps> {
                     <div
                         style={{
                             left: `${percent}%`,
-                            position: "absolute",
-                            marginLeft: "-11px",
-                            marginTop: "-35px"
-                        }}
-                    >
+                            position: 'absolute',
+                            marginLeft: '-11px',
+                            marginTop: '-35px',
+                        }}>
                         <div className="tooltip">
                             <span className="tooltiptext">Value: {value}</span>
                         </div>
@@ -158,21 +156,21 @@ export class Handle extends Component<HandleProps> {
                     aria-valuenow={value}
                     style={{
                         left: `${percent}%`,
-                        position: "absolute",
-                        marginLeft: "-11px",
-                        marginTop: "-6px",
+                        position: 'absolute',
+                        marginLeft: '-11px',
+                        marginTop: '-6px',
                         zIndex: 400,
                         width: 24,
                         height: 24,
-                        cursor: "pointer",
+                        cursor: 'pointer',
                         border: 0,
-                        borderRadius: "50%",
-                        boxShadow: "1px 1px 1px 1px rgba(0, 0, 0, 0.4)",
-                        backgroundColor: disabled ? "#666" : "#3e1db3"
+                        borderRadius: '50%',
+                        boxShadow: '1px 1px 1px 1px rgba(0, 0, 0, 0.4)',
+                        backgroundColor: disabled ? '#666' : '#3e1db3',
                     }}
                     {...getHandleProps(id, {
                         onMouseEnter: this.onMouseEnter,
-                        onMouseLeave: this.onMouseLeave
+                        onMouseLeave: this.onMouseLeave,
                     })}
                 />
             </Fragment>
@@ -190,23 +188,18 @@ interface TrackProps {
     getTrackProps: () => object;
 }
 
-export function Track({
-    source,
-    target,
-    getTrackProps,
-    disabled = false
-}: TrackProps) {
+export function Track({ source, target, getTrackProps, disabled = false }: TrackProps) {
     return (
         <div
             style={{
-                position: "absolute",
+                position: 'absolute',
                 height: 14,
                 zIndex: 1,
-                backgroundColor: disabled ? "#999" : "#3e1db3",
+                backgroundColor: disabled ? '#999' : '#3e1db3',
                 borderRadius: 7,
-                cursor: "pointer",
+                cursor: 'pointer',
                 left: `${source.percent}%`,
-                width: `${target.percent - source.percent}%`
+                width: `${target.percent - source.percent}%`,
             }}
             {...getTrackProps()}
         />
@@ -229,25 +222,24 @@ export function Tick({ tick, count, format = defaultFormat }: TickProps) {
         <div>
             <div
                 style={{
-                    position: "absolute",
+                    position: 'absolute',
                     marginTop: 20,
                     width: 1,
                     height: 5,
-                    backgroundColor: "rgb(200,200,200)",
-                    left: `${tick.percent}%`
+                    backgroundColor: 'rgb(200,200,200)',
+                    left: `${tick.percent}%`,
                 }}
             />
             <div
                 style={{
-                    position: "absolute",
+                    position: 'absolute',
                     marginTop: 25,
                     fontSize: 10,
-                    textAlign: "center",
+                    textAlign: 'center',
                     marginLeft: `${-(100 / count) / 2}%`,
                     width: `${100 / count}%`,
-                    left: `${tick.percent}%`
-                }}
-            >
+                    left: `${tick.percent}%`,
+                }}>
                 {format(tick.value)}
             </div>
         </div>
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 5b3f37993..1121134cf 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -271,7 +271,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         setupMoveUpEvents(
             e.target,
             e,
-            action((e, down, delta) => {
+            action((moveEv, down, delta) => {
                 if (this._controlsTransform) {
                     this._controlsTransform.X = Math.max(0, Math.min(delta[0] + this._controlsTransform.X, window.innerWidth));
                     this._controlsTransform.Y = Math.max(0, Math.min(delta[1] + this._controlsTransform.Y, window.innerHeight));
@@ -309,8 +309,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             // convert to desired file format
             const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
             // if you want to preview the captured image,
-            const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
-            const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[\.\/\?\=]/g, '_'));
+            const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, '');
+            const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[./?=]/g, '_'));
             const filename = basename(encodedFilename);
             ClientUtils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
         }
@@ -410,7 +410,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             canvas.height = 100;
             canvas.width = 100;
             canvas.getContext('2d')?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 100, 100);
-            const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
+            const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, '');
             const encodedFilename = encodeURIComponent('thumbnail' + retitled + '_' + video.currentTime.toString().replace(/\./, '_'));
             thumbnailPromises?.push(ClientUtils.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true));
             const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1);
@@ -486,7 +486,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                     },
                     icon: 'expand-arrows-alt',
                 });
-            subitems.push({ description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered', event: () => (this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks), icon: 'expand-arrows-alt' });
+            subitems.push({
+                description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered',
+                event: () => {
+                    this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks;
+                },
+                icon: 'expand-arrows-alt',
+            });
             subitems.push({
                 description: (this.layoutDoc.dontAutoPlayFollowedLinks ? '' : "Don't") + ' play when link is selected',
                 event: () => {
@@ -494,7 +500,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                 },
                 icon: 'expand-arrows-alt',
             });
-            subitems.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick', event: () => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors), icon: 'expand-arrows-alt' });
+            subitems.push({
+                description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick',
+                event: () => {
+                    this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors;
+                },
+                icon: 'expand-arrows-alt',
+            });
             // subitems.push({ description: "Start Trim All", event: () => this.startTrim(TrimScope.All), icon: "expand-arrows-alt" });
             // subitems.push({ description: "Start Trim Clip", event: () => this.startTrim(TrimScope.Clip), icon: "expand-arrows-alt" });
             // subitems.push({ description: "Stop Trim", event: () => this.finishTrim(), icon: "expand-arrows-alt" });
@@ -593,8 +605,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         setupMoveUpEvents(
             this,
             e,
-            e => {
-                this.Snapshot(e.clientX, e.clientY);
+            moveEv => {
+                this.Snapshot(moveEv.clientX, moveEv.clientY);
                 return true;
             },
             emptyFunction,
@@ -657,13 +669,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
 
     // for annotating, adds doc with time info
     @action.bound
-    addDocWithTimecode(doc: Doc | Doc[]): boolean {
-        const docs = doc instanceof Doc ? [doc] : doc;
+    addDocWithTimecode(docIn: Doc | Doc[]): boolean {
+        const docs = docIn instanceof Doc ? [docIn] : docIn;
         const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
         docs.forEach(doc => {
             doc._timecodeToHide = (doc._timecodeToShow = curTime) + 1;
         });
-        return this.addDocument(doc);
+        return this.addDocument(docs);
     }
 
     // play back the audio from seekTimeInSeconds, fullPlay tells whether clip is being played to end vs link range
@@ -671,7 +683,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => {
         clearTimeout(this._playRegionTimer);
         this._playRegionTimer = undefined;
-        if (isNaN(this.player?.duration)) {
+        if (this.player?.duration === undefined || isNaN(this.player.duration)) {
             setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
         } else if (this.player) {
             // trimBounds override requested playback bounds
@@ -723,7 +735,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                 e,
                 returnFalse,
                 returnFalse,
-                action((e: PointerEvent, doubleTap?: boolean) => {
+                action((clickEv: PointerEvent, doubleTap?: boolean) => {
                     if (doubleTap) {
                         this.startTrim(TrimScope.All);
                     } else if (this.timeline) {
@@ -782,9 +794,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             setupMoveUpEvents(
                 this,
                 e,
-                action(e => {
+                action(moveEv => {
                     MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
+                    this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
                     return true;
                 }),
                 returnFalse,
diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx
index 7fd799952..2d1d3d7db 100644
--- a/src/client/views/nodes/audio/AudioWaveform.tsx
+++ b/src/client/views/nodes/audio/AudioWaveform.tsx
@@ -7,8 +7,8 @@ import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { Cast } from '../../../../fields/Types';
 import { numberRange } from '../../../../Utils';
+import { Colors } from '../../global/globalEnums';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { Colors } from './../../global/globalEnums';
 import './AudioWaveform.scss';
 import { WaveCanvas } from './WaveCanvas';
 
@@ -62,7 +62,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
         return NumListCast(this._props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)]);
     }
 
-    audioBucketField = (start: number, end: number, zoomFactor: number) => this._props.fieldKey + '_audioBuckets/' + '/' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
+    audioBucketField = (start: number, end: number, zoomFactor: number) => this._props.fieldKey + '_audioBuckets//' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
 
     componentWillUnmount() {
         this._disposer?.();
@@ -72,7 +72,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
         this._disposer = reaction(
             () => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this._props.zoomFactor }),
             ({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
-                if (!this._props.layoutDoc[fieldKey] && this._props.layoutDoc.layout_fieldKey != 'layout_icon') {
+                if (!this._props.layoutDoc[fieldKey] && this._props.layoutDoc.layout_fieldKey !== 'layout_icon') {
                     // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
                     const waveform = Cast(this._props.layoutDoc[this.audioBucketField(0, this._props.rawDuration, 1)], listSpec('number'));
                     this._props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice((clipStart / this._props.rawDuration) * waveform.length, (clipEnd / this._props.rawDuration) * waveform.length));
@@ -109,7 +109,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
                     progressColor={Colors.MEDIUM_BLUE_ALT}
                     progress={this._props.progress ?? 1}
                     barWidth={200 / this.audioBuckets.length}
-                    //gradientColors={this._props.gradientColors}
+                    // gradientColors={this._props.gradientColors}
                     peaks={this.audioBuckets}
                     width={(this._props.PanelWidth ?? 0) * window.devicePixelRatio}
                     height={this.waveHeight * window.devicePixelRatio}
diff --git a/src/client/views/nodes/audio/WaveCanvas.tsx b/src/client/views/nodes/audio/WaveCanvas.tsx
index d3f5669a2..eacda2d42 100644
--- a/src/client/views/nodes/audio/WaveCanvas.tsx
+++ b/src/client/views/nodes/audio/WaveCanvas.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import React from 'react';
 
 interface WaveCanvasProps {
@@ -14,7 +15,7 @@ interface WaveCanvasProps {
 
 export class WaveCanvas extends React.Component<WaveCanvasProps> {
     // If the first value of peaks is negative, addToIndices will be 1
-    posPeaks = (peaks: number[], addToIndices: number) => peaks.filter((_, index) => (index + addToIndices) % 2 == 0);
+    posPeaks = (peaks: number[], addToIndices: number) => peaks.filter((_, index) => (index + addToIndices) % 2 === 0);
 
     drawBars = (waveCanvasCtx: CanvasRenderingContext2D, width: number, halfH: number, peaks: number[]) => {
         // Bar wave draws the bottom only as a reflection of the top,
@@ -47,6 +48,7 @@ export class WaveCanvas extends React.Component<WaveCanvasProps> {
 
         // A half-pixel offset makes lines crisp
         const $ = 0.5 / this.props.pixelRatio;
+        // eslint-disable-next-line no-bitwise
         const length = ~~(allPeaks.length / 2); // ~~ is Math.floor for positive numbers.
 
         const scale = width / length;
@@ -55,14 +57,14 @@ export class WaveCanvas extends React.Component<WaveCanvasProps> {
         waveCanvasCtx.beginPath();
         waveCanvasCtx.moveTo($, halfH);
 
-        for (var i = 0; i < length; i++) {
-            var h = Math.round((allPeaks[2 * i] / absmax) * halfH);
+        for (let i = 0; i < length; i++) {
+            const h = Math.round((allPeaks[2 * i] / absmax) * halfH);
             waveCanvasCtx.lineTo(i * scale + $, halfH - h);
         }
 
         // Draw the bottom edge going backwards, to make a single closed hull to fill.
-        for (var i = length - 1; i >= 0; i--) {
-            var h = Math.round((allPeaks[2 * i + 1] / absmax) * halfH);
+        for (let i = length - 1; i >= 0; i--) {
+            const h = Math.round((allPeaks[2 * i + 1] / absmax) * halfH);
             waveCanvasCtx.lineTo(i * scale + $, halfH - h);
         }
 
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 8577510e3..5893c346f 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -57,12 +57,13 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     docBackgroundColor(type: string): string {
         // TODO: Return a different color based on the event type
+        console.log(type);
         return 'blue';
     }
 
     get calendarEvents(): EventSourceInput | undefined {
         if (this.childDocs.length === 0) return undefined;
-        return this.childDocs.map((doc, idx) => {
+        return this.childDocs.map(doc => {
             const docTitle = StrCast(doc.title);
             const docDateRange = StrCast(doc.date_range);
             const [startDate, endDate] = dateRangeStrToDates(docDateRange);
@@ -85,7 +86,7 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
         });
     }
 
-    handleEventClick = (arg: EventClickArg) => {
+    handleEventClick = (/* arg: EventClickArg */) => {
         // TODO: open popover with event description, option to open CalendarManager and change event date, delete event, etc.
     };
 
@@ -113,7 +114,7 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
     render() {
         return (
             <div className="calendar-box-conatiner">
-                <div id="calendar-box-v1"></div>
+                <div id="calendar-box-v1" />
             </div>
         );
     }
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index a72ed1813..3ec49fa27 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -1,60 +1,11 @@
 import { TextSelection } from 'prosemirror-state';
 import * as ReactDOM from 'react-dom/client';
-import { Doc } from '../../../../fields/Doc';
-import { DocServer } from '../../../DocServer';
 import * as React from 'react';
 import { IReactionDisposer, computed, reaction } from 'mobx';
+import { Doc } from '../../../../fields/Doc';
+import { DocServer } from '../../../DocServer';
 import { NumCast } from '../../../../fields/Types';
 
-// creates an inline comment in a note when '>>' is typed.
-// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
-// the comment can be toggled on/off with the '<-' text anchor.
-export class DashDocCommentView {
-    dom: HTMLDivElement; // container for label and value
-    root: any;
-    node: any;
-
-    constructor(node: any, view: any, getPos: any) {
-        this.node = node;
-        this.dom = document.createElement('div');
-        this.dom.style.width = node.attrs.width;
-        this.dom.style.height = node.attrs.height;
-        this.dom.style.fontWeight = 'bold';
-        this.dom.style.position = 'relative';
-        this.dom.style.display = 'inline-block';
-        this.dom.onkeypress = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onkeydown = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onkeyup = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onmousedown = function (e: any) {
-            e.stopPropagation();
-        };
-
-        this.root = ReactDOM.createRoot(this.dom);
-        this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
-        (this as any).dom = this.dom;
-    }
-
-    setHeight = (hgt: number) => {
-        !this.node.attrs.reflow && DocServer.GetRefField(this.node.attrs.docId).then(doc => doc instanceof Doc && (this.dom.style.height = hgt + ''));
-    };
-
-    destroy() {
-        this.root.unmount();
-    }
-    deselectNode() {
-        this.dom.classList.remove('ProseMirror-selectednode');
-    }
-    selectNode() {
-        this.dom.classList.add('ProseMirror-selectednode');
-    }
-}
-
 interface IDashDocCommentViewInternal {
     docId: string;
     view: any;
@@ -65,9 +16,6 @@ interface IDashDocCommentViewInternal {
 export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
     _reactionDisposer: IReactionDisposer | undefined;
 
-    @computed get _dashDoc() {
-        return DocServer.GetRefField(this.props.docId);
-    }
     constructor(props: any) {
         super(props);
         this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
@@ -77,58 +25,62 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
     }
     componentDidMount(): void {
         this._reactionDisposer?.();
-        this._dashDoc.then(
-            doc =>
-                doc instanceof Doc &&
-                (this._reactionDisposer = reaction(
+        this._dashDoc.then(doc => {
+            if (doc instanceof Doc) {
+                this._reactionDisposer = reaction(
                     () => NumCast((doc as Doc)._height),
                     hgt => this.props.setHeight(hgt),
-                    {
-                        fireImmediately: true,
-                    }
-                ))
-        );
+                    { fireImmediately: true }
+                );
+            }
+        });
     }
     componentWillUnmount(): void {
         this._reactionDisposer?.();
     }
 
-    onPointerLeaveCollapsed(e: any) {
+    @computed get _dashDoc() {
+        return DocServer.GetRefField(this.props.docId);
+    }
+
+    onPointerLeaveCollapsed = (e: any) => {
         this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
         e.preventDefault();
         e.stopPropagation();
-    }
+    };
 
-    onPointerEnterCollapsed(e: any) {
+    onPointerEnterCollapsed = (e: any) => {
         this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
         e.preventDefault();
         e.stopPropagation();
-    }
+    };
 
-    onPointerUpCollapsed(e: any) {
+    onPointerUpCollapsed = (e: any) => {
         const target = this.targetNode();
 
         if (target) {
             const expand = target.hidden;
-            const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+            const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: !target.node.attrs.hidden });
             this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
             setTimeout(() => {
                 expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
                 try {
                     this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1))));
-                } catch (e) {}
+                } catch (err) {
+                    /* empty */
+                }
             }, 0);
         }
         e.stopPropagation();
-    }
+    };
 
-    onPointerDownCollapsed(e: any) {
+    onPointerDownCollapsed = (e: any) => {
         e.stopPropagation();
-    }
+    };
 
     targetNode = () => {
         // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
-        const state = this.props.view.state;
+        const { state } = this.props.view;
         for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) {
             const m = state.doc.nodeAt(i);
             if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docId === this.props.docId) {
@@ -141,7 +93,9 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
         setTimeout(() => {
             try {
                 this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2)));
-            } catch (e) {}
+            } catch (err) {
+                /* empty */
+            }
         }, 0);
         return undefined;
     };
@@ -154,7 +108,60 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
                 onPointerLeave={this.onPointerLeaveCollapsed}
                 onPointerEnter={this.onPointerEnterCollapsed}
                 onPointerUp={this.onPointerUpCollapsed}
-                onPointerDown={this.onPointerDownCollapsed}></span>
+                onPointerDown={this.onPointerDownCollapsed}
+            />
         );
     }
 }
+
+// creates an inline comment in a note when '>>' is typed.
+// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
+// the comment can be toggled on/off with the '<-' text anchor.
+export class DashDocCommentView {
+    dom: HTMLDivElement; // container for label and value
+    root: any;
+    node: any;
+
+    constructor(node: any, view: any, getPos: any) {
+        this.node = node;
+        this.dom = document.createElement('div');
+        this.dom.style.width = node.attrs.width;
+        this.dom.style.height = node.attrs.height;
+        this.dom.style.fontWeight = 'bold';
+        this.dom.style.position = 'relative';
+        this.dom.style.display = 'inline-block';
+        this.dom.onkeypress = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onkeydown = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onkeyup = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onmousedown = function (e: any) {
+            e.stopPropagation();
+        };
+
+        this.root = ReactDOM.createRoot(this.dom);
+        this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
+        (this as any).dom = this.dom;
+    }
+
+    setHeight = (hgt: number) => {
+        !this.node.attrs.reflow &&
+            DocServer.GetRefField(this.node.attrs.docId).then(doc => {
+                doc instanceof Doc && (this.dom.style.height = hgt + '');
+            });
+    };
+
+    destroy() {
+        this.root.unmount();
+    }
+    deselectNode() {
+        this.dom.classList.remove('ProseMirror-selectednode');
+    }
+    selectNode() {
+        this.dom.classList.add('ProseMirror-selectednode');
+    }
+}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index dee7d70bb..f311b3cdd 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,10 +1,10 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { NodeSelection } from 'prosemirror-state';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
-import { Utils } from '../../../../Utils';
-import { returnFalse } from '../../../../ClientUtils';
+import { ClientUtils, returnFalse } from '../../../../ClientUtils';
 import { Doc } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { NumCast } from '../../../../fields/Types';
@@ -16,63 +16,7 @@ import { DocumentView } from '../DocumentView';
 import { FocusViewOptions } from '../FieldView';
 import { FormattedTextBox } from './FormattedTextBox';
 
-var horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
-export class DashDocView {
-    dom: HTMLSpanElement; // container for label and value
-    root: any;
-
-    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-        this.dom = document.createElement('span');
-        this.dom.style.position = 'relative';
-        this.dom.style.textIndent = '0';
-        this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString();
-        this.dom.style.height = node.attrs.height;
-        this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
-        (this.dom.style as any).float = node.attrs.float;
-        this.dom.onkeypress = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onkeydown = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onkeyup = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onmousedown = function (e: any) {
-            e.stopPropagation();
-        };
-
-        this.root = ReactDOM.createRoot(this.dom);
-        this.root.render(
-            <DashDocViewInternal
-                docId={node.attrs.docId}
-                embedding={node.attrs.embedding}
-                width={node.attrs.width}
-                height={node.attrs.height}
-                hidden={node.attrs.hidden}
-                fieldKey={node.attrs.fieldKey}
-                tbox={tbox}
-                view={view}
-                node={node}
-                getPos={getPos}
-            />
-        );
-    }
-    destroy() {
-        setTimeout(() => {
-            try {
-                this.root.unmount();
-            } catch {}
-        });
-    }
-    deselectNode() {
-        this.dom.style.backgroundColor = '';
-    }
-    selectNode() {
-        this.dom.style.backgroundColor = 'rgb(141, 182, 247)';
-    }
-}
-
+const horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
 interface IDashDocViewInternal {
     docId: string;
     embedding: string;
@@ -85,6 +29,7 @@ interface IDashDocViewInternal {
     node: any;
     getPos: any;
 }
+
 @observer
 export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewInternal> {
     _spanRef = React.createRef<HTMLDivElement>();
@@ -158,7 +103,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
 
     getDocTransform = () => {
         if (!this._spanRef.current) return Transform.Identity();
-        const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
+        const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._spanRef.current);
         return new Transform(-translateX, -translateY, 1).scale(1 / scale);
     };
     outerFocus = (target: Doc, options: FocusViewOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
@@ -227,3 +172,61 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
         );
     }
 }
+
+export class DashDocView {
+    dom: HTMLSpanElement; // container for label and value
+    root: any;
+
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this.dom = document.createElement('span');
+        this.dom.style.position = 'relative';
+        this.dom.style.textIndent = '0';
+        this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString();
+        this.dom.style.height = node.attrs.height;
+        this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
+        (this.dom.style as any).float = node.attrs.float;
+        this.dom.onkeypress = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onkeydown = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onkeyup = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onmousedown = function (e: any) {
+            e.stopPropagation();
+        };
+
+        this.root = ReactDOM.createRoot(this.dom);
+        this.root.render(
+            <DashDocViewInternal
+                docId={node.attrs.docId}
+                embedding={node.attrs.embedding}
+                width={node.attrs.width}
+                height={node.attrs.height}
+                hidden={node.attrs.hidden}
+                fieldKey={node.attrs.fieldKey}
+                tbox={tbox}
+                view={view}
+                node={node}
+                getPos={getPos}
+            />
+        );
+    }
+    destroy() {
+        setTimeout(() => {
+            try {
+                this.root.unmount();
+            } catch {
+                /* empty */
+            }
+        });
+    }
+    deselectNode() {
+        this.dom.style.backgroundColor = '';
+    }
+    selectNode() {
+        this.dom.style.backgroundColor = 'rgb(141, 182, 247)';
+    }
+}
diff --git a/src/client/views/nodes/formattedText/EquationEditor.tsx b/src/client/views/nodes/formattedText/EquationEditor.tsx
index b4102e08e..d9b1a2cf8 100644
--- a/src/client/views/nodes/formattedText/EquationEditor.tsx
+++ b/src/client/views/nodes/formattedText/EquationEditor.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import React, { Component, createRef } from 'react';
 
 // Import JQuery, required for the functioning of the equation editor
@@ -5,11 +6,9 @@ import $ from 'jquery';
 
 import './EquationEditor.scss';
 
-// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
 // @ts-ignore
 window.jQuery = $;
 
-// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
 // @ts-ignore
 require('mathquill/build/mathquill');
 
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index b90653acc..5167c8f2a 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { IReactionDisposer } from 'mobx';
 import { observer } from 'mobx-react';
 import { TextSelection } from 'prosemirror-state';
@@ -10,44 +11,6 @@ import EquationEditor from './EquationEditor';
 import { FormattedTextBox } from './FormattedTextBox';
 import { DocData } from '../../../../fields/DocSymbols';
 
-export class EquationView {
-    dom: HTMLDivElement; // container for label and value
-    root: any;
-    tbox: FormattedTextBox;
-    view: any;
-    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-        this.tbox = tbox;
-        this.view = view;
-        this.dom = document.createElement('div');
-        this.dom.style.width = node.attrs.width;
-        this.dom.style.height = node.attrs.height;
-        this.dom.style.position = 'relative';
-        this.dom.style.display = 'inline-block';
-        this.dom.onmousedown = function (e: any) {
-            e.stopPropagation();
-        };
-
-        this.root = ReactDOM.createRoot(this.dom);
-        this.root.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />);
-    }
-    _editor: EquationEditor | undefined;
-    setEditor = (editor?: EquationEditor) => (this._editor = editor);
-    destroy() {
-        this.root.unmount();
-    }
-    setSelection() {
-        this._editor?.mathField.focus();
-    }
-    selectNode() {
-        this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus
-        setTimeout(() => {
-            this._editor?.mathField.focus();
-            setTimeout(() => (this.tbox._applyingChange = ''));
-        });
-    }
-    deselectNode() {}
-}
-
 interface IEquationViewInternal {
     fieldKey: string;
     tbox: FormattedTextBox;
@@ -70,12 +33,12 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
         this._textBoxDoc = props.tbox.Document;
     }
 
-    componentWillUnmount() {
-        this._reactionDisposer?.();
-    }
     componentDidMount() {
         this.props.setEditor(this._ref.current ?? undefined);
     }
+    componentWillUnmount() {
+        this._reactionDisposer?.();
+    }
 
     render() {
         return (
@@ -100,12 +63,56 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
                 <EquationEditor
                     ref={this._ref}
                     value={StrCast(this._textBoxDoc[DocData][this._fieldKey])}
-                    onChange={(str: any) => (this._textBoxDoc[DocData][this._fieldKey] = str)}
+                    onChange={(str: any) => {
+                        this._textBoxDoc[DocData][this._fieldKey] = str;
+                    }}
                     autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
                     autoOperatorNames="sin cos tan"
-                    spaceBehavesLikeTab={true}
+                    spaceBehavesLikeTab
                 />
             </div>
         );
     }
 }
+
+export class EquationView {
+    dom: HTMLDivElement; // container for label and value
+    root: any;
+    tbox: FormattedTextBox;
+    view: any;
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this.tbox = tbox;
+        this.view = view;
+        this.dom = document.createElement('div');
+        this.dom.style.width = node.attrs.width;
+        this.dom.style.height = node.attrs.height;
+        this.dom.style.position = 'relative';
+        this.dom.style.display = 'inline-block';
+        this.dom.onmousedown = function (e: any) {
+            e.stopPropagation();
+        };
+
+        this.root = ReactDOM.createRoot(this.dom);
+        this.root.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />);
+    }
+    _editor: EquationEditor | undefined;
+    setEditor = (editor?: EquationEditor) => {
+        this._editor = editor;
+    };
+    destroy() {
+        this.root.unmount();
+    }
+    setSelection() {
+        this._editor?.mathField.focus();
+    }
+    selectNode() {
+        this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus
+        setTimeout(() => {
+            this._editor?.mathField.focus();
+            setTimeout(() => {
+                this.tbox._applyingChange = '';
+            });
+        });
+    }
+    deselectNode() {}
+}
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
index b327e5137..4641da2e9 100644
--- a/src/client/views/nodes/formattedText/FootnoteView.tsx
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -2,9 +2,9 @@ import { EditorView } from 'prosemirror-view';
 import { EditorState } from 'prosemirror-state';
 import { keymap } from 'prosemirror-keymap';
 import { baseKeymap, toggleMark } from 'prosemirror-commands';
-import { schema } from './schema_rts';
 import { redo, undo } from 'prosemirror-history';
 import { StepMap } from 'prosemirror-transform';
+import { schema } from './schema_rts';
 
 export class FootnoteView {
     innerView: any;
@@ -100,8 +100,8 @@ export class FootnoteView {
         this.innerView.updateState(state);
 
         if (!tr.getMeta('fromOutside')) {
-            const outerTr = this.outerView.state.tr,
-                offsetMap = StepMap.offset(this.getPos() + 1);
+            const outerTr = this.outerView.state.tr;
+            const offsetMap = StepMap.offset(this.getPos() + 1);
             for (const transaction of transactions) {
                 for (const step of transaction.steps) {
                     outerTr.step(step.map(offsetMap));
@@ -115,7 +115,7 @@ export class FootnoteView {
         if (!node.sameMarkup(this.node)) return false;
         this.node = node;
         if (this.innerView) {
-            const state = this.innerView.state;
+            const { state } = this.innerView;
             const start = node.content.findDiffStart(state.doc.content);
             if (start !== null) {
                 let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 52d93ec38..01c46edeb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -19,20 +19,22 @@ export function findLinkMark(marks: readonly Mark[]): Mark | undefined {
     return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor);
 }
 export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
-    let before = 0,
-        nbef = rpos.nodeBefore;
+    let before = 0;
+    let nbef = rpos.nodeBefore;
     while (nbef && finder(nbef.marks)) {
         before += nbef.nodeSize;
+        // eslint-disable-next-line no-param-reassign
         rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
         rpos && (nbef = rpos.nodeBefore);
     }
     return before;
 }
 export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
-    let after = 0,
-        naft = rpos.nodeAfter;
+    let after = 0;
+    let naft = rpos.nodeAfter;
     while (naft && finder(naft.marks)) {
         after += naft.nodeSize;
+        // eslint-disable-next-line no-param-reassign
         rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
         rpos && (naft = rpos.nodeAfter);
     }
@@ -80,10 +82,10 @@ export class FormattedTextBoxComment {
     }
 
     static showCommentbox(view: EditorView, nbef: number) {
-        const state = view.state;
+        const { state } = view;
         // These are in screen coordinates
-        const start = view.coordsAtPos(state.selection.from - nbef),
-            end = view.coordsAtPos(state.selection.from - nbef);
+        const start = view.coordsAtPos(state.selection.from - nbef);
+        const end = view.coordsAtPos(state.selection.from - nbef);
         // The box in which the tooltip is positioned, to use as base
         const box = (document.getElementsByClassName('mainView-container') as any)[0].getBoundingClientRect();
         // Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left)
@@ -110,14 +112,16 @@ export class FormattedTextBoxComment {
     }
 
     static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string, noPreview?: boolean) {
-        const state = view.state;
+        const { state } = view;
         // this section checks to see if the insertion point is over text entered by a different user.  If so, it sets ths comment text to indicate the user and the modification date
         if (state.selection.$from) {
             const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
             const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
             const noselection = state.selection.$from === state.selection.$to;
             let child: any = null;
-            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any /* , pos: number, parent: any */) => {
+                !child && node.marks.length && (child = node);
+            });
             const mark = child && findOtherUserMark(child.marks);
             if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
                 FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
@@ -132,7 +136,7 @@ export class FormattedTextBoxComment {
         if (state.selection.$from && hrefs?.length) {
             const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
             const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
-            //nbef &&
+            // nbef &&
             naft &&
                 LinkInfo.SetLinkInfo({
                     DocumentView: textBox.DocumentView,
diff --git a/src/client/views/nodes/formattedText/OrderedListView.tsx b/src/client/views/nodes/formattedText/OrderedListView.tsx
index c3595e59b..dbc60f7bf 100644
--- a/src/client/views/nodes/formattedText/OrderedListView.tsx
+++ b/src/client/views/nodes/formattedText/OrderedListView.tsx
@@ -1,8 +1,7 @@
 export class OrderedListView {
-
-    update(node: any) {
-        // if attr's of an ordered_list (e.g., bulletStyle) change, 
+    update() {
+        // if attr's of an ordered_list (e.g., bulletStyle) change,
         // return false forces the dom node to be recreated which is necessary for the bullet labels to update
-        return false; 
+        return false;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
index 30da91710..6c88a0d29 100644
--- a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
+++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
@@ -1,18 +1,19 @@
+/* eslint-disable import/extensions */
+import { Node, DOMOutputSpec } from 'prosemirror-model';
 import clamp from '../../../util/clamp';
 import convertToCSSPTValue from '../../../util/convertToCSSPTValue';
 import toCSSLineSpacing from '../../../util/toCSSLineSpacing';
-import { Node, DOMOutputSpec } from 'prosemirror-model';
 
-//import type { NodeSpec } from './Types';
+// import type { NodeSpec } from './Types';
 type NodeSpec = {
-    attrs?: { [key: string]: any },
-    content?: string,
-    draggable?: boolean,
-    group?: string,
-    inline?: boolean,
-    name?: string,
-    parseDOM?: Array<any>,
-    toDOM?: (node: any) => DOMOutputSpec,
+    attrs?: { [key: string]: any };
+    content?: string;
+    draggable?: boolean;
+    group?: string;
+    inline?: boolean;
+    name?: string;
+    parseDOM?: Array<any>;
+    toDOM?: (node: any) => DOMOutputSpec;
 };
 
 // This assumes that every 36pt maps to one indent level.
@@ -25,41 +26,18 @@ export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']);
 
 const ALIGN_PATTERN = /(left|right|center|justify)/;
 
-// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
-// :: NodeSpec A plain paragraph textblock. Represented in the DOM
-// as a `<p>` element.
-export const ParagraphNodeSpec: NodeSpec = {
-    attrs: {
-        align: { default: null },
-        color: { default: null },
-        id: { default: null },
-        indent: { default: null },
-        inset: { default: null },
-        lineSpacing: { default: null },
-        // TODO: Add UI to let user edit / clear padding.
-        paddingBottom: { default: null },
-        // TODO: Add UI to let user edit / clear padding.
-        paddingTop: { default: null },
-    },
-    content: 'inline*',
-    group: 'block',
-    parseDOM: [{ tag: 'p', getAttrs }],
-    toDOM,
-};
+function convertMarginLeftToIndentValue(marginLeft: string): number {
+    const ptValue = convertToCSSPTValue(marginLeft);
+    return clamp(MIN_INDENT_LEVEL, Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), MAX_INDENT_LEVEL);
+}
 
 function getAttrs(dom: HTMLElement): Object {
-    const {
-        lineHeight,
-        textAlign,
-        marginLeft,
-        paddingTop,
-        paddingBottom,
-    } = dom.style;
+    const { lineHeight, textAlign, marginLeft, paddingTop, paddingBottom } = dom.style;
 
     let align = dom.getAttribute('align') || textAlign || '';
-    align = ALIGN_PATTERN.test(align) ? align : "";
+    align = ALIGN_PATTERN.test(align) ? align : '';
 
-    let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10);
+    let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || '', 10);
 
     if (!indent && marginLeft) {
         indent = convertMarginLeftToIndentValue(marginLeft);
@@ -74,15 +52,7 @@ function getAttrs(dom: HTMLElement): Object {
 }
 
 function toDOM(node: Node): DOMOutputSpec {
-    const {
-        align,
-        indent,
-        inset,
-        lineSpacing,
-        paddingTop,
-        paddingBottom,
-        id,
-    } = node.attrs;
+    const { align, indent, inset, lineSpacing, paddingTop, paddingBottom, id } = node.attrs;
     const attrs: { [key: string]: any } | null = {};
 
     let style = '';
@@ -128,16 +98,29 @@ function toDOM(node: Node): DOMOutputSpec {
     return ['p', attrs, 0];
 }
 
+// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
+// :: NodeSpec A plain paragraph textblock. Represented in the DOM
+// as a `<p>` element.
+export const ParagraphNodeSpec: NodeSpec = {
+    attrs: {
+        align: { default: null },
+        color: { default: null },
+        id: { default: null },
+        indent: { default: null },
+        inset: { default: null },
+        lineSpacing: { default: null },
+        // TODO: Add UI to let user edit / clear padding.
+        paddingBottom: { default: null },
+        // TODO: Add UI to let user edit / clear padding.
+        paddingTop: { default: null },
+    },
+    content: 'inline*',
+    group: 'block',
+    parseDOM: [{ tag: 'p', getAttrs }],
+    toDOM,
+};
+
 export const toParagraphDOM = toDOM;
 export const getParagraphNodeAttrs = getAttrs;
 
-export function convertMarginLeftToIndentValue(marginLeft: string): number {
-    const ptValue = convertToCSSPTValue(marginLeft);
-    return clamp(
-        MIN_INDENT_LEVEL,
-        Math.floor(ptValue / INDENT_MARGIN_PT_SIZE),
-        MAX_INDENT_LEVEL
-    );
-}
-
-export default ParagraphNodeSpec;
\ No newline at end of file
+export default ParagraphNodeSpec;
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 88adab66e..c107a6724 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -14,6 +14,7 @@ import { CollectionView } from '../../collections/CollectionView';
 import { ContextMenu } from '../../ContextMenu';
 import { KeyValueBox } from '../KeyValueBox';
 import { FormattedTextBox } from './FormattedTextBox';
+// eslint-disable-next-line import/extensions
 import { wrappingInputRule } from './prosemirrorPatches';
 import { RichTextMenu } from './RichTextMenu';
 import { schema } from './schema_rts';
@@ -253,7 +254,7 @@ export class RichTextRules {
                 }
                 if (marks) {
                     const tr = state.tr.deleteRange(start, end);
-                    return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
+                    return marks ? Array.from(marks).reduce((tr2, m) => tr2.addStoredMark(m), tr) : tr;
                 }
 
                 const isValidColor = (strColor: string) => {
@@ -283,7 +284,7 @@ export class RichTextRules {
                 return marks
                     ? Array.from(marks)
                           .filter(m => m.type !== state.schema.marks.user_mark)
-                          .reduce((tr, m) => tr.removeStoredMark(m), tr)
+                          .reduce((tr2, m) => tr2.removeStoredMark(m), tr)
                     : tr;
             }),
 
@@ -309,11 +310,11 @@ export class RichTextRules {
                             teditor.dispatch(tstate.tr.setSelection(new TextSelection(tstate.doc.resolve(selection))));
                         }
                     };
-                    const getTitledDoc = (docTitle: string) => {
-                        if (!DocServer.FindDocByTitle(docTitle)) {
-                            Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true });
+                    const getTitledDoc = (title: string) => {
+                        if (!DocServer.FindDocByTitle(title)) {
+                            Docs.Create.TextDocument('', { title: title, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true });
                         }
-                        const titledDoc = DocServer.FindDocByTitle(docTitle);
+                        const titledDoc = DocServer.FindDocByTitle(title);
                         return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
                     };
                     const target = getTitledDoc(docTitle);
@@ -336,7 +337,7 @@ export class RichTextRules {
                     const assign = match[4] === ':' ? (match[4] = '') : match[4];
                     const value = match[5];
                     const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('=');
-                    const getTitledDoc = (docTitle: string) => DocServer.FindDocByTitle(docTitle);
+                    const getTitledDoc = (title: string) => DocServer.FindDocByTitle(title);
                     // if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' )
                     if (value?.includes(',') && !value.startsWith('((')) {
                         const values = value.split(',');
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 7ec296ed2..2c366b49b 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -3,6 +3,15 @@ import { Fragment, Node, Slice } from 'prosemirror-model';
 import * as ReactDOM from 'react-dom/client';
 import * as React from 'react';
 
+interface ISummaryView {}
+// currently nothing needs to be rendered for the internal view of a summary.
+// eslint-disable-next-line react/prefer-stateless-function
+export class SummaryViewInternal extends React.Component<ISummaryView> {
+    render() {
+        return null;
+    }
+}
+
 // an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
 // this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't
 // really need a react view.  However, it would be cleaner to figure out how to do this just as a react rendering
@@ -32,8 +41,8 @@ export class SummaryView {
         };
 
         const js = node.toJSON;
-        node.toJSON = function () {
-            return js.apply(this, arguments);
+        node.toJSON = function (...args: any[]) {
+            return js.apply(this, args);
         };
 
         this.root = ReactDOM.createRoot(this.dom);
@@ -54,7 +63,8 @@ export class SummaryView {
         const visited = new Set();
         for (let i: number = start + 1; i < view.state.doc.nodeSize - 1; i++) {
             let skip = false;
-            view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
+            // eslint-disable-next-line no-loop-func
+            view.state.doc.nodesBetween(start, i, (node: Node /* , pos: number, parent: Node, index: number */) => {
                 if (node.isLeaf && !visited.has(node) && !skip) {
                     if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
                         visited.add(node);
@@ -87,11 +97,3 @@ export class SummaryView {
         this.dom.className = this.className(visible);
     };
 }
-
-interface ISummaryView {}
-// currently nothing needs to be rendered for the internal view of a summary.
-export class SummaryViewInternal extends React.Component<ISummaryView> {
-    render() {
-        return <> </>;
-    }
-}
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 95eb86720..b195654ce 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
 /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
 /* eslint-disable jsx-a11y/img-redundant-alt */
 /* eslint-disable jsx-a11y/click-events-have-key-events */
@@ -56,7 +57,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     // format: array of [image source, corresponding image Doc]
     const [edits, setEdits] = useState<(string | Doc)[][]>([]);
     const [edited, setEdited] = useState(false);
-    const [brushStyle, setBrushStyle] = useState<BrushStyle>(BrushStyle.ADD);
+    const [brushStyle] = useState<BrushStyle>(BrushStyle.ADD);
     const [input, setInput] = useState('');
     const [loading, setLoading] = useState(false);
     const [canvasDims, setCanvasDims] = useState<ImageDimensions>({
@@ -102,8 +103,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         if (!ctx || !currImg.current || !canvasRef.current) return;
 
         const target = redoStack.current[redoStack.current.length - 1];
-        if (!target) {
-        } else {
+        if (target) {
             undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()];
             const img = new Image();
             img.src = target;
@@ -139,7 +139,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     };
 
     // stop brushing, push to undo stack
-    const handlePointerUp = (e: React.PointerEvent) => {
+    const handlePointerUp = () => {
         const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef);
         if (!ctx) return;
         if (!isBrushing) return;
@@ -148,11 +148,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
     // handles brushing on pointer movement
     useEffect(() => {
-        if (!isBrushing) return;
+        if (!isBrushing) return undefined;
         const canvas = canvasRef.current;
-        if (!canvas) return;
+        if (!canvas) return undefined;
         const ctx = ImageUtility.getCanvasContext(canvasRef);
-        if (!ctx) return;
+        if (!ctx) return undefined;
 
         const handlePointerMove = (e: PointerEvent) => {
             const currPoint = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale);
@@ -300,6 +300,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                     CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
 
                     // add the doc to the main freeform
+                    // eslint-disable-next-line no-use-before-define
                     await createNewImgDoc(originalImg.current, true);
                 }
             } else {
@@ -313,12 +314,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height)));
                 const imgRes = await Promise.all(
                     imgUrls.map(async url => {
+                        // eslint-disable-next-line no-use-before-define
                         const saveRes = await onSave(url);
                         return [url, saveRes as Doc];
                     })
                 );
                 setEdits(imgRes);
                 const image = new Image();
+                // eslint-disable-next-line prefer-destructuring
                 image.src = imgUrls[0];
                 ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height);
                 currImg.current = image;
@@ -351,7 +354,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
     // creates a new image document and returns its reference
     const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise<Doc | undefined> => {
-        if (!imageRootDoc) return;
+        if (!imageRootDoc) return undefined;
         const { src } = img;
         const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
         const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
@@ -375,7 +378,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             parentDoc.current = newImg;
             return newImg;
         }
-        if (!parentDoc.current) return;
+        if (!parentDoc.current) return undefined;
         const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
         const initialY = 0;
 
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index 185ba2280..d1f68ee0e 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -1,9 +1,9 @@
 import './GenerativeFillButtons.scss';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
-import { activeColor } from './generativeFillUtils/generativeFillConstants';
 import { Button, IconButton, Type } from 'browndash-components';
 import { AiOutlineInfo } from 'react-icons/ai';
+import { activeColor } from './generativeFillUtils/generativeFillConstants';
 
 interface ButtonContainerProps {
     getEdit: () => Promise<void>;
@@ -11,7 +11,7 @@ interface ButtonContainerProps {
     onReset: () => void;
 }
 
-const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
+function Buttons({ loading, getEdit, onReset }: ButtonContainerProps) {
     return (
         <div className="generativeFillBtnContainer">
             <Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} />
@@ -20,7 +20,7 @@ const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
                     text="GET EDITS"
                     type={Type.TERT}
                     color={activeColor}
-                    icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
+                    icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
                     iconPlacement="right"
                     onClick={() => {
                         if (!loading) getEdit();
@@ -36,9 +36,9 @@ const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
                     }}
                 />
             )}
-            <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size={'16px'} />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
+            <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size="16px" />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
         </div>
     );
-};
+}
 
 export default Buttons;
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
index 97e03ff20..6da8c3da0 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
@@ -1,10 +1,6 @@
 import { Point } from './generativeFillInterfaces';
 
 export class GenerativeFillMathHelpers {
-    static distanceBetween = (p1: Point, p2: Point) => {
-        return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
-    };
-    static angleBetween = (p1: Point, p2: Point) => {
-        return Math.atan2(p2.x - p1.x, p2.y - p1.y);
-    };
+    static distanceBetween = (p1: Point, p2: Point) => Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
+    static angleBetween = (p1: Point, p2: Point) => Math.atan2(p2.x - p1.x, p2.y - p1.y);
 }
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 47a14135f..24dba1778 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -17,15 +17,14 @@ export class ImageUtility {
      * @param canvas Canvas to convert
      * @returns Blob of canvas
      */
-    static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> => {
-        return new Promise(resolve => {
+    static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> =>
+        new Promise(resolve => {
             canvas.toBlob(blob => {
                 if (blob) {
                     resolve(blob);
                 }
             }, 'image/png');
         });
-    };
 
     // given a square api image, get the cropped img
     static getCroppedImg = (img: HTMLImageElement, width: number, height: number): HTMLCanvasElement | undefined => {
@@ -48,11 +47,12 @@ export class ImageUtility {
             }
             return canvas;
         }
+        return undefined;
     };
 
     // converts an image to a canvas data url
-    static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> => {
-        return new Promise<string>((resolve, reject) => {
+    static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> =>
+        new Promise<string>((resolve, reject) => {
             const img = new Image();
             img.onload = () => {
                 const canvas = this.getCroppedImg(img, width, height);
@@ -66,7 +66,6 @@ export class ImageUtility {
             };
             img.src = imageSrc;
         });
-    };
 
     // calls the openai api to get image edits
     static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
@@ -91,7 +90,7 @@ export class ImageUtility {
             console.log(data.data);
             return {
                 status: 'success',
-                urls: (data.data as { b64_json: string }[]).map(data => `data:image/png;base64,${data.b64_json}`),
+                urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`),
             };
         } catch (err) {
             console.log(err);
@@ -100,12 +99,10 @@ export class ImageUtility {
     };
 
     // mock api call
-    static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => {
-        return {
-            status: 'success',
-            urls: [mockSrc, mockSrc, mockSrc],
-        };
-    };
+    static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => ({
+        status: 'success',
+        urls: [mockSrc, mockSrc, mockSrc],
+    });
 
     // Gets the canvas rendering context of a canvas
     static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => {
@@ -150,12 +147,12 @@ export class ImageUtility {
 
     // Draws the image to the current canvas
     static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject<HTMLCanvasElement>, width: number, height: number) => {
-        const drawImg = (img: HTMLImageElement) => {
+        const drawImg = (htmlImg: HTMLImageElement) => {
             const ctx = this.getCanvasContext(canvasRef);
             if (!ctx) return;
             ctx.globalCompositeOperation = 'source-over';
             ctx.clearRect(0, 0, width, height);
-            ctx.drawImage(img, 0, 0, width, height);
+            ctx.drawImage(htmlImg, 0, 0, width, height);
         };
 
         if (img.complete) {
@@ -173,7 +170,7 @@ export class ImageUtility {
         canvas.width = canvasSize;
         canvas.height = canvasSize;
         const ctx = canvas.getContext('2d');
-        if (!ctx) return;
+        if (!ctx) return undefined;
         ctx?.clearRect(0, 0, canvasSize, canvasSize);
         ctx.drawImage(paddedCanvas, 0, 0);
 
@@ -195,7 +192,7 @@ export class ImageUtility {
     // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas)
     static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => {
         const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
-        const data = imageData.data;
+        const { data } = imageData;
         for (let i = 0; i < canvas.height; i++) {
             for (let j = 0; j < xOffset; j++) {
                 const targetIdx = 4 * (i * canvas.width + j);
@@ -224,7 +221,7 @@ export class ImageUtility {
     // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas)
     static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => {
         const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
-        const data = imageData.data;
+        const { data } = imageData;
         for (let j = 0; j < canvas.width; j++) {
             for (let i = 0; i < yOffset; i++) {
                 const targetIdx = 4 * (i * canvas.width + j);
@@ -256,7 +253,7 @@ export class ImageUtility {
         canvas.width = canvasSize;
         canvas.height = canvasSize;
         const ctx = canvas.getContext('2d');
-        if (!ctx) return;
+        if (!ctx) return undefined;
         // fix scaling
         const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
         const width = Math.floor(img.width * scale);
@@ -310,5 +307,6 @@ export class ImageUtility {
         } catch (err) {
             console.error(err);
         }
+        return undefined;
     };
 }
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
index 9e620ad11..260923a64 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
@@ -1,15 +1,11 @@
-import { Point } from "./generativeFillInterfaces";
+import { Point } from './generativeFillInterfaces';
 
 export class PointerHandler {
-  static getPointRelativeToElement = (
-    element: HTMLElement,
-    e: React.PointerEvent | PointerEvent,
-    scale: number
-  ): Point => {
-    const boundingBox = element.getBoundingClientRect();
-    return {
-      x: (e.clientX - boundingBox.x) / scale,
-      y: (e.clientY - boundingBox.y) / scale,
+    static getPointRelativeToElement = (element: HTMLElement, e: React.PointerEvent | PointerEvent, scale: number): Point => {
+        const boundingBox = element.getBoundingClientRect();
+        return {
+            x: (e.clientX - boundingBox.x) / scale,
+            y: (e.clientY - boundingBox.y) / scale,
+        };
     };
-  };
 }
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index dec9a5019..317719032 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -22,13 +22,14 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return (
             <div style={{ backgroundColor: 'pink' }}>
                 <DocumentView
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props} //
                     LayoutTemplateString={undefined}
                     Document={this.Document}
                     isContentActive={returnFalse}
                     addDocument={returnFalse}
                     ScreenToLocalTransform={this.screenToLocalXf}
-                    hideResizeHandles={true}
+                    hideResizeHandles
                 />
             </div>
         );
diff --git a/src/client/views/nodes/trails/index.ts b/src/client/views/nodes/trails/index.ts
index 8f3f7b03a..7b18974df 100644
--- a/src/client/views/nodes/trails/index.ts
+++ b/src/client/views/nodes/trails/index.ts
@@ -1,3 +1,3 @@
-export * from "./PresBox";
-export * from "./PresElementBox";
-export * from "./PresEnums";
\ No newline at end of file
+export * from './PresBox';
+export * from './PresElementBox';
+export * from './PresEnums';
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 02ff661e5..7b959533f 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -95,29 +95,29 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         setupMoveUpEvents(
             this,
             e,
-            (e: PointerEvent) => {
-                this.StartDrag(e, this._commentRef.current!);
+            (moveEv: PointerEvent) => {
+                this.StartDrag(moveEv, this._commentRef.current!);
                 return true;
             },
             returnFalse,
-            e => this.OnClick?.(e)
+            clickEv => this.OnClick?.(clickEv)
         );
     };
 
     audioDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnAudio?.(e));
+        setupMoveUpEvents(this, e, returnFalse, returnFalse, clickEv => this.OnAudio?.(clickEv));
     };
 
     cropDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(
             this,
             e,
-            (e: PointerEvent) => {
-                this.StartCropDrag(e, this._cropRef.current!);
+            (moveEv: PointerEvent) => {
+                this.StartCropDrag(moveEv, this._cropRef.current!);
                 return true;
             },
             returnFalse,
-            e => this.OnCrop?.(e)
+            clickev => this.OnCrop?.(clickev)
         );
     };
 
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 4fff2a8e7..7a1e35636 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -30,7 +30,6 @@ export class Uploader extends React.Component<ImageUploadProps> {
     @observable nm: string = 'Choose files'; // Text of 'Choose Files' button
     @observable process: string = ''; // Current status of upload
     @observable private dialogueBoxOpacity = 1;
-    @observable private overlayOpacity = 0.4;
 
     onClick = async () => {
         try {
@@ -167,6 +166,6 @@ export class Uploader extends React.Component<ImageUploadProps> {
     };
 
     render() {
-        return <MainViewModal contents={this.uploadInterface} isDisplayed interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.closeUpload} />;
+        return <MainViewModal contents={this.uploadInterface} isDisplayed interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.closeUpload} />;
     }
 }
-- 
cgit v1.2.3-70-g09d2

