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

---
 src/client/views/collections/CollectionMenu.tsx | 38 +++++++++++++++----------
 1 file changed, 23 insertions(+), 15 deletions(-)

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

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


From 939e18624af4252551f38c43335ee8ef0acd144c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 21 Apr 2024 19:03:49 -0400
Subject: more lint cleanup

---
 .eslintrc.json                                     |   1 +
 package-lock.json                                  |   3 +-
 package.json                                       |   2 +-
 .../apis/google_docs/GooglePhotosClientUtils.ts    |  82 ++-
 src/client/util/BranchingTrailManager.tsx          |  41 +-
 src/client/util/CalendarManager.tsx                |  39 +-
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/util/DragManager.ts                     |  18 +-
 src/client/util/GroupManager.tsx                   |  85 ++-
 src/client/util/HypothesisUtils.ts                 |   2 -
 src/client/util/Import & Export/ImageUtils.ts      |   2 +-
 src/client/util/LinkFollower.ts                    |   7 +-
 src/client/util/LinkManager.ts                     |  28 +-
 src/client/util/ScriptingGlobals.ts                |   1 +
 src/client/util/SearchUtil.ts                      |  20 +-
 src/client/util/SettingsManager.tsx                | 323 +++++----
 src/client/util/SharingManager.tsx                 | 753 +++++++++++----------
 src/client/util/SnappingManager.ts                 |   3 +
 src/client/util/UndoManager.ts                     |  90 +--
 src/client/util/reportManager/ReportManager.tsx    |  38 +-
 src/client/views/ContextMenuItem.tsx               |  15 +-
 src/client/views/DashboardView.tsx                 |  41 +-
 src/client/views/DictationOverlay.tsx              |  55 +-
 src/client/views/DocComponent.tsx                  |   5 +-
 src/client/views/DocumentButtonBar.tsx             | 114 ++--
 src/client/views/EditableView.tsx                  |  23 +-
 src/client/views/FieldsDropdown.tsx                |  16 +-
 src/client/views/FilterPanel.tsx                   |   1 +
 src/client/views/GlobalKeyHandler.ts               |  58 +-
 src/client/views/InkingStroke.tsx                  | 109 +--
 src/client/views/MainView.tsx                      |  42 +-
 src/client/views/MetadataEntryMenu.tsx             | 196 ------
 src/client/views/OverlayView.tsx                   |   7 +-
 src/client/views/PropertiesButtons.tsx             | 182 +++--
 src/client/views/PropertiesView.tsx                |  44 +-
 src/client/views/SidebarAnnos.tsx                  |  13 +-
 src/client/views/StyleProvider.tsx                 |  75 +-
 src/client/views/UndoStack.tsx                     |  19 +-
 src/client/views/animationtimeline/Timeline.tsx    |   1 +
 src/client/views/animationtimeline/Track.tsx       |   2 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |  96 +--
 src/client/views/collections/CollectionMenu.tsx    |  19 +-
 .../views/collections/CollectionNoteTakingView.tsx |  85 ++-
 .../views/collections/CollectionPileView.tsx       |  13 +-
 .../collections/CollectionStackedTimeline.tsx      | 152 +++--
 src/client/views/collections/CollectionSubView.tsx |  31 +-
 .../views/collections/CollectionTimeView.tsx       |  32 +-
 .../views/collections/CollectionTreeView.tsx       |  79 ++-
 src/client/views/collections/TabDocView.tsx        | 347 +++++-----
 src/client/views/collections/TreeView.tsx          |  46 +-
 .../CollectionFreeFormInfoUI.tsx                   |  33 +-
 .../CollectionFreeFormLayoutEngines.tsx            |  38 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 295 +++++---
 .../collections/collectionFreeForm/MarqueeView.tsx |  10 +-
 .../collectionSchema/SchemaTableCell.tsx           |  33 +-
 src/client/views/global/globalScripts.ts           |  13 +-
 src/client/views/linking/LinkMenuItem.tsx          |  32 +-
 src/client/views/linking/LinkPopup.tsx             |  21 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  44 +-
 src/client/views/nodes/ComparisonBox.tsx           |  17 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 115 ++--
 .../nodes/DataVizBox/components/Histogram.tsx      | 230 +++----
 .../nodes/DataVizBox/components/LineChart.tsx      | 107 ++-
 .../views/nodes/DataVizBox/components/TableBox.tsx |  80 ++-
 src/client/views/nodes/DocumentContentsView.tsx    |  33 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |  34 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/FieldView.tsx               |  10 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  60 +-
 src/client/views/nodes/ImageBox.tsx                |   7 +-
 src/client/views/nodes/KeyValuePair.tsx            |  13 +-
 src/client/views/nodes/LabelBox.tsx                |  25 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |   6 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |  25 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |  31 +-
 src/client/views/nodes/PDFBox.tsx                  | 100 ++-
 .../views/nodes/RecordingBox/RecordingBox.tsx      |  37 +-
 src/client/views/nodes/ScreenshotBox.tsx           |  29 +-
 src/client/views/nodes/VideoBox.tsx                | 103 ++-
 src/client/views/nodes/WebBox.tsx                  | 153 +++--
 .../views/nodes/formattedText/DashFieldView.tsx    | 310 +++++----
 .../nodes/formattedText/FormattedTextBox.scss      |   3 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    |  51 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     | 226 +++----
 .../views/nodes/formattedText/RichTextRules.ts     |   2 +-
 src/client/views/nodes/formattedText/marks_rts.ts  |  30 +-
 src/client/views/nodes/formattedText/nodes_rts.ts  |  14 +-
 src/client/views/nodes/trails/PresBox.tsx          | 635 ++++++++++-------
 src/client/views/nodes/trails/PresElementBox.tsx   |  96 +--
 src/client/views/pdf/Annotation.tsx                |   4 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |   3 +-
 src/client/views/search/SearchBox.tsx              |  68 +-
 src/client/views/topbar/TopBar.tsx                 |  25 +-
 src/fields/Doc.ts                                  |  56 +-
 src/fields/List.ts                                 |  34 +-
 src/fields/ObjectField.ts                          |   1 +
 src/fields/Proxy.ts                                |  17 +-
 src/fields/Schema.ts                               |   2 +-
 src/fields/Types.ts                                |  15 +-
 src/fields/documentSchemas.ts                      |   3 +-
 src/mobile/ImageUpload.tsx                         |  77 +--
 src/mobile/MobileInterface.tsx                     |   2 +-
 src/server/ApiManagers/UploadManager.ts            |   4 +-
 104 files changed, 3740 insertions(+), 3134 deletions(-)
 delete mode 100644 src/client/views/MetadataEntryMenu.tsx

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

diff --git a/.eslintrc.json b/.eslintrc.json
index 0c4e375a9..780626412 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -53,6 +53,7 @@
         "react/destructuring-assignment": 0,
         "no-restricted-globals": ["error", "event"],
         "no-param-reassign": ["error", { "props": false }],
+        "import/no-cycle": 0,
         "no-alert": 0,
         "radix": "off"
     },
diff --git a/package-lock.json b/package-lock.json
index cc1a0ea64..c616bb6ea 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -183,6 +183,7 @@
                 "react-measure": "^2.5.2",
                 "react-resizable": "^3.0.5",
                 "react-select": "^5.8.0",
+                "react-type-animation": "^3.2.0",
                 "react-xarrows": "^2.0.2",
                 "readline": "^1.3.0",
                 "recharts": "^2.10.3",
@@ -287,7 +288,6 @@
                 "jsdom": "^24.0.0",
                 "mocha": "^10.2.0",
                 "prettier": "^3.1.0",
-                "react-type-animation": "^3.2.0",
                 "scss-loader": "0.0.1",
                 "style-loader": "^4.0.0",
                 "ts-loader": "^9.5.1",
@@ -30110,7 +30110,6 @@
             "version": "3.2.0",
             "resolved": "https://registry.npmjs.org/react-type-animation/-/react-type-animation-3.2.0.tgz",
             "integrity": "sha512-WXTe0i3rRNKjmggPvT5ntye1QBt0ATGbijeW6V3cQe2W0jaMABXXlPPEdtofnS9tM7wSRHchEvI9SUw+0kUohw==",
-            "dev": true,
             "peerDependencies": {
                 "prop-types": "^15.5.4",
                 "react": ">= 15.0.0",
diff --git a/package.json b/package.json
index 3f2f5a70f..a3ba6bdd9 100644
--- a/package.json
+++ b/package.json
@@ -83,7 +83,6 @@
         "jsdom": "^24.0.0",
         "mocha": "^10.2.0",
         "prettier": "^3.1.0",
-        "react-type-animation": "^3.2.0",
         "scss-loader": "0.0.1",
         "style-loader": "^4.0.0",
         "ts-loader": "^9.5.1",
@@ -267,6 +266,7 @@
         "react-measure": "^2.5.2",
         "react-resizable": "^3.0.5",
         "react-select": "^5.8.0",
+        "react-type-animation": "^3.2.0",
         "react-xarrows": "^2.0.2",
         "readline": "^1.3.0",
         "recharts": "^2.10.3",
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 757031fec..07a2708ec 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -1,3 +1,5 @@
+/* eslint-disable no-use-before-define */
+import Photos = require('googlephotos');
 import { AssertionError } from 'assert';
 import { EditorState } from 'prosemirror-state';
 import { ClientUtils } from '../../../ClientUtils';
@@ -5,14 +7,12 @@ import { Doc, DocListCastAsync, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { RichTextField } from '../../../fields/RichTextField';
 import { RichTextUtils } from '../../../fields/RichTextUtils';
-import { Cast, StrCast } from '../../../fields/Types';
-import { ImageField } from '../../../fields/URLField';
+import { Cast, ImageCast, StrCast } from '../../../fields/Types';
 import { MediaItem, NewMediaItemResult } from '../../../server/apis/google/SharedTypes';
 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');
 
 export namespace GooglePhotos {
     const endpoint = async () => new Photos(await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken());
@@ -76,17 +76,16 @@ export namespace GooglePhotos {
         export const CollectionToAlbum = async (options: AlbumCreationOptions): Promise<Opt<AlbumCreationResult>> => {
             const { collection, title, descriptionKey, tag } = options;
             const dataDocument = Doc.GetProto(collection);
-            const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => Cast(doc.data, ImageField));
+            const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => ImageCast(doc.data));
             if (!images || !images.length) {
                 return undefined;
             }
-            const resolved = title ? title : StrCast(collection.title) || `Dash Collection (${collection[Id]}`;
+            const resolved = title || StrCast(collection.title) || `Dash Collection (${collection[Id]}`;
             const { id, productUrl } = await Create.Album(resolved);
             const response = await Transactions.UploadImages(images, { id }, descriptionKey);
             if (response) {
                 const { results, failed } = response;
-                let index: Opt<number>;
-                while ((index = failed.pop()) !== undefined) {
+                for (let index = failed.pop(); index !== undefined; index = failed.pop()) {
                     Doc.RemoveDocFromList(dataDocument, 'data', images.splice(index, 1)[0]);
                 }
                 const mediaItems: MediaItem[] = results.map(item => item.mediaItem);
@@ -97,13 +96,12 @@ export namespace GooglePhotos {
                 for (let i = 0; i < images.length; i++) {
                     const image = Doc.GetProto(images[i]);
                     const mediaItem = mediaItems[i];
-                    if (!mediaItem) {
-                        continue;
+                    if (mediaItem) {
+                        image.googlePhotosId = mediaItem.id;
+                        image.googlePhotosAlbumUrl = productUrl;
+                        image.googlePhotosUrl = mediaItem.productUrl || mediaItem.baseUrl;
+                        idMapping[mediaItem.id] = image;
                     }
-                    image.googlePhotosId = mediaItem.id;
-                    image.googlePhotosAlbumUrl = productUrl;
-                    image.googlePhotosUrl = mediaItem.productUrl || mediaItem.baseUrl;
-                    idMapping[mediaItem.id] = image;
                 }
                 collection.googlePhotosAlbumUrl = productUrl;
                 collection.googlePhotosIdMapping = idMapping;
@@ -114,6 +112,7 @@ export namespace GooglePhotos {
                 Transactions.AddTextEnrichment(collection, `Find me at ${ClientUtils.prepend(`/doc/${collection[Id]}?sharing=true`)}`);
                 return { albumId: id, mediaItems };
             }
+            return undefined;
         };
     }
 
@@ -124,7 +123,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(ClientUtils.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);
         };
@@ -144,7 +143,7 @@ export namespace GooglePhotos {
             const images = (await DocListCastAsync(collection.data))!.map(Doc.GetProto);
             images?.forEach(image => tagMapping.set(image[Id], ContentCategories.NONE));
             const values = Object.values(ContentCategories).filter(value => value !== ContentCategories.NONE);
-            for (const value of values) {
+            values.forEach(async value => {
                 const searched = (await ContentSearch({ included: [value] }))?.mediaItems?.map(({ id }) => id);
                 searched?.forEach(async id => {
                     const image = await Cast(idMapping[id], Doc);
@@ -154,7 +153,7 @@ export namespace GooglePhotos {
                         !tags?.includes(value) && tagMapping.set(key, tags + delimiter + value);
                     }
                 });
-            }
+            });
             images?.forEach(image => {
                 const concatenated = tagMapping.get(image[Id])!;
                 const tags = concatenated.split(delimiter);
@@ -200,9 +199,10 @@ export namespace GooglePhotos {
         export const AlbumSearch = async (albumId: string, pageSize = 100): Promise<MediaItem[]> => {
             const photos = await endpoint();
             const mediaItems: MediaItem[] = [];
-            let nextPageTokenStored: Opt<string> = undefined;
+            let nextPageTokenStored: Opt<string>;
             const found = 0;
             do {
+                // eslint-disable-next-line no-await-in-loop
                 const response: any = await photos.mediaItems.search(albumId, pageSize, nextPageTokenStored);
                 mediaItems.push(...response.mediaItems);
                 nextPageTokenStored = response.nextPageToken;
@@ -222,7 +222,7 @@ export namespace GooglePhotos {
             excluded.length && excluded.forEach(category => contentFilter.addExcludedContentCategories(category));
             filters.setContentFilter(contentFilter);
 
-            const date = options.date;
+            const { date } = options;
             if (date) {
                 const dateFilter = new photos.DateFilter();
                 if (date instanceof Date) {
@@ -240,15 +240,11 @@ export namespace GooglePhotos {
             });
         };
 
-        export const GetImage = async (mediaItemId: string): Promise<Transactions.MediaItem> => {
-            return (await endpoint()).mediaItems.get(mediaItemId);
-        };
+        export const GetImage = async (mediaItemId: string): Promise<Transactions.MediaItem> => (await endpoint()).mediaItems.get(mediaItemId);
     }
 
     namespace Create {
-        export const Album = async (title: string) => {
-            return (await endpoint()).albums.create(title);
-        };
+        export const Album = async (title: string) => (await endpoint()).albums.create(title);
     }
 
     export namespace Transactions {
@@ -278,6 +274,7 @@ export namespace GooglePhotos {
                     return enrichmentItem.id;
                 }
             }
+            return undefined;
         };
 
         export const WriteMediaItemsToServer = async (body: { mediaItems: any[] }): Promise<UploadInformation[]> => {
@@ -291,9 +288,12 @@ export namespace GooglePhotos {
                 return undefined;
             }
             const baseUrls: string[] = await Promise.all(
-                response.results.map(item => {
-                    return new Promise<string>(resolve => Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl)));
-                })
+                response.results.map(
+                    item =>
+                        new Promise<string>(resolve => {
+                            Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl));
+                        })
+                )
             );
             return baseUrls;
         };
@@ -303,27 +303,25 @@ export namespace GooglePhotos {
             failed: number[];
         }
 
-        export const UploadImages = async (sources: Doc[], album?: AlbumReference, descriptionKey = 'caption'): Promise<Opt<ImageUploadResults>> => {
+        export const UploadImages = async (sources: Doc[], albumIn?: AlbumReference, descriptionKey = 'caption'): Promise<Opt<ImageUploadResults>> => {
             await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
-            if (album && 'title' in album) {
-                album = await Create.Album(album.title);
-            }
+            const album = albumIn && 'title' in albumIn ? await Create.Album(albumIn.title) : albumIn;
             const media: MediaInput[] = [];
-            for (const source of sources) {
-                const data = Cast(Doc.GetProto(source).data, ImageField);
-                if (!data) {
-                    return;
-                }
-                const url = data.url.href;
-                const target = Doc.MakeEmbedding(source);
-                const description = parseDescription(target, descriptionKey);
-                await DocUtils.makeCustomViewClicked(target, Docs.Create.FreeformDocument);
-                media.push({ url, description });
-            }
+            sources
+                .filter(source => ImageCast(Doc.GetProto(source).data))
+                .forEach(async source => {
+                    const data = ImageCast(Doc.GetProto(source).data);
+                    const url = data.url.href;
+                    const target = Doc.MakeEmbedding(source);
+                    const description = parseDescription(target, descriptionKey);
+                    await DocUtils.makeCustomViewClicked(target, Docs.Create.FreeformDocument);
+                    media.push({ url, description });
+                });
             if (media.length) {
                 const results = await Networking.PostToServer('/googlePhotosMediaPost', { media, album });
                 return results;
             }
+            return undefined;
         };
 
         const parseDescription = (document: Doc, descriptionKey: string) => {
diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx
index 02879e3c4..28c00644f 100644
--- a/src/client/util/BranchingTrailManager.tsx
+++ b/src/client/util/BranchingTrailManager.tsx
@@ -1,18 +1,31 @@
+/* eslint-disable react/no-unused-class-component-methods */
+/* eslint-disable react/no-array-index-key */
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
-import { PresBox } from '../views/nodes/trails';
 import { OverlayView } from '../views/OverlayView';
+import { PresBox } from '../views/nodes/trails';
 import { DocumentManager } from './DocumentManager';
-import { Docs } from '../documents/Documents';
-import { nullAudio } from '../../fields/URLField';
 
 @observer
 export class BranchingTrailManager extends React.Component {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: BranchingTrailManager;
 
+    // stack of the history
+    @observable private slideHistoryStack: String[] = [];
+    @observable private containsSet: Set<String> = new Set<String>();
+    // docId to Doc map
+    @observable private docIdToDocMap: Map<String, Doc> = new Map<String, Doc>();
+
+    // prev pres to copmare with
+    @observable private prevPresId: String | null = null;
+    @action setPrevPres = action((newId: String | null) => {
+        this.prevPresId = newId;
+    });
+
     constructor(props: any) {
         super(props);
         makeObservable(this);
@@ -22,7 +35,7 @@ export class BranchingTrailManager extends React.Component {
     }
 
     setupUi = () => {
-        OverlayView.Instance.addWindow(<BranchingTrailManager></BranchingTrailManager>, { x: 100, y: 150, width: 1000, title: 'Branching Trail' });
+        OverlayView.Instance.addWindow(<BranchingTrailManager />, { x: 100, y: 150, width: 1000, title: 'Branching Trail' });
         // OverlayView.Instance.forceUpdate();
         console.log(OverlayView.Instance);
         // let hi = Docs.Create.TextDocument("beee", {
@@ -36,23 +49,11 @@ export class BranchingTrailManager extends React.Component {
         console.log(DocumentManager._overlayViews);
     };
 
-    // stack of the history
-    @observable private slideHistoryStack: String[] = [];
     @action setSlideHistoryStack = action((newArr: String[]) => {
         this.slideHistoryStack = newArr;
     });
 
-    @observable private containsSet: Set<String> = new Set<String>();
-
-    // prev pres to copmare with
-    @observable private prevPresId: String | null = null;
-    @action setPrevPres = action((newId: String | null) => {
-        this.prevPresId = newId;
-    });
-
-    // docId to Doc map
-    @observable private docIdToDocMap: Map<String, Doc> = new Map<String, Doc>();
-
+    // eslint-disable-next-line react/sort-comp
     observeDocumentChange = (targetDoc: Doc, pres: PresBox) => {
         const presId = pres.Document[Id];
         if (this.prevPresId === presId) {
@@ -106,7 +107,7 @@ export class BranchingTrailManager extends React.Component {
         if (this.slideHistoryStack.length === 0) {
             Doc.UserDoc().isBranchingMode = false;
         }
-        //PresBox.NavigateToTarget(targetDoc, targetDoc);
+        // PresBox.NavigateToTarget(targetDoc, targetDoc);
     };
 
     @computed get trailBreadcrumbs() {
@@ -116,11 +117,11 @@ export class BranchingTrailManager extends React.Component {
                     const [presId, targetDocId] = info.split(',');
                     const doc = this.docIdToDocMap.get(targetDocId);
                     if (!doc) {
-                        return <></>;
+                        return null;
                     }
                     return (
                         <span key={targetDocId}>
-                            <button key={index} onPointerDown={e => this.clickHandler(e, targetDocId, index)}>
+                            <button type="button" key={index} onPointerDown={e => this.clickHandler(e, targetDocId, index)}>
                                 {presId.slice(0, 3) + ':' + doc.title}
                             </button>
                             -{'>'}
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 6e9094b3a..46aa4d238 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -1,4 +1,10 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum';
+import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { TextField } from '@mui/material';
+import { Button } from 'browndash-components';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -6,21 +12,16 @@ import Select from 'react-select';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { DocData } from '../../fields/DocSymbols';
 import { StrCast } from '../../fields/Types';
+import { Docs } from '../documents/Documents';
 import { DictationOverlay } from '../views/DictationOverlay';
 import { MainViewModal } from '../views/MainViewModal';
+import { ObservableReactComponent } from '../views/ObservableReactComponent';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
 import './CalendarManager.scss';
 import { DocumentManager } from './DocumentManager';
 import { SelectionManager } from './SelectionManager';
 import { SettingsManager } from './SettingsManager';
-// import { DateRange, Range, RangeKeyDict } from 'react-date-range';
-import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum';
-import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button } from 'browndash-components';
-import { Docs } from '../documents/Documents';
-import { ObservableReactComponent } from '../views/ObservableReactComponent';
 // import 'react-date-range/dist/styles.css';
 // import 'react-date-range/dist/theme/default.css';
 
@@ -47,6 +48,7 @@ const formatCalendarDateToString = (calendarDate: any) => {
 
 @observer
 export class CalendarManager extends ObservableReactComponent<{}> {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: CalendarManager;
     @observable private isOpen = false;
     @observable private targetDoc: Doc | undefined = undefined; // the target document
@@ -83,10 +85,10 @@ export class CalendarManager extends ObservableReactComponent<{}> {
         this.creationType = type;
     };
 
-    public open = (target?: DocumentView, target_doc?: Doc) => {
+    public open = (target?: DocumentView, targetDoc?: Doc) => {
         console.log('hi');
         runInAction(() => {
-            this.targetDoc = target_doc || target?.Document;
+            this.targetDoc = targetDoc || target?.Document;
             this.targetDocView = target;
             DictationOverlay.Instance.hasActiveModal = true;
             this.isOpen = this.targetDoc !== undefined;
@@ -117,7 +119,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
     @action
     handleSelectChange = (option: any) => {
         if (option) {
-            let selectOpt = option as CalendarSelectOptions;
+            const selectOpt = option as CalendarSelectOptions;
             this.selectedExistingCalendarOption = selectOpt;
             this.calendarName = selectOpt.value; // or label
         }
@@ -136,7 +138,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
 
     // TODO: Make undoable
     private addToCalendar = () => {
-        let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+        const docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
         const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // doc to add to calendar
 
         console.log(targetDoc);
@@ -159,7 +161,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
                 }
             } else {
                 // find existing calendar based on selected name (should technically always find one)
-                const existingCalendar = this.existingCalendars.find(calendar => StrCast(calendar.title) === this.calendarName);
+                const existingCalendar = this.existingCalendars.find(findCal => StrCast(findCal.title) === this.calendarName);
                 if (existingCalendar) calendar = existingCalendar;
                 else {
                     this.errorMessage = 'Must select an existing calendar';
@@ -252,11 +254,9 @@ export class CalendarManager extends ObservableReactComponent<{}> {
 
     @computed
     get calendarInterface() {
-        let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+        const docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
         const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
 
-        const currentDate = new Date();
-
         return (
             <div
                 className="calendar-interface"
@@ -268,10 +268,10 @@ export class CalendarManager extends ObservableReactComponent<{}> {
                     <b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}</b>
                 </p>
                 <div className="creation-type-container">
-                    <div className={`calendar-creation ${this.creationType === 'new-calendar' ? 'calendar-creation-selected' : ''}`} onClick={e => this.setInterationType('new-calendar')}>
+                    <div className={`calendar-creation ${this.creationType === 'new-calendar' ? 'calendar-creation-selected' : ''}`} onClick={() => this.setInterationType('new-calendar')}>
                         Add to New Calendar
                     </div>
-                    <div className={`calendar-creation ${this.creationType === 'existing-calendar' ? 'calendar-creation-selected' : ''}`} onClick={e => this.setInterationType('existing-calendar')}>
+                    <div className={`calendar-creation ${this.creationType === 'existing-calendar' ? 'calendar-creation-selected' : ''}`} onClick={() => this.setInterationType('existing-calendar')}>
                         Add to Existing calendar
                     </div>
                 </div>
@@ -317,7 +317,8 @@ export class CalendarManager extends ObservableReactComponent<{}> {
                                     color: StrCast(Doc.UserDoc().userColor),
                                     width: '100%',
                                 }),
-                            }}></Select>
+                            }}
+                        />
                     )}
                 </div>
                 <div className="description-container">
@@ -351,6 +352,6 @@ export class CalendarManager extends ObservableReactComponent<{}> {
     }
 
     render() {
-        return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+        return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
     }
 }
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 27ae5c9a0..6dba8027d 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -709,7 +709,7 @@ pie title Minerals in my tap water
         return [
             { title: "Back",    toolTip: "Go back",         btnType: ButtonType.ClickButton,  icon: "arrow-left",              scripts: { onClick: '{ return webBack(); }' }},
             { title: "Forward", toolTip: "Go forward",      btnType: ButtonType.ClickButton,  icon: "arrow-right",             scripts: { onClick: '{ return webForward(); }'}},
-            { title: "URL",     toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script:  '{ return webSetURL(value, _readOnly_); }'} },
+            { title: "URL",     toolTip: "URL", width: 250, btnType: ButtonType.EditText,     icon: "lock", ignoreClick: true, scripts: { script:  '{ return webSetURL(value, _readOnly_); }'} },
         ];
     }
     static videoTools() {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 62f055f1a..3890b7845 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -495,18 +495,20 @@ 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('*'))]
+                .map(dele => (dele as any).style)
+                .forEach(style => {
+                    style && (style.pointerEvents = 'none');
+                });
 
             dragDiv.appendChild(dragElement);
             if (dragElement !== ele) {
-                const children = [Array.from(ele.children), Array.from(dragElement.children)];
-                while (children[0].length) {
-                    const childs = [children[0].pop(), children[1].pop()];
+                const dragChildren = [Array.from(ele.children), Array.from(dragElement.children)];
+                while (dragChildren[0].length) {
+                    const childs = [dragChildren[0].pop(), dragChildren[1].pop()];
                     if (childs[0]?.children) {
-                        children[0].push(...Array.from(childs[0].children));
-                        children[1].push(...Array.from(childs[1]!.children));
+                        dragChildren[0].push(...Array.from(childs[0].children));
+                        dragChildren[1].push(...Array.from(childs[1]!.children));
                     }
                     if (childs[0]?.scrollTop) childs[1]!.scrollTop = childs[0].scrollTop;
                 }
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index c261c0f1e..8d84dbad8 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.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 { Button, IconButton, Size, Type } from 'browndash-components';
 import { action, computed, makeObservable, observable } from 'mobx';
@@ -30,6 +32,7 @@ export interface UserOptions {
 
 @observer
 export class GroupManager extends ObservableReactComponent<{}> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: GroupManager;
     @observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not.
     @observable private users: string[] = []; // list of users populated from the database.
@@ -160,7 +163,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
     addGroup(groupDoc: Doc): boolean {
         if (this.GroupManagerDoc) {
             Doc.AddDocToList(this.GroupManagerDoc, 'data', groupDoc);
-            this.GroupManagerDoc['data_modificationDate'] = new DateField();
+            this.GroupManagerDoc.data_modificationDate = new DateField();
             return true;
         }
         return false;
@@ -202,7 +205,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
             !memberList.includes(email) && memberList.push(email);
             groupDoc.members = JSON.stringify(memberList);
             SharingManager.Instance.shareWithAddedMember(groupDoc, email);
-            this.GroupManagerDoc && (this.GroupManagerDoc['data_modificationDate'] = new DateField());
+            this.GroupManagerDoc && (this.GroupManagerDoc.data_modificationDate = new DateField());
         }
     }
 
@@ -216,10 +219,9 @@ export class GroupManager extends ObservableReactComponent<{}> {
             const memberList = JSON.parse(StrCast(groupDoc.members));
             const index = memberList.indexOf(email);
             if (index !== -1) {
-                const user = memberList.splice(index, 1)[0];
                 groupDoc.members = JSON.stringify(memberList);
                 SharingManager.Instance.removeMember(groupDoc, email);
-                this.GroupManagerDoc && (this.GroupManagerDoc['data_modificationDate'] = new DateField());
+                this.GroupManagerDoc && (this.GroupManagerDoc.data_modificationDate = new DateField());
             }
         }
     }
@@ -275,7 +277,9 @@ export class GroupManager extends ObservableReactComponent<{}> {
         TaskCompletionBox.textDisplayed = 'Group created!';
         TaskCompletionBox.taskCompleted = true;
         setTimeout(
-            action(() => (TaskCompletionBox.taskCompleted = false)),
+            action(() => {
+                TaskCompletionBox.taskCompleted = false;
+            }),
             2000
         );
     };
@@ -292,7 +296,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
                     </p>
                     <div className="close-button">
                         <Button
-                            icon={<FontAwesomeIcon icon={'times'} size={'lg'} />}
+                            icon={<FontAwesomeIcon icon="times" size="lg" />}
                             onClick={action(() => {
                                 this.createGroupModalOpen = false;
                                 TaskCompletionBox.taskCompleted = false;
@@ -302,7 +306,17 @@ export class GroupManager extends ObservableReactComponent<{}> {
                     </div>
                 </div>
                 <div className="group-input" style={{ border: StrCast(Doc.UserDoc().userColor) }}>
-                    <input ref={this.inputRef} onKeyDown={this.handleKeyDown} autoFocus type="text" placeholder="Group name" onChange={action(() => (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} />
+                    <input
+                        ref={this.inputRef}
+                        onKeyDown={this.handleKeyDown}
+                        // eslint-disable-next-line jsx-a11y/no-autofocus
+                        autoFocus
+                        type="text"
+                        placeholder="Group name"
+                        onChange={action(() => {
+                            this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797';
+                        })}
+                    />
                 </div>
                 <div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
                     <Select
@@ -310,7 +324,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
                         isMulti
                         options={this.options}
                         onChange={this.handleChange}
-                        placeholder={'Select users'}
+                        placeholder="Select users"
                         value={this.selectedUsers}
                         closeMenuOnSelect={false}
                         styles={{
@@ -335,8 +349,8 @@ export class GroupManager extends ObservableReactComponent<{}> {
                         }}
                     />
                 </div>
-                <div className={'create-button'}>
-                    <Button text={'Create'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} />
+                <div className="create-button">
+                    <Button text="Create" type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} />
                 </div>
             </div>
         );
@@ -344,7 +358,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
         return (
             <MainViewModal
                 isDisplayed={this.createGroupModalOpen}
-                interactive={true}
+                interactive
                 contents={contents}
                 dialogueBoxStyle={{ width: '90%', height: '70%' }}
                 closeOnExternalClick={action(() => {
@@ -372,28 +386,59 @@ export class GroupManager extends ObservableReactComponent<{}> {
         return (
             <div className="group-interface" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
                 {this.groupCreationModal}
-                {this.currentGroup ? <GroupMemberView group={this.currentGroup} onCloseButtonClick={action(() => (this.currentGroup = undefined))} /> : null}
+                {this.currentGroup ? (
+                    <GroupMemberView
+                        group={this.currentGroup}
+                        onCloseButtonClick={action(() => {
+                            this.currentGroup = undefined;
+                        })}
+                    />
+                ) : null}
                 <div className="group-heading">
                     <p>
                         <b>Manage Groups</b>
                     </p>
-                    <Button icon={<FontAwesomeIcon icon={'plus'} />} iconPlacement={'left'} text={'Create Group'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.createGroupModalOpen = true))} />
-                    <div className={'close-button'}>
-                        <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={StrCast(Doc.UserDoc().userColor)} />
+                    <Button
+                        icon={<FontAwesomeIcon icon="plus" />}
+                        iconPlacement="left"
+                        text="Create Group"
+                        type={Type.TERT}
+                        color={StrCast(Doc.UserDoc().userColor)}
+                        onClick={action(() => {
+                            this.createGroupModalOpen = true;
+                        })}
+                    />
+                    <div className="close-button">
+                        <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.close} color={StrCast(Doc.UserDoc().userColor)} />
                     </div>
                 </div>
                 <div className="main-container">
-                    <div className="sort-groups" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
+                    <div
+                        className="sort-groups"
+                        onClick={action(() => {
+                            this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending';
+                        })}>
                         Name
                         <IconButton icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} />
                     </div>
-                    <div className={'style-divider'} style={{ background: StrCast(Doc.UserDoc().userColor) }} />
+                    <div className="style-divider" style={{ background: StrCast(Doc.UserDoc().userColor) }} />
                     <div className="group-body" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}>
                         {groups.map(group => (
                             <div className="group-row" key={StrCast(group.title || group.groupName)}>
                                 <div className="group-name">{StrCast(group.title || group.groupName)}</div>
-                                <div className="group-info" onClick={action(() => (this.currentGroup = group))}>
-                                    <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.currentGroup = group))} />
+                                <div
+                                    className="group-info"
+                                    onClick={action(() => {
+                                        this.currentGroup = group;
+                                    })}>
+                                    <IconButton
+                                        icon={<FontAwesomeIcon icon="info-circle" />}
+                                        size={Size.XSMALL}
+                                        color={StrCast(Doc.UserDoc().userColor)}
+                                        onClick={action(() => {
+                                            this.currentGroup = group;
+                                        })}
+                                    />
                                 </div>
                             </div>
                         ))}
@@ -404,6 +449,6 @@ export class GroupManager extends ObservableReactComponent<{}> {
     }
 
     render() {
-        return <MainViewModal contents={this.groupInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxStyle={{ zIndex: 1002 }} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={this.close} />;
+        return <MainViewModal contents={this.groupInterface} isDisplayed={this.isOpen} interactive dialogueBoxStyle={{ zIndex: 1002 }} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={this.close} />;
     }
 }
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index 48d3ac046..6dc96f1b5 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -3,12 +3,10 @@ import { simulateMouseClick } from '../../ClientUtils';
 import { Doc, Opt } from '../../fields/Doc';
 import { Cast, StrCast } from '../../fields/Types';
 import { WebField } from '../../fields/URLField';
-import { DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
 import { DocumentLinksButton } from '../views/nodes/DocumentLinksButton';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { DocumentManager } from './DocumentManager';
-import { SearchUtil } from './SearchUtil';
 import { SelectionManager } from './SelectionManager';
 
 export namespace Hypothesis {
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index dfad9755c..8d4eefa7e 100644
--- a/src/client/util/Import & Export/ImageUtils.ts	
+++ b/src/client/util/Import & Export/ImageUtils.ts	
@@ -16,7 +16,7 @@ export namespace ImageUtils {
     };
     export const ExtractImgInfo = async (document: Doc): Promise<imgInfo | undefined> => {
         const field = Cast(document.data, ImageField);
-        return field ? await Networking.PostToServer('/inspectImage', { source: field.url.href }) : undefined;
+        return field ? Networking.PostToServer('/inspectImage', { source: field.url.href }) : undefined;
     };
 
     export const AssignImgInfo = (document: Doc, data?: imgInfo) => {
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index ba715aa1f..85bada8c9 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -53,7 +53,7 @@ export class LinkFollower {
         const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView ?? backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
         const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? fwdLinks.concat(backLinks) : traverseBacklink ? backLinks : fwdLinks;
         const followLinks = sourceDoc.followLinkToggle || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1);
-        var count = 0;
+        let count = 0;
         const allFinished = () => ++count === followLinks.length && finished?.();
         if (!followLinks.length) {
             finished?.();
@@ -69,7 +69,7 @@ export class LinkFollower {
                         ? linkDoc.link_anchor_2
                         : linkDoc.link_anchor_1
             ) as Doc;
-            const srcAnchor = LinkManager.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
+            const srcAnchor: Doc = LinkManager.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
             if (target) {
                 const doFollow = (canToggle?: boolean) => {
                     const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
@@ -113,10 +113,12 @@ export class LinkFollower {
                 }
                 const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
                 if (srcAnchor.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
+                    // eslint-disable-next-line prefer-destructuring
                     target.x = moveTo[0];
                     movedTarget = true;
                 }
                 if (srcAnchor.followLinkYoffset !== undefined && moveTo[1] !== target.y) {
+                    // eslint-disable-next-line prefer-destructuring
                     target.y = moveTo[1];
                     movedTarget = true;
                 }
@@ -130,6 +132,7 @@ export class LinkFollower {
     }
 }
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) {
     SelectionManager.DeselectAll();
     return LinkFollower.FollowLink(undefined, doc, altKey) ? undefined : { select: true };
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 82cd791cc..c986bd674 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -25,6 +25,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
  * - user defined kvps
  */
 export class LinkManager {
+    // eslint-disable-next-line no-use-before-define
     @observable static _instance: LinkManager;
     @observable.shallow userLinkDBs: Doc[] = [];
     @observable public currentLink: Opt<Doc> = undefined;
@@ -61,7 +62,7 @@ export class LinkManager {
                         Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) =>
                             Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then(
                                 link &&
-                                    action(lAnchProtoProtos => {
+                                    action(() => {
                                         Doc.AddDocToList(Doc.UserDoc(), 'links', link);
                                         lAnchs[0]?.[DocData][DirectLinks].add(link);
                                         lAnchs[1]?.[DocData][DirectLinks].add(link);
@@ -78,7 +79,7 @@ export class LinkManager {
                     Promise.all([lproto?.link_anchor_1 as Doc, lproto?.link_anchor_2 as Doc].map(PromiseValue)).then((lAnchs: Opt<Doc>[]) =>
                         Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) =>
                             Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then(
-                                action(lAnchProtoProtos => {
+                                action(() => {
                                     link && lAnchs[0] && lAnchs[0][DocData][DirectLinks].delete(link);
                                     link && lAnchs[1] && lAnchs[1][DocData][DirectLinks].delete(link);
                                 })
@@ -100,7 +101,8 @@ export class LinkManager {
                                 (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
                                 (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link)));
                                 break;
-                            case 'update': //let oldValue = change.oldValue;
+                            case 'update': // let oldValue = change.oldValue;
+                            default:
                         }
                     },
                     true
@@ -120,6 +122,8 @@ export class LinkManager {
                                     added?.forEach((link: any) => addLinkToDoc(toRealField(link)));
                                     removed?.forEach((link: any) => remLinkFromDoc(toRealField(link)));
                                 });
+                                break;
+                            default:
                         }
                     },
                     true
@@ -133,7 +137,8 @@ export class LinkManager {
                     case 'splice':
                         (change as any).added.forEach(watchUserLinkDB);
                         break;
-                    case 'update': //let oldValue = change.oldValue;
+                    case 'update': // let oldValue = change.oldValue;
+                    default:
                 }
             },
             true
@@ -143,7 +148,7 @@ export class LinkManager {
     }
 
     public createlink_relationshipLists = () => {
-        //create new lists for link relations and their associated colors if the lists don't already exist
+        // create new lists for link relations and their associated colors if the lists don't already exist
         !Doc.UserDoc().link_relationshipList && (Doc.UserDoc().link_relationshipList = new List<string>());
         !Doc.UserDoc().link_ColorList && (Doc.UserDoc().link_ColorList = new List<string>());
         !Doc.UserDoc().link_relationshipSizes && (Doc.UserDoc().link_relationshipSizes = new List<number>());
@@ -153,6 +158,7 @@ export class LinkManager {
         Doc.AddDocToList(Doc.UserDoc(), 'links', linkDoc);
         if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
             Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc);
+            // eslint-disable-next-line no-use-before-define
             setTimeout(UPDATE_SERVER_CACHE, 100);
         }
     }
@@ -203,7 +209,7 @@ export class LinkManager {
         this.relatedLinker(anchor).forEach(link => {
             if (link.link_relationship && link.link_relationship !== '-ungrouped-') {
                 const relation = StrCast(link.link_relationship);
-                const anchorRelation = relation.indexOf(':') !== -1 ? relation.split(':')[Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), anchor) ? 0 : 1] : relation;
+                const anchorRelation: string = relation.indexOf(':') !== -1 ? relation.split(':')[Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), anchor) ? 0 : 1] : relation;
                 const group = anchorGroups.get(anchorRelation);
                 anchorGroups.set(anchorRelation, group ? [...group, link] : [link]);
             } else {
@@ -234,14 +240,7 @@ export class LinkManager {
         if (Doc.AreProtosEqual(DocCast(anchor?.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return '1';
         if (Doc.AreProtosEqual(DocCast(anchor?.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return '2';
         if (Doc.AreProtosEqual(anchor, linkDoc)) return '0';
-
-        // const a1 = DocCast(linkDoc.link_anchor_1);
-        // const a2 = DocCast(linkDoc.link_anchor_2);
-        // if (linkDoc.link_matchEmbeddings) {
-        //     return [a2, a2.annotationOn].includes(anchor) ? '2' : '1';
-        // }
-        // if (Doc.AreProtosEqual(a2, anchor) || Doc.AreProtosEqual(a2.annotationOn as Doc, anchor)) return '2';
-        // return Doc.AreProtosEqual(a1, anchor) || Doc.AreProtosEqual(a1.annotationOn as Doc, anchor) ? '1' : '2';
+        return undefined;
     }
 }
 
@@ -282,6 +281,7 @@ export function UPDATE_SERVER_CACHE() {
 }
 
 ScriptingGlobals.add(
+    // eslint-disable-next-line prefer-arrow-callback
     function links(doc: any) {
         return new List(LinkManager.Links(doc));
     },
diff --git a/src/client/util/ScriptingGlobals.ts b/src/client/util/ScriptingGlobals.ts
index bc159ed65..ac524394a 100644
--- a/src/client/util/ScriptingGlobals.ts
+++ b/src/client/util/ScriptingGlobals.ts
@@ -41,6 +41,7 @@ export namespace ScriptingGlobals {
         if (n === undefined || n === 'undefined') {
             return false;
         }
+        // eslint-disable-next-line no-prototype-builtins
         if (_scriptingGlobals.hasOwnProperty(n)) {
             throw new Error(`Global with name ${n} is already registered, choose another name`);
         }
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 65b9a977d..609fedfa9 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -8,16 +8,16 @@ import { DocOptions, FInfo } from '../documents/Documents';
 export namespace SearchUtil {
     export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
 
-    export function SearchCollection(collectionDoc: Opt<Doc>, query: string, matchKeyNames: boolean, onlyKeys?: string[]) {
+    export function SearchCollection(collectionDoc: Opt<Doc>, queryIn: string, matchKeyNames: boolean, onlyKeys?: string[]) {
         const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
         const blockedKeys = matchKeyNames
             ? []
             : Object.entries(DocOptions)
-                  .filter(([key, info]: [string, FInfo]) => !info?.searchable())
+                  .filter(([, info]: [string, FInfo]) => !info?.searchable())
                   .map(([key]) => key);
 
-        const exact = query.startsWith('=');
-        query = query.toLowerCase().split('=').lastElement();
+        const exact = queryIn.startsWith('=');
+        const query = queryIn.toLowerCase().split('=').lastElement();
 
         const results = new ObservableMap<Doc, string[]>();
         if (collectionDoc) {
@@ -51,7 +51,11 @@ export namespace SearchUtil {
      */
     export function documentKeys(doc: Doc) {
         const keys: { [key: string]: boolean } = {};
-        Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false)));
+        Doc.GetAllPrototypes(doc).map(proto =>
+            Object.keys(proto).forEach(key => {
+                keys[key] = false;
+            })
+        );
         return Array.from(Object.keys(keys));
     }
 
@@ -62,12 +66,14 @@ export namespace SearchUtil {
      * This method iterates through an array of docs and all docs within those docs, calling
      * the function func on each doc.
      */
-    export function foreachRecursiveDoc(docs: Doc[], func: (depth: number, doc: Doc) => void) {
+    export function foreachRecursiveDoc(docsIn: Doc[], func: (depth: number, doc: Doc) => void) {
+        let docs = docsIn;
         let newarray: Doc[] = [];
-        var depth = 0;
+        let depth = 0;
         const visited: Doc[] = [];
         while (docs.length > 0) {
             newarray = [];
+            // eslint-disable-next-line no-loop-func
             docs.filter(d => d && !visited.includes(d)).forEach(d => {
                 visited.push(d);
                 const fieldKey = Doc.LayoutFieldKey(d);
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index f983c29b7..8347844f7 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.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 { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
@@ -17,8 +19,8 @@ import { MainViewModal } from '../views/MainViewModal';
 import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
 import { GroupManager } from './GroupManager';
 import './SettingsManager.scss';
-import { undoBatch } from './UndoManager';
 import { SnappingManager } from './SnappingManager';
+import { undoable } from './UndoManager';
 
 export enum ColorScheme {
     Dark = 'Dark',
@@ -38,24 +40,104 @@ 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;
-    @observable private passwordResultText = '';
-    @observable private playgroundMode = false;
+    @observable private _passwordResultText = '';
+    @observable private _playgroundMode = false;
 
-    @observable private curr_password = '';
-    @observable private new_password = '';
-    @observable private new_confirm = '';
+    @observable private _curr_password = '';
+    @observable private _new_password = '';
+    @observable private _new_confirm = '';
     @observable private _lastPressedSidebarBtn: Opt<Doc> = undefined; // bcz: this is a hack to handle highlighting buttons in the leftpanel menu .. need to find a cleaner approach
-    @observable activeTab = 'Accounts';
+    @observable private _activeTab = 'Accounts';
+    @observable private _isOpen = false;
 
     @observable public propertiesWidth: number = 0;
 
+    private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true));
+
+    public closeMgr = action(() => {
+        this._isOpen = false;
+    });
+    public openMgr = action(() => {
+        this._isOpen = true;
+    });
+
+    private matchSystem = undoable(() => {
+        if (Doc.UserDoc().userThemeSystem) {
+            if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark);
+            if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light);
+        }
+    }, 'match system theme');
+    private setFreeformScrollMode = undoable((mode: string) => {
+        Doc.UserDoc().freeformScrollMode = mode;
+    }, 'set scroll mode');
+    private selectUserMode = undoable((mode: string) => {
+        Doc.noviceMode = mode === 'Novice';
+    }, 'change user mode');
+    private changeFontFamily = undoable((font: string) => {
+        Doc.UserDoc().fontFamily = font;
+    }, 'change font family');
+    private switchUserBackgroundColor = undoable((color: string) => {
+        Doc.UserDoc().userBackgroundColor = color;
+        addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${color} !important` });
+    }, 'change background color');
+    private switchUserColor = undoable((color: string) => {
+        Doc.UserDoc().userColor = color;
+    }, 'change user color');
+    switchUserVariantColor = undoable((color: string) => {
+        Doc.UserDoc().userVariantColor = color;
+    }, 'change variant color');
+    userThemeSystemToggle = undoable(() => {
+        Doc.UserDoc().userThemeSystem = !Doc.UserDoc().userThemeSystem;
+        this.matchSystem();
+    }, 'change theme color');
+    playgroundModeToggle = undoable(
+        action(() => {
+            this._playgroundMode = !this._playgroundMode;
+            if (this._playgroundMode) {
+                DocServer.Control.makeReadOnly();
+                addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
+            } else ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Control.makeEditable();
+        }),
+        'set playgorund mode'
+    );
+    changeColorScheme = undoable(
+        action((scheme: string) => {
+            Doc.UserDoc().userTheme = scheme;
+            switch (scheme) {
+                case ColorScheme.Light:
+                    this.switchUserColor('#323232');
+                    this.switchUserBackgroundColor('#DFDFDF');
+                    this.switchUserVariantColor('#BDDDF5');
+                    break;
+                case ColorScheme.Dark:
+                    this.switchUserColor('#DFDFDF');
+                    this.switchUserBackgroundColor('#323232');
+                    this.switchUserVariantColor('#4476F7');
+                    break;
+                case ColorScheme.CoolBlue:
+                    this.switchUserColor('#ADEAFF');
+                    this.switchUserBackgroundColor('#060A15');
+                    this.switchUserVariantColor('#3C51FF');
+                    break;
+                case ColorScheme.Cupcake:
+                    this.switchUserColor('#3BC7FF');
+                    this.switchUserBackgroundColor('#fffdf7');
+                    this.switchUserVariantColor('#FFD7F3');
+                    break;
+                case ColorScheme.Custom:
+                    break;
+                default:
+            }
+        }),
+        'change color scheme'
+    );
+
     constructor(props: {}) {
         super(props);
         makeObservable(this);
         SettingsManager.Instance = this;
         this.matchSystem();
-        window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
+        window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
             if (Doc.UserDoc().userThemeSystem) {
                 if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark);
                 if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light);
@@ -73,26 +155,9 @@ export class SettingsManager extends React.Component<{}> {
         );
         SnappingManager.SettingsStyle = SettingsManager._settingsStyle;
     }
-    matchSystem = () => {
-        if (Doc.UserDoc().userThemeSystem) {
-            if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark);
-            if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light);
-        }
-    };
-
-    public close = action(() => (this.isOpen = false));
-    public open = action(() => (this.isOpen = true));
 
-    private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true));
-    private changePassword = async () => {
-        if (!(this.curr_password && this.new_password && this.new_confirm)) {
-            runInAction(() => (this.passwordResultText = "Error: Hey, we're missing some fields!"));
-        } else {
-            const passwordBundle = { curr_pass: this.curr_password, new_pass: this.new_password, new_confirm: this.new_confirm };
-            const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
-            runInAction(() => (this.passwordResultText = error ? 'Error: ' + error[0].msg + '...' : 'Password successfully updated!'));
-        }
-    };
+    public get LastPressedBtn() { return this._lastPressedSidebarBtn; } // prettier-ignore
+    public set LastPressedBtn(state:Doc|undefined) { this._lastPressedSidebarBtn = state; } // prettier-ignore
 
     @computed public static get userColor() {
         return StrCast(Doc.UserDoc().userColor);
@@ -106,60 +171,6 @@ export class SettingsManager extends React.Component<{}> {
         return StrCast(Doc.UserDoc().userBackgroundColor);
     }
 
-    public get LastPressedBtn() { return this._lastPressedSidebarBtn; } // prettier-ignore
-    public SetLastPressedBtn = (state?:Doc) => runInAction(() => (this._lastPressedSidebarBtn = state)); // prettier-ignore
-
-    @undoBatch selectUserMode = action((mode: string) => (Doc.noviceMode = mode === 'Novice'));
-    @undoBatch changelayout_showTitle = action((e: React.ChangeEvent) => (Doc.UserDoc().layout_showTitle = (e.currentTarget as any).value ? 'title' : undefined));
-    @undoBatch changeFontFamily = action((font: string) => (Doc.UserDoc().fontFamily = font));
-    @undoBatch changeFontSize = action((val: number) => (Doc.UserDoc().fontSize = val));
-    @undoBatch switchUserBackgroundColor = action((color: string) => {
-        Doc.UserDoc().userBackgroundColor = color;
-        addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${color} !important` });
-    });
-    @undoBatch switchUserColor = action((color: string) => (Doc.UserDoc().userColor = color));
-    @undoBatch switchUserVariantColor = action((color: string) => (Doc.UserDoc().userVariantColor = color));
-    @undoBatch userThemeSystemToggle = action(() => {
-        Doc.UserDoc().userThemeSystem = !Doc.UserDoc().userThemeSystem;
-        this.matchSystem();
-    });
-    @undoBatch playgroundModeToggle = action(() => {
-        this.playgroundMode = !this.playgroundMode;
-        if (this.playgroundMode) {
-            DocServer.Control.makeReadOnly();
-            addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
-        } else ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Control.makeEditable();
-    });
-
-    @undoBatch
-    changeColorScheme = action((scheme: string) => {
-        Doc.UserDoc().userTheme = scheme;
-        switch (scheme) {
-            case ColorScheme.Light:
-                this.switchUserColor('#323232');
-                this.switchUserBackgroundColor('#DFDFDF');
-                this.switchUserVariantColor('#BDDDF5');
-                break;
-            case ColorScheme.Dark:
-                this.switchUserColor('#DFDFDF');
-                this.switchUserBackgroundColor('#323232');
-                this.switchUserVariantColor('#4476F7');
-                break;
-            case ColorScheme.CoolBlue:
-                this.switchUserColor('#ADEAFF');
-                this.switchUserBackgroundColor('#060A15');
-                this.switchUserVariantColor('#3C51FF');
-                break;
-            case ColorScheme.Cupcake:
-                this.switchUserColor('#3BC7FF');
-                this.switchUserBackgroundColor('#fffdf7');
-                this.switchUserVariantColor('#FFD7F3');
-                break;
-            case ColorScheme.Custom:
-                break;
-        }
-    });
-
     @computed get colorsContent() {
         const schemeMap = Array.from(Object.keys(ColorScheme));
         const userTheme = StrCast(Doc.UserDoc().userTheme);
@@ -227,7 +238,9 @@ export class SettingsManager extends React.Component<{}> {
                     formLabel="Show document header"
                     formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
-                    onClick={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')}
+                    onClick={() => {
+                        Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date';
+                    }}
                     toggleStatus={Doc.UserDoc().layout_showTitle !== undefined}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
@@ -236,8 +249,10 @@ export class SettingsManager extends React.Component<{}> {
                     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'])}
+                    onClick={() => {
+                        Doc.UserDoc().documentLinksButton_fullMenu = !Doc.UserDoc().documentLinksButton_fullMenu;
+                    }}
+                    toggleStatus={BoolCast(Doc.UserDoc().documentLinksButton_fullMenu)}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                 />
@@ -245,7 +260,9 @@ export class SettingsManager extends React.Component<{}> {
                     formLabel="Show Button Labels"
                     formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
-                    onClick={e => (FontIconBox.ShowIconLabels = !FontIconBox.ShowIconLabels)}
+                    onClick={() => {
+                        FontIconBox.ShowIconLabels = !FontIconBox.ShowIconLabels;
+                    }}
                     toggleStatus={FontIconBox.ShowIconLabels}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
@@ -254,7 +271,9 @@ export class SettingsManager extends React.Component<{}> {
                     formLabel="Recognize Ink Gestures"
                     formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
-                    onClick={e => (GestureOverlay.RecognizeGestures = !GestureOverlay.RecognizeGestures)}
+                    onClick={() => {
+                        GestureOverlay.RecognizeGestures = !GestureOverlay.RecognizeGestures;
+                    }}
                     toggleStatus={GestureOverlay.RecognizeGestures}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
@@ -263,7 +282,9 @@ export class SettingsManager extends React.Component<{}> {
                     formLabel="Hide Labels In Ink Shapes"
                     formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
-                    onClick={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)}
+                    onClick={() => {
+                        Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels;
+                    }}
                     toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
@@ -272,7 +293,9 @@ export class SettingsManager extends React.Component<{}> {
                     formLabel="Open Ink Docs in Lightbox"
                     formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
-                    onClick={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)}
+                    onClick={() => {
+                        Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox;
+                    }}
                     toggleStatus={BoolCast(Doc.UserDoc().openInkInLightbox)}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
@@ -281,7 +304,9 @@ export class SettingsManager extends React.Component<{}> {
                     formLabel="Show Link Lines"
                     formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
-                    onClick={e => (Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines)}
+                    onClick={() => {
+                        Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines;
+                    }}
                     toggleStatus={BoolCast(Doc.UserDoc().showLinkLines)}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
@@ -296,7 +321,9 @@ export class SettingsManager extends React.Component<{}> {
                         step={2}
                         type={Type.TERT}
                         unit="px"
-                        setNumber={val => console.log('GOT: ' + (Doc.UserDoc().headerHeight = val))}
+                        setNumber={val => {
+                            Doc.UserDoc().headerHeight = val;
+                        }}
                     />
                 </Group>
             </div>
@@ -320,7 +347,6 @@ export class SettingsManager extends React.Component<{}> {
 
     @computed get textContent() {
         const fontFamilies = ['Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text', 'Roboto'];
-        const fontSizes = ['7px', '8px', '9px', '10px', '12px', '14px', '16px', '18px', '20px', '24px', '32px', '48px', '72px'];
 
         return (
             <div className="tab-content appearances-content">
@@ -338,19 +364,19 @@ export class SettingsManager extends React.Component<{}> {
                                 type={Type.PRIM}
                                 number={NumCast(Doc.UserDoc().fontSize, Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')))}
                                 unit="px"
-                                setNumber={val => (Doc.UserDoc().fontSize = val + 'px')}
+                                setNumber={val => {
+                                    Doc.UserDoc().fontSize = val + 'px';
+                                }}
                             />
                             <Dropdown
-                                items={fontFamilies.map(val => {
-                                    return {
-                                        text: val,
-                                        val: val,
-                                        style: {
-                                            fontFamily: val,
-                                        },
-                                    };
-                                })}
-                                closeOnSelect={true}
+                                items={fontFamilies.map(val => ({
+                                    text: val,
+                                    val: val,
+                                    style: {
+                                        fontFamily: val,
+                                    },
+                                }))}
+                                closeOnSelect
                                 dropdownType={DropdownType.SELECT}
                                 type={Type.TERT}
                                 selectedVal={StrCast(Doc.UserDoc().fontFamily)}
@@ -367,28 +393,13 @@ export class SettingsManager extends React.Component<{}> {
         );
     }
 
-    @action
-    changeVal = (value: string, pass: string) => {
-        switch (pass) {
-            case 'curr':
-                this.curr_password = value;
-                break;
-            case 'new':
-                this.new_password = value;
-                break;
-            case 'conf':
-                this.new_confirm = value;
-                break;
-        }
-    };
-
     @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 />
-                {!this.passwordResultText ? null : <div className={`${this.passwordResultText.startsWith('Error') ? 'error' : 'success'}-text`}>{this.passwordResultText}</div>}
+                {!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} />
             </div>
@@ -418,10 +429,6 @@ export class SettingsManager extends React.Component<{}> {
         );
     }
 
-    setFreeformScrollMode = (mode: string) => {
-        Doc.UserDoc().freeformScrollMode = mode;
-    };
-
     @computed get modesContent() {
         return (
             <div className="tab-content modes-content">
@@ -430,7 +437,7 @@ export class SettingsManager extends React.Component<{}> {
                     <div className="tab-column-content">
                         <Dropdown
                             formLabel="Mode"
-                            closeOnSelect={true}
+                            closeOnSelect
                             items={[
                                 {
                                     text: 'Novice',
@@ -454,7 +461,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
@@ -462,7 +469,7 @@ export class SettingsManager extends React.Component<{}> {
                     <div className="tab-column-content">
                         <Dropdown
                             formLabel="Scroll Mode"
-                            closeOnSelect={true}
+                            closeOnSelect
                             items={[
                                 {
                                     text: 'Scroll to Pan',
@@ -493,10 +500,28 @@ export class SettingsManager extends React.Component<{}> {
                             formLabel="Default access private"
                             color={SettingsManager.userColor}
                             toggleStatus={BoolCast(Doc.defaultAclPrivate)}
-                            onClick={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))}
+                            onClick={action(() => {
+                                Doc.defaultAclPrivate = !Doc.defaultAclPrivate;
+                            })}
+                        />
+                        <Toggle
+                            toggleType={ToggleType.SWITCH}
+                            formLabel="Enable Sharing UI"
+                            color={SettingsManager.userColor}
+                            toggleStatus={BoolCast(Doc.IsSharingEnabled)}
+                            onClick={action(() => {
+                                Doc.IsSharingEnabled = !Doc.IsSharingEnabled;
+                            })}
+                        />
+                        <Toggle
+                            toggleType={ToggleType.SWITCH}
+                            formLabel="Disable Info UI"
+                            color={SettingsManager.userColor}
+                            toggleStatus={BoolCast(Doc.IsInfoUIDisabled)}
+                            onClick={action(() => {
+                                Doc.IsInfoUIDisabled = !Doc.IsInfoUIDisabled;
+                            })}
                         />
-                        <Toggle toggleType={ToggleType.SWITCH} formLabel="Enable Sharing UI" color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.IsSharingEnabled)} onClick={action(() => (Doc.IsSharingEnabled = !Doc.IsSharingEnabled))} />
-                        <Toggle toggleType={ToggleType.SWITCH} formLabel="Disable Info UI" color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.IsInfoUIDisabled)} onClick={action(() => (Doc.IsInfoUIDisabled = !Doc.IsInfoUIDisabled))} />
                     </div>
                 </div>
             </div>
@@ -518,7 +543,7 @@ export class SettingsManager extends React.Component<{}> {
                 <div className="settings-panel" style={{ background: SettingsManager.userColor }}>
                     <div className="settings-tabs">
                         {tabs.map(tab => {
-                            const isActive = this.activeTab === tab.title;
+                            const isActive = this._activeTab === tab.title;
                             return (
                                 <div
                                     key={tab.title}
@@ -527,7 +552,9 @@ export class SettingsManager extends React.Component<{}> {
                                         color: isActive ? SettingsManager.userColor : SettingsManager.userBackgroundColor,
                                     }}
                                     className={'tab-control ' + (isActive ? 'active' : 'inactive')}
-                                    onClick={action(() => (this.activeTab = tab.title))}>
+                                    onClick={action(() => {
+                                        this._activeTab = tab.title;
+                                    })}>
                                     {tab.title}
                                 </div>
                             );
@@ -544,12 +571,12 @@ export class SettingsManager extends React.Component<{}> {
                 </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.closeMgr} color={SettingsManager.userColor} />
                 </div>
 
                 <div className="settings-content" style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}>
                     {tabs.map(tab => (
-                        <div key={tab.title} className={'tab-section ' + (this.activeTab === tab.title ? 'active' : 'inactive')}>
+                        <div key={tab.title} className={'tab-section ' + (this._activeTab === tab.title ? 'active' : 'inactive')}>
                             {tab.ele}
                         </div>
                     ))}
@@ -558,13 +585,37 @@ export class SettingsManager extends React.Component<{}> {
         );
     }
 
+    private changePassword = async () => {
+        if (!(this._curr_password && this._new_password && this._new_confirm)) {
+            runInAction(() => {
+                this._passwordResultText = "Error: Hey, we're missing some fields!";
+            });
+        } else {
+            const passwordBundle = { curr_pass: this._curr_password, new_pass: this._new_password, new_confirm: this._new_confirm };
+            const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
+            runInAction(() => {
+                this._passwordResultText = error ? 'Error: ' + error[0].msg + '...' : 'Password successfully updated!';
+            });
+        }
+    };
+
+    @action
+    changeVal = (value: string, pass: string) => {
+        switch (pass) {
+            case 'curr': this._curr_password = value; break;
+            case 'new':  this._new_password = value;  break;
+            case 'conf': this._new_confirm = value;  break;
+            default:
+        } // prettier-ignore
+    };
+
     render() {
         return (
             <MainViewModal
                 contents={this.settingsInterface}
-                isDisplayed={this.isOpen}
-                interactive={true}
-                closeOnExternalClick={this.close}
+                isDisplayed={this._isOpen}
+                interactive
+                closeOnExternalClick={this.closeMgr}
                 dialogueBoxStyle={{ width: 'fit-content', height: '300px', background: Cast(Doc.UserDoc().userColor, 'string', null) }}
             />
         );
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index ade4cc218..6676e4e03 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,3 +1,6 @@
+/* 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 */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Button, IconButton, Size, Type } from 'browndash-components';
 import { concat, intersection } from 'lodash';
@@ -65,7 +68,10 @@ interface ValidatedUser {
 
 @observer
 export class SharingManager extends React.Component<{}> {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: SharingManager;
+    private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup
+    private populating: boolean = false; // whether the list of users is populating or not
     @observable private isOpen = false; // whether the SharingManager modal is open or not
     @observable public users: ValidatedUser[] = []; // the list of users with sharing docs
     @observable private targetDoc: Doc | undefined = undefined; // the document being shared
@@ -77,11 +83,9 @@ export class SharingManager extends React.Component<{}> {
     @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
     @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of groups
-    private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup
     // if both showUserOptions and showGroupOptions are false then both are displayed
     @observable private showUserOptions: boolean = false; // whether to show individuals as options when sharing (in the react-select component)
     @observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component)
-    private populating: boolean = false; // whether the list of users is populating or not
     @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
@@ -90,33 +94,6 @@ export class SharingManager extends React.Component<{}> {
     //     return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
     // }
 
-    public open = (target?: DocumentView, target_doc?: Doc) => {
-        this.populateUsers();
-        runInAction(() => {
-            this.targetDocView = target;
-            this.targetDoc = target_doc || target?.Document;
-            DictationOverlay.Instance.hasActiveModal = true;
-            this.isOpen = this.targetDoc !== undefined;
-            this.permissions = SharingPermissions.Augment;
-            this.upgradeNested = true;
-        });
-    };
-
-    public close = action(() => {
-        this.isOpen = false;
-        this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
-        TaskCompletionBox.taskCompleted = false;
-        setTimeout(
-            action(() => {
-                // this.copied = false;
-                DictationOverlay.Instance.hasActiveModal = false;
-                this.targetDoc = undefined;
-            }),
-            500
-        );
-        this.layoutDocAcls = false;
-    });
-
     constructor(props: {}) {
         super(props);
         makeObservable(this);
@@ -131,32 +108,334 @@ export class SharingManager extends React.Component<{}> {
     }
 
     /**
-     * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
+     * Handles changes in the users selected in react-select
      */
-    populateUsers = async () => {
-        if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) {
-            this.populating = true;
-            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';
+    @action
+    handleUsersChange = (selectedOptions: any) => {
+        this.selectedUsers = selectedOptions as UserOptions[];
+    };
+
+    /**
+     * Handles changes in the permission chosen to share with someone with
+     */
+    handlePermissionsChange = undoable(
+        action((event: React.ChangeEvent<HTMLSelectElement>) => {
+            this.permissions = event.currentTarget.value as SharingPermissions;
+        }),
+        'permission change'
+    );
+
+    /**
+     * @returns the main interface of the SharingManager.
+     */
+    @computed get sharingInterface() {
+        if (!this.targetDoc) return null;
+        TraceMobx();
+        const groupList = GroupManager.Instance?.allGroups || [];
+
+        const sortedUsers = this.users
+            .slice()
+            .sort(this.sortUsers)
+            .map(({ user: { email } }) => ({ label: email, value: indType + email }));
+        const sortedGroups = groupList
+            .slice()
+            .sort(this.sortGroups)
+            .map(({ title }) => ({ label: StrCast(title), value: groupType + StrCast(title) }));
+
+        // the next block handles the users shown (individuals/groups/both)
+        const options: GroupedOptions[] = [];
+        if (GroupManager.Instance) {
+            if ((this.showUserOptions && this.showGroupOptions) || (!this.showUserOptions && !this.showGroupOptions)) {
+                options.push({ label: 'Individuals', options: sortedUsers }, { label: 'Groups', options: sortedGroups });
+            } else if (this.showUserOptions) options.push({ label: 'Individuals', options: sortedUsers });
+            else options.push({ label: 'Groups', options: sortedGroups });
+        }
+
+        const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
+        const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
+
+        let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+
+        if (this.myDocAcls) {
+            const newDocs: Doc[] = [];
+            SearchUtil.foreachRecursiveDoc(docs, (depth, doc) => newDocs.push(doc));
+            docs = newDocs.filter(doc => GetEffectiveAcl(doc) === AclAdmin);
+        }
+
+        const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
+
+        // tslint:disable-next-line: no-unnecessary-callback-wrapper
+        const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc));
+        const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin);
+
+        // users in common between all docs
+        const commonKeys = intersection(docs).reduce((list, doc) => (doc?.[DocAcl] ? [...list, ...Object.keys(doc[DocAcl])] : list), [] as string[]);
+
+        // the list of users shared with
+        const userListContents = users
+            // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
+            .filter(({ user }) => docs[0]?.author !== user.email)
+            .map(({ user, linkDatabase, sharingDoc, userColor }) => {
+                const userKey = `acl-${normalizeEmail(user.email)}`;
+                const uniform = docs.every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]);
+                // const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
+                let permissions = targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(targetDoc[userKey]);
+                permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
+
+                return !permissions ? null : (
+                    <div key={userKey} className="container">
+                        <span className="padding">{user.email}</span>
+                        <div className="edit-actions">
+                            {admin || this.myDocAcls ? (
+                                <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value, undefined)}>
+                                    {this.sharingOptions(uniform)}
+                                </select>
+                            ) : (
+                                <div className={`permissions-dropdown-${permissions}`}>
+                                    {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)}
+                                    &nbsp;
+                                </div>
+                            )}
+                        </div>
+                    </div>
+                );
             });
-            const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[]));
-            raw.map(
-                action((newUser: User) => {
-                    const sharingDoc = docs[newUser.sharingDocumentId];
-                    const linkDatabase = docs[newUser.linkDatabaseId];
-                    if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
-                        if (!this.users.find(user => user.user.email === newUser.email)) {
-                            this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
-                            //LinkManager.addLinkDB(linkDatabase);
-                        }
-                    }
-                })
+
+        // checks if every doc has the same author
+        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(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 === ClientUtils.CurrentUserEmail() ? 'Me' : StrCast(targetDoc?.author)}</span>
+                    <div className="edit-actions">
+                        <div className="permissions-dropdown">Owner</div>
+                    </div>
+                </div>
+            ) : null,
+            sameAuthor && targetDoc?.author !== ClientUtils.CurrentUserEmail() ? (
+                <div key="me" className="container">
+                    <span className="padding">Me</span>
+                    <div className="edit-actions">
+                        <div className={`permissions-dropdown-${curUserPermission}`}>
+                            {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? concat(ReverseHierarchyMap.get(curUserPermission!)?.image, ' ', curUserPermission) : '-multiple-'}
+                            &nbsp;
+                        </div>
+                    </div>
+                </div>
+            ) : null
+        );
+
+        // the list of groups shared with
+        const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true));
+        groupListMap.unshift({ title: 'Guest' }); // , { title: "ALL" });
+        const groupListContents = groupListMap.map(group => {
+            const groupKey = `acl-${StrCast(group.title)}`;
+            const uniform = docs.every(doc => doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey]);
+            const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
+
+            return !permissions ? null : (
+                <div key={groupKey} className="container" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+                    <div className="padding">{StrCast(group.title)}</div>
+                    &nbsp;
+                    {group instanceof Doc ? (
+                        <IconButton
+                            icon={<FontAwesomeIcon icon="info-circle" />}
+                            size={Size.XSMALL}
+                            color={SettingsManager.userColor}
+                            onClick={action(() => {
+                                GroupManager.Instance.currentGroup = group;
+                            })}
+                        />
+                    ) : null}
+                    <div className="edit-actions">
+                        {admin || this.myDocAcls ? (
+                            <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
+                                {this.sharingOptions(uniform, group.title === 'Guest')}
+                            </select>
+                        ) : (
+                            <div className={`permissions-dropdown-${permissions}`}>
+                                {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)}
+                                &nbsp;
+                            </div>
+                        )}
+                    </div>
+                </div>
             );
-            this.populating = false;
-        }
-    };
+        });
+        return (
+            <div className="sharing-interface">
+                {GroupManager.Instance?.currentGroup ? (
+                    <GroupMemberView
+                        group={GroupManager.Instance.currentGroup}
+                        onCloseButtonClick={action(() => {
+                            GroupManager.Instance.currentGroup = undefined;
+                        })}
+                    />
+                ) : null}
+                <div
+                    className="sharing-contents"
+                    style={{
+                        background: SettingsManager.userBackgroundColor,
+                        color: StrCast(Doc.UserDoc().userColor),
+                    }}>
+                    <p className="share-title" style={{ color: SettingsManager.userColor }}>
+                        <div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}>
+                            <FontAwesomeIcon icon="question-circle" size="sm" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} />
+                        </div>
+                        <b>Share </b>
+                        {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
+                    </p>
+                    <div className="share-copy-link">
+                        <Button type={Type.TERT} color={SettingsManager.userColor} icon={<FontAwesomeIcon icon="copy" size="sm" />} iconPlacement="left" text="Copy Guest URL" onClick={this.copyURL} />
+                    </div>
+                    <div className="close-button">
+                        <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.close} color={SettingsManager.userColor} />
+                    </div>
+                    {admin ? (
+                        <div className="share-container">
+                            <div className="share-setup" style={{ border: StrCast(Doc.UserDoc().userColor) }}>
+                                <Select
+                                    className="user-search"
+                                    placeholder="Enter user or group name..."
+                                    isMulti
+                                    isSearchable
+                                    closeMenuOnSelect={false}
+                                    options={options}
+                                    onKeyDown={e => e.stopPropagation()}
+                                    onChange={this.handleUsersChange}
+                                    value={this.selectedUsers}
+                                    styles={{
+                                        control: () => ({
+                                            display: 'inline-flex',
+                                            width: '100%',
+                                        }),
+                                        indicatorSeparator: () => ({
+                                            display: 'inline-flex',
+                                            visibility: 'hidden',
+                                        }),
+                                        indicatorsContainer: () => ({
+                                            display: 'inline-flex',
+                                            textDecorationColor: 'black',
+                                        }),
+                                        valueContainer: () => ({
+                                            display: 'inline-flex',
+                                            fontStyle: StrCast(Doc.UserDoc().userColor),
+                                            color: StrCast(Doc.UserDoc().userColor),
+                                            width: '100%',
+                                        }),
+                                    }}
+                                />
+                                <div className="permissions-select">
+                                    <select className={`permissions-dropdown-${this.permissions}`} onChange={this.handlePermissionsChange} value={this.permissions}>
+                                        {this.sharingOptions(true)}
+                                    </select>
+                                </div>
+                                <div className="share-button">
+                                    <Button text="SHARE" type={Type.TERT} color={SettingsManager.userColor} onClick={this.share} />
+                                </div>
+                            </div>
+                            <div className="sort-checkboxes">
+                                <input
+                                    type="checkbox"
+                                    onChange={action(() => {
+                                        this.showUserOptions = !this.showUserOptions;
+                                    })}
+                                />{' '}
+                                <label style={{ marginRight: 10 }}>Individuals</label>
+                                <input
+                                    type="checkbox"
+                                    onChange={action(() => {
+                                        this.showGroupOptions = !this.showGroupOptions;
+                                    })}
+                                />{' '}
+                                <label>Groups</label>
+                            </div>
+
+                            <div className="acl-container">
+                                {Doc.noviceMode ? null : (
+                                    <div className="layoutDoc-acls">
+                                        <input
+                                            type="checkbox"
+                                            onChange={action(() => {
+                                                this.upgradeNested = !this.upgradeNested;
+                                            })}
+                                            checked={this.upgradeNested}
+                                        />{' '}
+                                        <label>Upgrade Nested </label>
+                                        <input
+                                            type="checkbox"
+                                            onChange={action(() => {
+                                                this.layoutDocAcls = !this.layoutDocAcls;
+                                            })}
+                                            checked={this.layoutDocAcls}
+                                        />{' '}
+                                        <label>Layout</label>
+                                    </div>
+                                )}
+                            </div>
+                        </div>
+                    ) : (
+                        <div className="share-container">
+                            <div className="acl-container">
+                                <div className="layoutDoc-acls">
+                                    <input
+                                        type="checkbox"
+                                        onChange={action(() => {
+                                            this.layoutDocAcls = !this.layoutDocAcls;
+                                        })}
+                                        checked={this.layoutDocAcls}
+                                    />{' '}
+                                    <label>Layout</label>
+                                </div>
+                            </div>
+                        </div>
+                    )}
+                    <div className="main-container" style={{ color: StrCast(Doc.UserDoc().userColor), border: StrCast(Doc.UserDoc().userColor) }}>
+                        <div className="individual-container">
+                            <div
+                                className="user-sort"
+                                onClick={action(() => {
+                                    this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending';
+                                })}>
+                                <div className="title-individual">
+                                    Individuals
+                                    <IconButton
+                                        icon={<FontAwesomeIcon icon={this.individualSort === 'ascending' ? 'caret-up' : this.individualSort === 'descending' ? 'caret-down' : 'caret-right'} />}
+                                        size={Size.XSMALL}
+                                        color={StrCast(Doc.UserDoc().userColor)}
+                                    />
+                                </div>
+                            </div>
+                            <div className="users-list">{userListContents}</div>
+                        </div>
+                        <div className="group-container">
+                            <div
+                                className="user-sort"
+                                onClick={action(() => {
+                                    this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending';
+                                })}>
+                                <div className="title-group">
+                                    Groups
+                                    <IconButton icon={<FontAwesomeIcon icon="info-circle" />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => GroupManager.Instance.open())} />
+                                    <IconButton
+                                        icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} />}
+                                        size={Size.XSMALL}
+                                        color={StrCast(Doc.UserDoc().userColor)}
+                                    />
+                                </div>
+                            </div>
+                            <div className="groups-list">{groupListContents}</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        );
+    }
 
     /**
      * Shares the document with a user.
@@ -200,12 +479,69 @@ export class SharingManager extends React.Component<{}> {
             }
         });
     }, 'set group permissions');
+    /**
+     * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
+     */
+    populateUsers = async () => {
+        if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) {
+            this.populating = true;
+            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) => {
+                    const sharingDoc = docs[newUser.sharingDocumentId];
+                    const linkDatabase = docs[newUser.linkDatabaseId];
+                    if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
+                        if (!this.users.find(user => user.user.email === newUser.email)) {
+                            this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
+                            // LinkManager.addLinkDB(linkDatabase);
+                        }
+                    }
+                })
+            );
+            this.populating = false;
+        }
+    };
+
+    // eslint-disable-next-line react/sort-comp
+    public close = action(() => {
+        this.isOpen = false;
+        this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
+        TaskCompletionBox.taskCompleted = false;
+        setTimeout(
+            action(() => {
+                // this.copied = false;
+                DictationOverlay.Instance.hasActiveModal = false;
+                this.targetDoc = undefined;
+            }),
+            500
+        );
+        this.layoutDocAcls = false;
+    });
+
+    // eslint-disable-next-line react/no-unused-class-component-methods
+    public open = (target?: DocumentView, targetDoc?: Doc) => {
+        this.populateUsers();
+        runInAction(() => {
+            this.targetDocView = target;
+            this.targetDoc = targetDoc || target?.Document;
+            DictationOverlay.Instance.hasActiveModal = true;
+            this.isOpen = this.targetDoc !== undefined;
+            this.permissions = SharingPermissions.Augment;
+            this.upgradeNested = true;
+        });
+    };
 
     /**
      * Shares the documents shared with a group with a new user who has been added to that group.
      * @param group
      * @param emailId
      */
+    // eslint-disable-next-line react/no-unused-class-component-methods
     shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
         const user = this.users.find(({ user: { email } }) => email === emailId)!;
         const self = this;
@@ -231,6 +567,7 @@ export class SharingManager extends React.Component<{}> {
     /**
      * Called from the properties sidebar to change permissions of a user.
      */
+    // eslint-disable-next-line react/no-unused-class-component-methods
     shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
         if (layout) this.layoutDocAcls = true;
         if (shareWith !== 'Guest') {
@@ -254,6 +591,7 @@ export class SharingManager extends React.Component<{}> {
      * @param group
      * @param emailId
      */
+    // eslint-disable-next-line react/no-unused-class-component-methods
     removeMember = (group: Doc, emailId: string) => {
         const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
 
@@ -277,6 +615,7 @@ export class SharingManager extends React.Component<{}> {
      * Removes a group's permissions from documents that have been shared with it.
      * @param group
      */
+    // eslint-disable-next-line react/no-unused-class-component-methods
     removeGroup = (group: Doc) => {
         if (group.docsShared) {
             DocListCast(group.docsShared).forEach(doc => {
@@ -299,25 +638,12 @@ export class SharingManager extends React.Component<{}> {
     //     targetDoc["acl-" + PublicKey] = permission;
     // }s
 
-    /**
-     * Copies the Public sharing url to the user's clipboard.
-     */
-    private copyURL = (e: any) => {
-        ClientUtils.CopyText(ClientUtils.shareUrl(this.targetDoc![Id]));
-    };
-
-    /**
-     * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
-     */
-    private sharingOptions(uniform: boolean, showGuestOptions?: boolean) {
-        const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
-        if (!uniform) dropdownValues.unshift('-multiple-');
-        return dropdownValues.map(permission => (
-            <option key={permission} value={permission}>
-                {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
-            </option>
-        ));
-    }
+    /**
+     * Copies the Public sharing url to the user's clipboard.
+     */
+    private copyURL = () => {
+        ClientUtils.CopyText(ClientUtils.shareUrl(this.targetDoc![Id]));
+    };
 
     private focusOn = (contents: string) => {
         const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
@@ -350,24 +676,6 @@ export class SharingManager extends React.Component<{}> {
         );
     };
 
-    /**
-     * Handles changes in the users selected in react-select
-     */
-    @action
-    handleUsersChange = (selectedOptions: any) => {
-        this.selectedUsers = selectedOptions as UserOptions[];
-    };
-
-    /**
-     * Handles changes in the permission chosen to share with someone with
-     */
-    handlePermissionsChange = undoable(
-        action((event: React.ChangeEvent<HTMLSelectElement>) => {
-            this.permissions = event.currentTarget.value as SharingPermissions;
-        }),
-        'permission change'
-    );
-
     /**
      * Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
      */
@@ -389,7 +697,9 @@ export class SharingManager extends React.Component<{}> {
                     TaskCompletionBox.textDisplayed = 'Document shared!';
                     TaskCompletionBox.taskCompleted = true;
                     setTimeout(
-                        action(() => (TaskCompletionBox.taskCompleted = false)),
+                        action(() => {
+                            TaskCompletionBox.taskCompleted = false;
+                        }),
                         2000
                     );
                 }
@@ -418,263 +728,20 @@ export class SharingManager extends React.Component<{}> {
         const g2 = StrCast(group2.title);
         return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
     };
-
     /**
-     * @returns the main interface of the SharingManager.
+     * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
      */
-    @computed get sharingInterface() {
-        if (!this.targetDoc) return null;
-        TraceMobx();
-        const groupList = GroupManager.Instance?.allGroups || [];
-
-        const sortedUsers = this.users
-            .slice()
-            .sort(this.sortUsers)
-            .map(({ user: { email } }) => ({ label: email, value: indType + email }));
-        const sortedGroups = groupList
-            .slice()
-            .sort(this.sortGroups)
-            .map(({ title }) => ({ label: StrCast(title), value: groupType + StrCast(title) }));
-
-        // the next block handles the users shown (individuals/groups/both)
-        const options: GroupedOptions[] = [];
-        if (GroupManager.Instance) {
-            if ((this.showUserOptions && this.showGroupOptions) || (!this.showUserOptions && !this.showGroupOptions)) {
-                options.push({ label: 'Individuals', options: sortedUsers }, { label: 'Groups', options: sortedGroups });
-            } else if (this.showUserOptions) options.push({ label: 'Individuals', options: sortedUsers });
-            else options.push({ label: 'Groups', options: sortedGroups });
-        }
-
-        const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
-        const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
-
-        let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
-
-        if (this.myDocAcls) {
-            const newDocs: Doc[] = [];
-            SearchUtil.foreachRecursiveDoc(docs, (depth, doc) => newDocs.push(doc));
-            docs = newDocs.filter(doc => GetEffectiveAcl(doc) === AclAdmin);
-        }
-
-        const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
-
-        // tslint:disable-next-line: no-unnecessary-callback-wrapper
-        const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc));
-        const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin);
-
-        // users in common between all docs
-        const commonKeys = intersection(docs).reduce((list, doc) => (doc?.[DocAcl] ? [...list, ...Object.keys(doc[DocAcl])] : list), [] as string[]);
-
-        // the list of users shared with
-        const userListContents = users
-            // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
-            .filter(({ user }) => docs[0]?.author !== user.email)
-            .map(({ user, linkDatabase, sharingDoc, userColor }) => {
-                const userKey = `acl-${normalizeEmail(user.email)}`;
-                const uniform = docs.every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]);
-                // const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
-                let permissions = targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(targetDoc[userKey]);
-                permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
-
-                return !permissions ? null : (
-                    <div key={userKey} className={'container'}>
-                        <span className={'padding'}>{user.email}</span>
-                        <div className="edit-actions">
-                            {admin || this.myDocAcls ? (
-                                <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value, undefined)}>
-                                    {this.sharingOptions(uniform)}
-                                </select>
-                            ) : (
-                                <div className={`permissions-dropdown-${permissions}`}>
-                                    {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)}
-                                    &nbsp;
-                                </div>
-                            )}
-                        </div>
-                    </div>
-                );
-            });
-
-        // checks if every doc has the same author
-        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(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 === ClientUtils.CurrentUserEmail() ? 'Me' : StrCast(targetDoc?.author)}</span>
-                    <div className="edit-actions">
-                        <div className={'permissions-dropdown'}>Owner</div>
-                    </div>
-                </div>
-            ) : null,
-            sameAuthor && targetDoc?.author !== ClientUtils.CurrentUserEmail() ? (
-                <div key={'me'} className={'container'}>
-                    <span className={'padding'}>Me</span>
-                    <div className="edit-actions">
-                        <div className={`permissions-dropdown-${curUserPermission}`}>
-                            {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? concat(ReverseHierarchyMap.get(curUserPermission!)?.image, ' ', curUserPermission) : '-multiple-'}
-                            &nbsp;
-                        </div>
-                    </div>
-                </div>
-            ) : null
-        );
-
-        // the list of groups shared with
-        const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true));
-        groupListMap.unshift({ title: 'Guest' }); //, { title: "ALL" });
-        const groupListContents = groupListMap.map(group => {
-            let groupKey = `acl-${StrCast(group.title)}`;
-            const uniform = docs.every(doc => doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey]);
-            const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
-
-            return !permissions ? null : (
-                <div key={groupKey} className={'container'} style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
-                    <div className={'padding'}>{StrCast(group.title)}</div>
-                    &nbsp;
-                    {group instanceof Doc ? <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={SettingsManager.userColor} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : null}
-                    <div className={'edit-actions'}>
-                        {admin || this.myDocAcls ? (
-                            <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}>
-                                {this.sharingOptions(uniform, group.title === 'Guest')}
-                            </select>
-                        ) : (
-                            <div className={`permissions-dropdown-${permissions}`}>
-                                {concat(ReverseHierarchyMap.get(permissions)?.image, ' ', permissions)}
-                                &nbsp;
-                            </div>
-                        )}
-                    </div>
-                </div>
-            );
-        });
-        return (
-            <div className="sharing-interface">
-                {GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null}
-                <div
-                    className="sharing-contents"
-                    style={{
-                        background: SettingsManager.userBackgroundColor,
-                        color: StrCast(Doc.UserDoc().userColor),
-                    }}>
-                    <p className="share-title" style={{ color: SettingsManager.userColor }}>
-                        <div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}>
-                            <FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} />
-                        </div>
-                        <b>Share </b>
-                        {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
-                    </p>
-                    <div className="share-copy-link">
-                        <Button type={Type.TERT} color={SettingsManager.userColor} icon={<FontAwesomeIcon icon={'copy'} size="sm" />} iconPlacement={'left'} text={'Copy Guest URL'} onClick={this.copyURL} />
-                    </div>
-                    <div className="close-button">
-                        <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={SettingsManager.userColor} />
-                    </div>
-                    {admin ? (
-                        <div className="share-container">
-                            <div className="share-setup" style={{ border: StrCast(Doc.UserDoc().userColor) }}>
-                                <Select
-                                    className="user-search"
-                                    placeholder="Enter user or group name..."
-                                    isMulti
-                                    isSearchable
-                                    closeMenuOnSelect={false}
-                                    options={options}
-                                    onKeyDown={e => e.stopPropagation()}
-                                    onChange={this.handleUsersChange}
-                                    value={this.selectedUsers}
-                                    styles={{
-                                        control: () => ({
-                                            display: 'inline-flex',
-                                            width: '100%',
-                                        }),
-                                        indicatorSeparator: () => ({
-                                            display: 'inline-flex',
-                                            visibility: 'hidden',
-                                        }),
-                                        indicatorsContainer: () => ({
-                                            display: 'inline-flex',
-                                            textDecorationColor: 'black',
-                                        }),
-                                        valueContainer: () => ({
-                                            display: 'inline-flex',
-                                            fontStyle: StrCast(Doc.UserDoc().userColor),
-                                            color: StrCast(Doc.UserDoc().userColor),
-                                            width: '100%',
-                                        }),
-                                    }}
-                                />
-                                <div className="permissions-select">
-                                    <select className={`permissions-dropdown-${this.permissions}`} onChange={this.handlePermissionsChange} value={this.permissions}>
-                                        {this.sharingOptions(true)}
-                                    </select>
-                                </div>
-                                <div className="share-button">
-                                    <Button text={'SHARE'} type={Type.TERT} color={SettingsManager.userColor} onClick={this.share} />
-                                </div>
-                            </div>
-                            <div className="sort-checkboxes">
-                                <input type="checkbox" onChange={action(() => (this.showUserOptions = !this.showUserOptions))} /> <label style={{ marginRight: 10 }}>Individuals</label>
-                                <input type="checkbox" onChange={action(() => (this.showGroupOptions = !this.showGroupOptions))} /> <label>Groups</label>
-                            </div>
-
-                            <div className="acl-container">
-                                {Doc.noviceMode ? null : (
-                                    <div className="layoutDoc-acls">
-                                        <input type="checkbox" onChange={action(() => (this.upgradeNested = !this.upgradeNested))} checked={this.upgradeNested} /> <label>Upgrade Nested </label>
-                                        <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
-                                    </div>
-                                )}
-                            </div>
-                        </div>
-                    ) : (
-                        <div className="share-container">
-                            <div className="acl-container">
-                                <div className="layoutDoc-acls">
-                                    <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
-                                </div>
-                            </div>
-                        </div>
-                    )}
-                    <div className="main-container" style={{ color: StrCast(Doc.UserDoc().userColor), border: StrCast(Doc.UserDoc().userColor) }}>
-                        <div className={'individual-container'}>
-                            <div className="user-sort" onClick={action(() => (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}>
-                                <div className="title-individual">
-                                    Individuals
-                                    <IconButton
-                                        icon={<FontAwesomeIcon icon={this.individualSort === 'ascending' ? 'caret-up' : this.individualSort === 'descending' ? 'caret-down' : 'caret-right'} />}
-                                        size={Size.XSMALL}
-                                        color={StrCast(Doc.UserDoc().userColor)}
-                                    />
-                                </div>
-                            </div>
-                            <div className="users-list">{userListContents}</div>
-                        </div>
-                        <div className={'group-container'}>
-                            <div className="user-sort" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
-                                <div className="title-group">
-                                    Groups
-                                    <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => GroupManager.Instance.open())} />
-                                    <IconButton
-                                        icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} />}
-                                        size={Size.XSMALL}
-                                        color={StrCast(Doc.UserDoc().userColor)}
-                                    />
-                                </div>
-                            </div>
-                            <div className={'groups-list'}>{groupListContents}</div>
-                        </div>
-                    </div>
-                </div>
-            </div>
-        );
+    private sharingOptions(uniform: boolean, showGuestOptions?: boolean) {
+        const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
+        if (!uniform) dropdownValues.unshift('-multiple-');
+        return dropdownValues.map(permission => (
+            <option key={permission} value={permission}>
+                {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
+            </option>
+        ));
     }
 
     render() {
-        return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+        return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
     }
 }
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index eb47bbe88..3da85191f 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -10,6 +10,7 @@ export class SnappingManager {
     @observable _shiftKey = false;
     @observable _ctrlKey = false;
     @observable _metaKey = false;
+    @observable _showPresPaths = false;
     @observable _isLinkFollowing = false;
     @observable _isDragging: boolean = false;
     @observable _isResizing: string | undefined = undefined; // the string is the Id of the document being resized
@@ -36,6 +37,7 @@ export class SnappingManager {
     public static get ShiftKey()       { return this.Instance._shiftKey; } // prettier-ignore
     public static get CtrlKey()        { return this.Instance._ctrlKey; } // prettier-ignore
     public static get MetaKey()        { return this.Instance._metaKey; } // prettier-ignore
+    public static get ShowPresPaths()  { return this.Instance._showPresPaths; } // prettier-ignore
     public static get IsLinkFollowing(){ return this.Instance._isLinkFollowing; } // prettier-ignore
     public static get IsDragging()     { return this.Instance._isDragging; } // prettier-ignore
     public static get IsResizing()     { return this.Instance._isResizing; } // prettier-ignore
@@ -44,6 +46,7 @@ export class SnappingManager {
     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 SetShowPresPaths  = (paths:boolean) => runInAction(() => {this.Instance._showPresPaths = paths}); // 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
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 4e941508d..956c0e674 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,8 +1,11 @@
+/* eslint-disable prefer-spread */
+/* eslint-disable no-use-before-define */
 import { action, observable, runInAction } from 'mobx';
 import { Without } from '../../Utils';
 import { RichTextField } from '../../fields/RichTextField';
 
-export let printToConsole = false; // Doc.MyDockedBtns.linearView_IsOpen
+// eslint-disable-next-line prefer-const
+let printToConsole = false; // Doc.MyDockedBtns.linearView_IsOpen
 
 function getBatchName(target: any, key: string | symbol): string {
     const keyName = key.toString();
@@ -38,10 +41,11 @@ function propertyDecorator(target: any, key: string | symbol) {
 }
 
 export function undoable(fn: (...args: any[]) => any, batchName: string): (...args: any[]) => any {
-    return function () {
+    return function (...fargs) {
         const batch = UndoManager.StartBatch(batchName);
         try {
-            return fn.apply(undefined, arguments as any);
+            // eslint-disable-next-line prefer-rest-params
+            return fn.apply(undefined, fargs);
         } finally {
             batch.end();
         }
@@ -49,13 +53,15 @@ export function undoable(fn: (...args: any[]) => any, batchName: string): (...ar
 }
 
 export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any;
+// eslint-disable-next-line no-redeclare
 export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any;
+// eslint-disable-next-line no-redeclare
 export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
     if (!key) {
-        return function () {
+        return function (...fargs: any[]) {
             const batch = UndoManager.StartBatch('');
             try {
-                return target.apply(undefined, arguments);
+                return target.apply(undefined, fargs);
             } finally {
                 batch.end();
             }
@@ -63,7 +69,7 @@ export function undoBatch(target: any, key?: string | symbol, descriptor?: Typed
     }
     if (!descriptor) {
         propertyDecorator(target, key);
-        return;
+        return undefined;
     }
     const oldFunction = descriptor.value;
 
@@ -87,14 +93,18 @@ export namespace UndoManager {
     }
     type UndoBatch = UndoEvent[];
 
-    export let undoStackNames: string[] = observable([]);
-    export let redoStackNames: string[] = observable([]);
-    export let undoStack: UndoBatch[] = observable([]);
-    export let redoStack: UndoBatch[] = observable([]);
     let currentBatch: UndoBatch | undefined;
-    export let batchCounter = observable.box(0);
     let undoing = false;
-    export let tempEvents: UndoEvent[] | undefined = undefined;
+    let tempEvents: UndoEvent[] | undefined;
+    export const undoStackNames: string[] = observable([]);
+    export const redoStackNames: string[] = observable([]);
+    export const undoStack: UndoBatch[] = observable([]);
+    export const redoStack: UndoBatch[] = observable([]);
+    export const batchCounter = observable.box(0);
+    let _fieldPrinter: (val: any) => string = val => val?.toString();
+    export function SetFieldPrinter(printer: (val: any) => string) {
+        _fieldPrinter = printer;
+    }
 
     export function AddEvent(event: UndoEvent, value?: any): void {
         if (currentBatch && batchCounter.get() && !undoing) {
@@ -103,8 +113,8 @@ export namespace UndoManager {
                     '                         '.slice(0, batchCounter.get()) +
                         'UndoEvent : ' +
                         event.prop +
-                        ' = ' +
-                        (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toJavascriptString(val)).join(',') : Field.toJavascriptString(value))
+                        ' = ' + // prettier-ignore
+                        (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(_fieldPrinter).join(',') : _fieldPrinter(value))
                 );
             currentBatch.push(event);
             tempEvents?.push(event);
@@ -130,21 +140,22 @@ export namespace UndoManager {
     }
     export function FilterBatches(fieldTypes: string[]) {
         const fieldCounts: { [key: string]: number } = {};
-        const lastStack = UndoManager.undoStack.slice(-1)[0]; //.lastElement();
+        const lastStack = UndoManager.undoStack.slice(-1)[0]; // .lastElement();
         if (lastStack) {
-            lastStack.forEach(ev => fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1));
+            lastStack.forEach(ev => {
+                fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1);
+            });
             const fieldCount2: { [key: string]: number } = {};
-            runInAction(
-                () =>
-                    (UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => {
-                        if (fieldTypes.includes(ev.prop)) {
-                            fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1;
-                            if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true;
-                            return false;
-                        }
-                        return true;
-                    }))
-            );
+            runInAction(() => {
+                UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => {
+                    if (fieldTypes.includes(ev.prop)) {
+                        fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1;
+                        if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true;
+                        return false;
+                    }
+                    return true;
+                });
+            });
         }
     }
     export function TraceOpenBatches() {
@@ -161,11 +172,10 @@ export namespace UndoManager {
             if (this.disposed) {
                 console.log('WARNING: undo batch already disposed');
                 return false;
-            } else {
-                this.disposed = true;
-                openBatches.splice(openBatches.indexOf(this));
-                return EndBatch(this.batchName, cancel);
             }
+            this.disposed = true;
+            openBatches.splice(openBatches.indexOf(this));
+            return EndBatch(this.batchName, cancel);
         };
 
         end = () => this.dispose(false);
@@ -183,7 +193,7 @@ export namespace UndoManager {
 
     const EndBatch = action((batchName: string, cancel: boolean = false) => {
         runInAction(() => batchCounter.set(batchCounter.get() - 1));
-        printToConsole && console.log('                          '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')');
+        printToConsole && console.log('                          '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + (currentBatch?.length ?? 0) + ')');
         if (batchCounter.get() === 0 && currentBatch?.length) {
             if (!cancel) {
                 undoStack.push(currentBatch);
@@ -200,10 +210,10 @@ export namespace UndoManager {
     export function StartTempBatch() {
         tempEvents = [];
     }
-    export function EndTempBatch<T>(success: boolean) {
+    export function EndTempBatch(success: boolean) {
         UndoManager.UndoTempBatch(success);
     }
-    //TODO Make this return the return value
+    // TODO Make this return the return value
     export function RunInBatch<T>(fn: () => T, batchName: string) {
         const batch = StartBatch(batchName);
         try {
@@ -235,9 +245,11 @@ export namespace UndoManager {
         }
 
         undoing = true;
-        for (let i = commands.length - 1; i >= 0; i--) {
-            commands[i].undo();
-        }
+        // eslint-disable-next-line prettier/prettier
+        commands
+            .slice()
+            .reverse()
+            .forEach(command => command.undo());
         undoing = false;
 
         redoStackNames.push(names ?? '???');
@@ -256,9 +268,7 @@ export namespace UndoManager {
         }
 
         undoing = true;
-        for (const command of commands) {
-            command.redo();
-        }
+        commands.forEach(command => command.redo());
         undoing = false;
 
         undoStackNames.push(names ?? '???');
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
index 02b3ee32c..2224e642d 100644
--- a/src/client/util/reportManager/ReportManager.tsx
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/media-has-caption */
+/* eslint-disable react/no-unused-class-component-methods */
 import { Octokit } from '@octokit/core';
 import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
@@ -13,7 +16,7 @@ import { ClientUtils } from '../../../ClientUtils';
 import { Doc } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
 import { MainViewModal } from '../../views/MainViewModal';
-import '.././SettingsManager.scss';
+import '../SettingsManager.scss';
 import { SettingsManager } from '../SettingsManager';
 import './ReportManager.scss';
 import { Filter, FormInput, FormTextArea, IssueCard, IssueView } from './ReportManagerComponents';
@@ -25,10 +28,12 @@ import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, d
  */
 @observer
 export class ReportManager extends React.Component<{}> {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: ReportManager;
     @observable private isOpen = false;
 
     @observable private query = '';
+    // eslint-disable-next-line react/sort-comp
     @action private setQuery = (q: string) => {
         this.query = q;
     };
@@ -83,7 +88,9 @@ export class ReportManager extends React.Component<{}> {
         this.formData = newData;
     });
 
-    public close = action(() => (this.isOpen = false));
+    public close = action(() => {
+        this.isOpen = false;
+    });
     public open = action(async () => {
         this.isOpen = true;
         if (this.shownIssues.length === 0) {
@@ -165,7 +172,7 @@ export class ReportManager extends React.Component<{}> {
      * @returns JSX element of a piece of media (image, video, audio)
      */
     private getMediaPreview = (fileData: FileData): JSX.Element => {
-        const file = fileData.file;
+        const { file } = fileData;
         const mimeType = file.type;
         const preview = URL.createObjectURL(file);
 
@@ -180,7 +187,8 @@ export class ReportManager extends React.Component<{}> {
                     </div>
                 </div>
             );
-        } else if (mimeType.startsWith('video/')) {
+        }
+        if (mimeType.startsWith('video/')) {
             return (
                 <div key={fileData._id} className="report-media-wrapper">
                     <div className="report-media-content">
@@ -194,7 +202,8 @@ export class ReportManager extends React.Component<{}> {
                     </div>
                 </div>
             );
-        } else if (mimeType.startsWith('audio/')) {
+        }
+        if (mimeType.startsWith('audio/')) {
             return (
                 <div key={fileData._id} className="report-audio-wrapper">
                     <audio src={preview} controls />
@@ -204,7 +213,7 @@ export class ReportManager extends React.Component<{}> {
                 </div>
             );
         }
-        return <></>;
+        return <div />;
     };
 
     /**
@@ -307,8 +316,8 @@ export class ReportManager extends React.Component<{}> {
                 <div className="report-selects">
                     <Dropdown
                         color={StrCast(Doc.UserDoc().userColor)}
-                        formLabel={'Type'}
-                        closeOnSelect={true}
+                        formLabel="Type"
+                        closeOnSelect
                         items={bugDropdownItems}
                         selectedVal={this.formData.type}
                         setSelectedVal={val => {
@@ -320,8 +329,8 @@ export class ReportManager extends React.Component<{}> {
                     />
                     <Dropdown
                         color={StrCast(Doc.UserDoc().userColor)}
-                        formLabel={'Priority'}
-                        closeOnSelect={true}
+                        formLabel="Priority"
+                        closeOnSelect
                         items={priorityDropdownItems}
                         selectedVal={this.formData.priority}
                         setSelectedVal={val => {
@@ -347,7 +356,7 @@ export class ReportManager extends React.Component<{}> {
                         text="Submit"
                         type={Type.TERT}
                         color={StrCast(Doc.UserDoc().userVariantColor)}
-                        icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
+                        icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
                         iconPlacement="right"
                         onClick={() => {
                             this.reportIssue();
@@ -364,7 +373,7 @@ export class ReportManager extends React.Component<{}> {
                     />
                 )}
                 <div style={{ position: 'absolute', top: '4px', right: '4px' }}>
-                    <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size={'16px'} />} onClick={this.close} />
+                    <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={this.close} />
                 </div>
             </div>
         );
@@ -376,9 +385,8 @@ export class ReportManager extends React.Component<{}> {
     private reportComponent = () => {
         if (this.viewState === ViewState.VIEW) {
             return this.viewIssuesComponent();
-        } else {
-            return this.reportIssueComponent();
         }
+        return this.reportIssueComponent();
     };
 
     render() {
@@ -386,7 +394,7 @@ export class ReportManager extends React.Component<{}> {
             <MainViewModal
                 contents={this.reportComponent()}
                 isDisplayed={this.isOpen}
-                interactive={true}
+                interactive
                 closeOnExternalClick={this.close}
                 dialogueBoxStyle={{ width: 'auto', minWidth: '300px', height: '85vh', maxHeight: '90vh', background: StrCast(Doc.UserDoc().userBackgroundColor), borderRadius: '8px' }}
             />
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 5760872fb..eb1030eec 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/jsx-props-no-spreading */
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, makeObservable, observable, runInAction } from 'mobx';
@@ -17,6 +18,7 @@ export interface OriginalMenuProps {
 
 export interface SubmenuProps {
     description: string;
+    // eslint-disable-next-line no-use-before-define
     subitems: ContextMenuProps[];
     noexpand?: boolean;
     addDivider?: boolean;
@@ -37,7 +39,9 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
     }
 
     componentDidMount() {
-        runInAction(() => (this._items.length = 0));
+        runInAction(() => {
+            this._items.length = 0;
+        });
         if ((this._props as SubmenuProps)?.subitems) {
             (this._props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i)));
         }
@@ -83,7 +87,9 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
             return;
         }
         this.currentTimeout = setTimeout(
-            action(() => (this.overItem = false)),
+            action(() => {
+                this.overItem = false;
+            }),
             ContextMenuItem.timeout
         );
     };
@@ -147,10 +153,10 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
                     ) : null}
                     <div className="contextMenu-description" onMouseEnter={this.onPointerEnter} style={{ alignItems: 'center', alignSelf: 'center' }}>
                         {this._props.description}
-                        <FontAwesomeIcon icon={'angle-right'} size="lg" style={{ position: 'absolute', right: '10px' }} />
+                        <FontAwesomeIcon icon="angle-right" size="lg" style={{ position: 'absolute', right: '10px' }} />
                     </div>
                     <div
-                        className={`contextMenu-item-background`}
+                        className="contextMenu-item-background"
                         style={{
                             background: SnappingManager.userColor,
                         }}
@@ -159,5 +165,6 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
                 </div>
             );
         }
+        return null;
     }
 }
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 14abd5f89..25415a4f0 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.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 { Button, ColorPicker, EditableText, Size, Type } from 'browndash-components';
 import { action, computed, makeObservable, observable } from 'mobx';
@@ -48,10 +50,18 @@ export class DashboardView extends ObservableReactComponent<{}> {
     @observable private selectedDashboardGroup = DashboardGroup.MyDashboards;
     @observable private newDashboardName = '';
     @observable private newDashboardColor = '#AFAFAF';
-    @action abortCreateNewDashboard = () => (this.openModal = false);
-    @action setNewDashboardName = (name: string) => (this.newDashboardName = name);
-    @action setNewDashboardColor = (color: string) => (this.newDashboardColor = color);
-    @action selectDashboardGroup = (group: DashboardGroup) => (this.selectedDashboardGroup = group);
+    @action abortCreateNewDashboard = () => {
+        this.openModal = false;
+    };
+    @action setNewDashboardName = (name: string) => {
+        this.newDashboardName = name;
+    };
+    @action setNewDashboardColor = (color: string) => {
+        this.newDashboardColor = color;
+    };
+    @action selectDashboardGroup = (group: DashboardGroup) => {
+        this.selectedDashboardGroup = group;
+    };
 
     clickDashboard = (e: React.MouseEvent, dashboard: Doc) => {
         if (this.selectedDashboardGroup === DashboardGroup.SharedDashboards) {
@@ -138,9 +148,9 @@ export class DashboardView extends ObservableReactComponent<{}> {
             <>
                 <div className="dashboard-view">
                     <div className="left-menu">
-                        <Button text="My Dashboards" active={this.selectedDashboardGroup === DashboardGroup.MyDashboards} color={color} align={'flex-start'} onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)} fillWidth />
+                        <Button text="My Dashboards" active={this.selectedDashboardGroup === DashboardGroup.MyDashboards} color={color} align="flex-start" onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)} fillWidth />
                         <Button
-                            text={'Shared Dashboards' + ' (' + this.getDashboards(DashboardGroup.SharedDashboards).length + ')'}
+                            text={'Shared Dashboards (' + this.getDashboards(DashboardGroup.SharedDashboards).length + ')'}
                             active={this.selectedDashboardGroup === DashboardGroup.SharedDashboards}
                             color={this.getDashboards(DashboardGroup.SharedDashboards).some(dash => !DocListCast(Doc.MySharedDocs.viewed).includes(dash)) ? 'green' : color}
                             align="flex-start"
@@ -165,14 +175,22 @@ export class DashboardView extends ObservableReactComponent<{}> {
                                           onContextMenu={e => this.onContextMenu(dashboard, e)}
                                           onClick={e => this.clickDashboard(e, dashboard)}>
                                           <img
+                                              alt=""
                                               src={
                                                   href ??
                                                   'https://media.istockphoto.com/photos/hot-air-balloons-flying-over-the-botan-canyon-in-turkey-picture-id1297349747?b=1&k=20&m=1297349747&s=170667a&w=0&h=oH31fJty_4xWl_JQ4OIQWZKP8C6ji9Mz7L4XmEnbqRU='
                                               }
                                           />
                                           <div className="info">
-                                              <EditableText type={Type.PRIM} color={color} val={StrCast(dashboard.title)} setVal={val => (dashboard[DocData].title = val)} />
-                                              {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? <div>unviewed</div> : <div></div>}
+                                              <EditableText
+                                                  type={Type.PRIM}
+                                                  color={color}
+                                                  val={StrCast(dashboard.title)}
+                                                  setVal={val => {
+                                                      dashboard[DocData].title = val;
+                                                  }}
+                                              />
+                                              {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? <div>unviewed</div> : <div />}
                                               <div
                                                   className="more"
                                                   onPointerDown={e => {
@@ -208,7 +226,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
                         )}
                     </div>
                 </div>
-                <MainViewModal contents={this.namingInterface} isDisplayed={this.openModal} interactive={true} closeOnExternalClick={this.abortCreateNewDashboard} dialogueBoxStyle={{ width: '400px', height: '180px', color: Colors.LIGHT_GRAY }} />
+                <MainViewModal contents={this.namingInterface} isDisplayed={this.openModal} interactive closeOnExternalClick={this.abortCreateNewDashboard} dialogueBoxStyle={{ width: '400px', height: '180px', color: Colors.LIGHT_GRAY }} />
             </>
         );
     }
@@ -252,6 +270,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
                 //     }
                 //     CollectionView.SetSafeMode(true);
             } else if (state.nro || state.nro === null || state.readonly === false) {
+                /* empty */
             } else if (doc.readOnly) {
                 DocServer.Control.makeReadOnly();
             } else {
@@ -274,7 +293,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
 
     public static resetDashboard = (dashboard: Doc) => {
         const config = StrCast(dashboard.dockingConfig);
-        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('"', '')) ?? [];
 
         const components =
@@ -368,7 +387,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
             title: `Untitled Tab 1`,
         };
 
-        const title = name ? name : `Dashboard ${dashboardCount}`;
+        const title = name || `Dashboard ${dashboardCount}`;
         const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
         const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
 
diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index e098bc361..b242acdba 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -7,14 +7,14 @@ import { MainViewModal } from './MainViewModal';
 
 @observer
 export class DictationOverlay extends React.Component {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: DictationOverlay;
     @observable private _dictationState = DictationManager.placeholder;
     @observable private _dictationSuccessState: boolean | undefined = undefined;
     @observable private _dictationDisplayState = false;
     @observable private _dictationListeningState: DictationManager.Controls.ListeningUIStatus = false;
 
-    public isPointerDown = false;
-    public overlayTimeout: NodeJS.Timeout | undefined;
+    // eslint-disable-next-line react/no-unused-class-component-methods
     public hasActiveModal = false;
 
     constructor(props: any) {
@@ -23,47 +23,32 @@ export class DictationOverlay extends React.Component {
         DictationOverlay.Instance = this;
     }
 
-    public initiateDictationFade = () => {
-        const duration = DictationManager.Commands.dictationFadeDuration;
-        this.overlayTimeout = setTimeout(() => {
-            this.dictationOverlayVisible = false;
-            this.dictationSuccess = undefined;
-            DictationOverlay.Instance.hasActiveModal = false;
-            setTimeout(() => (this.dictatedPhrase = DictationManager.placeholder), 500);
-        }, duration);
-    };
-    public cancelDictationFade = () => {
-        if (this.overlayTimeout) {
-            clearTimeout(this.overlayTimeout);
-            this.overlayTimeout = undefined;
-        }
-    };
-
-    @computed public get dictatedPhrase() {
-        return this._dictationState;
-    }
-    @computed public get dictationSuccess() {
-        return this._dictationSuccessState;
-    }
-    @computed public get dictationOverlayVisible() {
-        return this._dictationDisplayState;
-    }
-    @computed public get isListening() {
-        return this._dictationListeningState;
-    }
-
+    @computed public get dictatedPhrase() { return this._dictationState; } // prettier-ignore
     public set dictatedPhrase(value: string) {
-        runInAction(() => (this._dictationState = value));
+        runInAction(() => {
+            this._dictationState = value;
+        });
     }
+    @computed public get dictationSuccess() { return this._dictationSuccessState; } // prettier-ignore
     public set dictationSuccess(value: boolean | undefined) {
-        runInAction(() => (this._dictationSuccessState = value));
+        runInAction(() => { this._dictationSuccessState = value; }); // prettier-ignore
     }
+    @computed public get dictationOverlayVisible() { return this._dictationDisplayState; } // prettier-ignore
     public set dictationOverlayVisible(value: boolean) {
-        runInAction(() => (this._dictationDisplayState = value));
+        runInAction(() => { this._dictationDisplayState = value; }); // prettier-ignore
     }
+    @computed public get isListening() { return this._dictationListeningState; } // prettier-ignore
     public set isListening(value: DictationManager.Controls.ListeningUIStatus) {
-        runInAction(() => (this._dictationListeningState = value));
+        runInAction(() => { this._dictationListeningState = value; }); // prettier-ignore
     }
+    public initiateDictationFade = () => {
+        setTimeout(() => {
+            this.dictationOverlayVisible = false;
+            this.dictationSuccess = undefined;
+            DictationOverlay.Instance.hasActiveModal = false;
+            setTimeout(() => { this.dictatedPhrase = DictationManager.placeholder; }, 500); // prettier-ignore
+        }, DictationManager.Commands.dictationFadeDuration);
+    };
 
     render() {
         const success = this.dictationSuccess;
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 348b18129..fae3610ca 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -326,7 +326,10 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
 
         isAnyChildContentActive = () => this._isAnyChildContentActive;
 
-        whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+        whenChildContentsActiveChanged = action((isActive: boolean) => {
+            this._isAnyChildContentActive = isActive;
+            this._props.whenChildContentsActiveChanged(isActive);
+        });
     }
     return Component;
 }
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 16586d922..63b485a43 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,3 +1,6 @@
+/* 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 { IconLookup, IconProp } from '@fortawesome/fontawesome-svg-core';
 import { faCalendarDays } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -33,6 +36,7 @@ import { DashFieldView } from './nodes/formattedText/DashFieldView';
 @observer
 export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: any }> {
     private _dragRef = React.createRef<HTMLDivElement>();
+    // eslint-disable-next-line no-use-before-define
     @observable public static Instance: DocumentButtonBar;
 
     constructor(props: any) {
@@ -60,8 +64,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                             key={icon.toString()}
                             size="sm"
                             icon={icon}
-                            onPointerEnter={action(e => (this.subPin = allDocs ? 'All ' : ''))}
-                            onPointerLeave={action(e => (this.subPin = ''))}
+                            onPointerEnter={action(() => {
+                                this.subPin = allDocs ? 'All ' : '';
+                            })}
+                            onPointerLeave={action(() => {
+                                this.subPin = '';
+                            })}
                             onClick={e => {
                                 this._props.views().forEach(dv => click(dv!.Document));
                                 e.stopPropagation();
@@ -77,12 +85,14 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 <div
                     className="documentButtonBar-icon documentButtonBar-follow"
                     style={{ backgroundColor: followLink ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: followLink ? Colors.BLACK : Colors.WHITE }}
-                    onClick={undoBatch(e => this._props.views().map(view => view?.toggleFollowLink(undefined, false)))}>
+                    onClick={undoBatch(() => this._props.views().map(view => view?.toggleFollowLink(undefined, false)))}>
                     <div className="documentButtonBar-followTypes">
                         {followBtn(
                             true,
-                            (doc: Doc) => (doc.followAllLinks = !doc.followAllLinks),
-                            (doc?: Doc) => (doc?.followAllLinks ? true : false),
+                            (doc: Doc) => {
+                                doc.followAllLinks = !doc.followAllLinks;
+                            },
+                            (doc?: Doc) => !!doc?.followAllLinks,
                             'window-maximize'
                         )}
                     </div>
@@ -100,22 +110,22 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 <div className="documentButtonBar-linkTypes">
                     <Tooltip title={<div>search for target</div>}>
                         <div className="documentButtonBar-button">
-                            <button style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleLinkSearch}>
-                                <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" />
-                                <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'center', top: 9, left: 2 }} icon={'link'} size="lg" />
+                            <button type="button" style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleLinkSearch}>
+                                <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon="search" size="lg" />
+                                <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'center', top: 9, left: 2 }} icon="link" size="lg" />
                             </button>
                         </div>
                     </Tooltip>
                     <Tooltip title={<div>open linked trail</div>}>
                         <div className="documentButtonBar-button">
-                            <button style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleTrail}>
+                            <button type="button" style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleTrail}>
                                 <FontAwesomeIcon icon="taxi" size="lg" />
                             </button>
                         </div>
                     </Tooltip>
                 </div>
                 <div style={{ width: 25, height: 25 }}>
-                    <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
+                    <DocumentLinksButton View={this.view0} AlwaysOn InMenu StartLink />
                 </div>
             </div>
         );
@@ -134,8 +144,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                             key={icon.toString()}
                             size="sm"
                             icon={icon}
-                            onPointerEnter={action(e => (this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : '')))}
-                            onPointerLeave={action(e => (this.subEndLink = ''))}
+                            onPointerEnter={action(() => {
+                                this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : '');
+                            })}
+                            onPointerLeave={action(() => {
+                                this.subEndLink = '';
+                            })}
                             onClick={e => {
                                 this.view0 &&
                                     DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.Document, true, this.view0, {
@@ -157,7 +171,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                     {linkBtn(false, true, 'address-card')}
                     {linkBtn(true, true, 'id-card')}
                 </div>
-                <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
+                <DocumentLinksButton View={this.view0} AlwaysOn InMenu StartLink={false} />
             </div>
         );
     }
@@ -177,15 +191,16 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                             key={icon.toString()}
                             size="sm"
                             icon={icon}
-                            onPointerEnter={action(
-                                e =>
-                                    (this.subPin =
-                                        (pinLayoutView ? 'Layout' : '') +
-                                        (pinLayoutView && pinContentView ? ' &' : '') +
-                                        (pinContentView ? ' Content View' : '') +
-                                        (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : ''))
-                            )}
-                            onPointerLeave={action(e => (this.subPin = ''))}
+                            onPointerEnter={action(() => {
+                                this.subPin =
+                                    (pinLayoutView ? 'Layout' : '') +
+                                    (pinLayoutView && pinContentView ? ' &' : '') +
+                                    (pinContentView ? ' Content View' : '') +
+                                    (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : '');
+                            })}
+                            onPointerLeave={action(() => {
+                                this.subPin = '';
+                            })}
                             onClick={e => {
                                 const docs = this._props
                                     .views()
@@ -232,8 +247,8 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
     get shareButton() {
         const targetDoc = this.view0?.Document;
         return !targetDoc ? null : (
-            <Tooltip title={<div className="dash-tooltip">{'Open Sharing Manager'}</div>}>
-                <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
+            <Tooltip title={<div className="dash-tooltip">Open Sharing Manager</div>}>
+                <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={() => SharingManager.Instance.open(this.view0, targetDoc)}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="users" />
                 </div>
             </Tooltip>
@@ -244,7 +259,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
     get menuButton() {
         const targetDoc = this.view0?.Document;
         return !targetDoc ? null : (
-            <Tooltip title={<div className="dash-tooltip">{`Open Context Menu`}</div>}>
+            <Tooltip title={<div className="dash-tooltip">Open Context Menu</div>}>
                 <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => this.openContextMenu(e))}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
                 </div>
@@ -260,8 +275,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 <div
                     className="documentButtonBar-icon"
                     style={{ color: 'white' }}
-                    onClick={e => {
-                        console.log('hi: ', CalendarManager.Instance);
+                    onClick={() => {
                         CalendarManager.Instance.open(this.view0, targetDoc);
                     }}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon={faCalendarDays as IconLookup} />
@@ -282,7 +296,18 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                     style={{ backgroundColor: this._isRecording ? Colors.ERROR_RED : Colors.DARK_GRAY, color: Colors.WHITE }}
                     onPointerDown={action((e: React.PointerEvent) => {
                         this._isRecording = true;
-                        this._props.views().map(view => view && DocumentViewInternal.recordAudioAnnotation(view.dataDoc, view.LayoutFieldKey, stopFunc => (this._stopFunc = stopFunc), emptyFunction));
+                        this._props.views().map(
+                            view =>
+                                view &&
+                                DocumentViewInternal.recordAudioAnnotation(
+                                    view.dataDoc,
+                                    view.LayoutFieldKey,
+                                    stopFunc => {
+                                        this._stopFunc = stopFunc;
+                                    },
+                                    emptyFunction
+                                )
+                        );
                         const b = UndoManager.StartBatch('Recording');
                         setupMoveUpEvents(
                             this,
@@ -310,10 +335,10 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         if (this._dragRef.current) {
             const dragDocView = this.view0!;
             const dragData = new DragManager.DocumentDragData([dragDocView.Document]);
-            const [left, top] = dragDocView.screenToContentsTransform().inverse().transformPoint(0, 0);
+            const origin = dragDocView.screenToContentsTransform().inverse().transformPoint(0, 0);
             dragData.defaultDropAction = dropActionType.embed;
             dragData.canEmbed = true;
-            DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false });
+            DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, origin[0], origin[1], { hideSource: false });
             return true;
         }
         return false;
@@ -336,8 +361,19 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
     @computed
     get templateButton() {
         return !this.view0 ? null : (
-            <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embedding</div>} open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom">
-                <div className="documentButtonBar-linkFlyout" ref={this._dragRef} onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}>
+            <Tooltip
+                title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embedding</div>}
+                open={this._tooltipOpen}
+                onClose={action(() => {
+                    this._tooltipOpen = false;
+                })}
+                placement="bottom">
+                <div
+                    className="documentButtonBar-linkFlyout"
+                    ref={this._dragRef}
+                    onPointerEnter={action(() => {
+                        !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true);
+                    })}>
                     <Popup icon={<FaEdit />} popup={this.templateMenu} popupContainsPt={returnTrue} />
                 </div>
             </Tooltip>
@@ -365,17 +401,17 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
 
     @observable _captureEndLinkLayout = false;
     @action
-    captureEndLinkLayout = (e: React.PointerEvent) => {
+    captureEndLinkLayout = () => {
         this._captureEndLinkLayout = !this._captureEndLinkLayout;
     };
     @observable _captureEndLinkContent = false;
     @action
-    captureEndLinkContent = (e: React.PointerEvent) => {
+    captureEndLinkContent = () => {
         this._captureEndLinkContent = !this._captureEndLinkContent;
     };
 
     @action
-    captureEndLinkState = (e: React.PointerEvent) => {
+    captureEndLinkState = () => {
         this._captureEndLinkContent = this._captureEndLinkLayout = !this._captureEndLinkLayout;
     };
 
@@ -402,13 +438,15 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         return (
             <div className="documentButtonBar">
                 <div className="documentButtonBar-button">
-                    <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} ShowCount={true} />
+                    <DocumentLinksButton View={this.view0} AlwaysOn InMenu ShowCount />
                 </div>
                 {this._showLinkPopup ? (
                     <div style={{ position: 'absolute', zIndex: 1000 }}>
                         <LinkPopup
                             key="popup"
-                            linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this._props.views().lastElement()?.Document.onClick))}
+                            linkCreated={link => {
+                                link.link_displayLine = !IsFollowLinkScript(this._props.views().lastElement()?.Document.onClick);
+                            }}
                             linkCreateAnchor={() => this._props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
                             linkFrom={() => this._props.views().lastElement()?.Document}
                         />
@@ -423,7 +461,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 <div className="documentButtonBar-button">{this.pinButton}</div>
                 <div className="documentButtonBar-button">{this.recordButton}</div>
                 <div className="documentButtonBar-button">{this.calendarButton}</div>
-                {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
+                {!Doc.UserDoc().documentLinksButton_fullMenu ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
                 <div className="documentButtonBar-button">{this.menuButton}</div>
             </div>
         );
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 85e893e19..684b948af 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -70,7 +72,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
     constructor(props: EditableProps) {
         super(props);
         makeObservable(this);
-        this._editing = this._props.editing ? true : false;
+        this._editing = !!this._props.editing;
     }
 
     componentDidMount(): void {
@@ -166,7 +168,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
                     this._props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
                     break;
                 }
-
+            // eslint-disable-next-line no-fallthrough
             default:
                 if (this._props.textCallback?.(e.key)) {
                     e.stopPropagation();
@@ -186,7 +188,6 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
                 this._editing = true;
                 this._props.isEditingCallback?.(true);
             }
-            // e.stopPropagation();
         }
     };
 
@@ -223,6 +224,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
     renderEditor() {
         return this._props.autosuggestProps ? (
             <Autosuggest
+                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props.autosuggestProps.autosuggestProps}
                 inputProps={{
                     className: 'editableView-input',
@@ -241,12 +243,13 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
         ) : this._props.oneLine !== false && this._props.GetValue()?.toString().indexOf('\n') === -1 ? (
             <input
                 className="editableView-input"
-                ref={r => (this._inputref = r)}
+                ref={r => { this._inputref = r; }} // prettier-ignore
                 style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minWidth: 20, background: this._props.background }}
                 placeholder={this._props.placeholder}
                 onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
                 defaultValue={this._props.GetValue()}
-                autoFocus={true}
+                // eslint-disable-next-line jsx-a11y/no-autofocus
+                autoFocus
                 onChange={this.onChange}
                 onKeyDown={this.onKeyDown}
                 onPointerDown={this.stopPropagation}
@@ -256,12 +259,13 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
         ) : (
             <textarea
                 className="editableView-input"
-                ref={r => (this._inputref = r)}
+                ref={r => { this._inputref = r; }} // prettier-ignore
                 style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this._props.background }}
                 placeholder={this._props.placeholder}
                 onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
                 defaultValue={this._props.GetValue()}
-                autoFocus={true}
+                // eslint-disable-next-line jsx-a11y/no-autofocus
+                autoFocus
                 onChange={this.onChange}
                 onKeyDown={this.onKeyDown}
                 onPointerDown={this.stopPropagation}
@@ -304,7 +308,10 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
                         fontStyle: this._props.fontStyle,
                         fontSize: this._props.fontSize,
                     }}>
-                    {this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : ''}
+                    {
+                        // eslint-disable-next-line react/jsx-props-no-spreading
+                        this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : ''
+                    }
                 </span>
             </div>
         );
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
index 6a5c2cb4c..3cb7848c2 100644
--- a/src/client/views/FieldsDropdown.tsx
+++ b/src/client/views/FieldsDropdown.tsx
@@ -64,7 +64,7 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
         return (
             <Select
                 styles={{
-                    control: (baseStyles, state) => ({
+                    control: (baseStyles /* , state */) => ({
                         ...baseStyles,
                         minHeight: '5px',
                         maxHeight: '30px',
@@ -73,17 +73,17 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
                         padding: 0,
                         margin: 0,
                     }),
-                    singleValue: (baseStyles, state) => ({
+                    singleValue: (baseStyles /* , state */) => ({
                         ...baseStyles,
                         color: SettingsManager.userColor,
                         background: SettingsManager.userBackgroundColor,
                     }),
-                    placeholder: (baseStyles, state) => ({
+                    placeholder: (baseStyles /* , state */) => ({
                         ...baseStyles,
                         color: SettingsManager.userColor,
                         background: SettingsManager.userBackgroundColor,
                     }),
-                    input: (baseStyles, state) => ({
+                    input: (baseStyles /* , state */) => ({
                         ...baseStyles,
                         padding: 0,
                         margin: 0,
@@ -95,7 +95,7 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
                         color: SettingsManager.userColor,
                         background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor,
                     }),
-                    menuList: (baseStyles, state) => ({
+                    menuList: (baseStyles /* , state */) => ({
                         ...baseStyles,
                         backgroundColor: SettingsManager.userBackgroundColor,
                     }),
@@ -106,12 +106,14 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
                 onChange={val => this._props.selectFunc((val as any as { value: string; label: string }).value)}
                 onKeyDown={e => {
                     if (e.key === 'Enter') {
-                        runInAction(() => this._props.selectFunc((this._newField = (e.nativeEvent.target as any)?.value)));
+                        runInAction(() => {
+                            this._props.selectFunc((this._newField = (e.nativeEvent.target as any)?.value));
+                        });
                     }
                     e.stopPropagation();
                 }}
                 onMenuClose={this._props.menuClose}
-                closeMenuOnSelect={true}
+                closeMenuOnSelect
                 value={this._props.showPlaceholder ? null : undefined}
             />
         );
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index d3cebd1c3..be307600c 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/jsx-props-no-spreading */
 /* 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';
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 5c923d301..ebd61db7d 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -12,7 +12,7 @@ import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
 import { SnappingManager } from '../util/SnappingManager';
-import { UndoManager } from '../util/UndoManager';
+import { UndoManager, undoable } from '../util/UndoManager';
 import { ContextMenu } from './ContextMenu';
 import { DocumentDecorations } from './DocumentDecorations';
 import { InkStrokeProperties } from './InkStrokeProperties';
@@ -27,15 +27,13 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { AnchorMenu } from './pdf/AnchorMenu';
 
 const modifiers = ['control', 'meta', 'shift', 'alt'];
-type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo;
 type KeyControlInfo = {
     preventDefault: boolean;
     stopPropagation: boolean;
 };
-
-export let CtrlKey = false;
+type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo;
 export class KeyManager {
-    public static Instance: KeyManager = new KeyManager();
+    public static Instance = new KeyManager();
     private router = new Map<string, KeyHandler>();
 
     constructor() {
@@ -49,8 +47,8 @@ export class KeyManager {
         this.router.set('1000', this.shift);
     }
 
-    public unhandle = action((e: KeyboardEvent) => {
-        e.key === 'Control' && (CtrlKey = false);
+    public unhandle = action((/* e: KeyboardEvent */) => {
+        /* empty */
     });
     public handleModifiers = action((e: KeyboardEvent) => {
         if (e.shiftKey) SnappingManager.SetShiftKey(true);
@@ -65,10 +63,9 @@ export class KeyManager {
 
     public handle = action((e: KeyboardEvent) => {
         // 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 (FormattedTextBox.SelectOnLoadChar) FormattedTextBox.SelectOnLoadChar += e.key === 'Enter' ? '\n' : e.key;
         const keyname = e.key && e.key.toLowerCase();
-        this.handleGreedy(keyname);
+        this.handleGreedy(/* keyname */);
 
         if (modifiers.includes(keyname)) {
             return;
@@ -101,20 +98,15 @@ export class KeyManager {
             case 'u':
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
                     const ungroupings = SelectionManager.Views;
-                    UndoManager.RunInBatch(() => ungroupings.forEach(dv => { dv.layoutDoc.group = undefined; }), 'ungroup');
+                    undoable(() => () => ungroupings.forEach(dv => { dv.layoutDoc.group = undefined; }), 'ungroup');
                     SelectionManager.DeselectAll();
                 }
                 break;
             case 'g':
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
                     const selected = SelectionManager.Views;
-                    const collectionView = selected.reduce((col, dv) => (col === null || dv.CollectionFreeFormView === col ? dv.CollectionFreeFormView : undefined), null as null | undefined | CollectionFreeFormView);
-                    if (collectionView) {
-                        UndoManager.RunInBatch(() =>
-                            collectionView._marqueeViewRef.current?.collection(e, true, SelectionManager.Docs)
-                        , 'grouping');
-                        break;
-                    }
+                    const cv = selected.reduce((col, dv) => (!col || dv.CollectionFreeFormView === col ? dv.CollectionFreeFormView : undefined), undefined as undefined | CollectionFreeFormView);
+                    cv && undoable(() => cv._marqueeViewRef.current?.collection(e, true, SelectionManager.Docs), 'grouping');
                 }
                 break;
             case ' ':
@@ -146,7 +138,7 @@ export class KeyManager {
                     // DictationManager.Controls.stop();
                     GoogleAuthenticationManager.Instance.cancel();
                     SharingManager.Instance.close();
-                    if (!GroupManager.Instance.isOpen) SettingsManager.Instance.close();
+                    if (!GroupManager.Instance.isOpen) SettingsManager.Instance.closeMgr();
                     GroupManager.Instance.close();
                     window.getSelection()?.empty();
                     document.body.focus();
@@ -186,17 +178,18 @@ export class KeyManager {
             case 'arrowdown':  return this.nudge(0, 10, 'nudge down');
             case 'u' : 
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
-                    UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => (doc.group = undefined)), 'unggroup');
+                    UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => {doc.group = undefined}), 'unggroup');
                     SelectionManager.DeselectAll();
                 }
                 break;
             case 'g':
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
                     const randomGroup = random(0, 1000);
-                    UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => (doc.group = randomGroup)), 'group');
+                    UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => {doc.group = randomGroup}), 'group');
                     SelectionManager.DeselectAll();
                 }
                 break;
+            default: 
         } // prettier-ignore
 
         return {
@@ -212,8 +205,9 @@ export class KeyManager {
         switch (keyname) {
             case 'ƒ':
             case 'f':
-                const dv = SelectionManager.Views?.[0];
-                UndoManager.RunInBatch(() => dv.CollectionFreeFormDocumentView?.float(), 'float');
+                UndoManager.RunInBatch(() => SelectionManager.Views?.[0]?.CollectionFreeFormDocumentView?.float(), 'float');
+                break;
+            default:
         }
 
         return {
@@ -248,15 +242,19 @@ export class KeyManager {
                 PromiseValue(Cast(Doc.UserDoc()['tabs-button-tools'], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
                 break;
             case 'i':
-                const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports);
-                if (importBtn) {
-                    MainView.Instance.selectMenu(importBtn);
+                {
+                    const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports);
+                    if (importBtn) {
+                        MainView.Instance.selectMenu(importBtn);
+                    }
                 }
                 break;
             case 's':
-                const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyTrails);
-                if (trailsBtn) {
-                    MainView.Instance.selectMenu(trailsBtn);
+                {
+                    const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyTrails);
+                    if (trailsBtn) {
+                        MainView.Instance.selectMenu(trailsBtn);
+                    }
                 }
                 break;
             case 'f':
@@ -323,6 +321,7 @@ export class KeyManager {
                 }
                 preventDefault = false;
                 break;
+            default:
         }
 
         return {
@@ -357,6 +356,7 @@ export class KeyManager {
             case 'p':
                 Doc.ActiveTool = InkTool.Write;
                 break;
+            default:
         }
 
         return {
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 9f82fdb18..e04daec3b 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -46,6 +46,7 @@ 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 {
     static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
@@ -66,7 +67,9 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
         this._props.setContentViewBox?.(this);
         this._disposers.selfDisper = reaction(
             () => this._props.isSelected(), // react to stroke being deselected by turning off ink handles
-            selected => !selected && (InkStrokeProperties.Instance._controlButton = false)
+            selected => {
+                !selected && (InkStrokeProperties.Instance._controlButton = false);
+            }
         );
     }
     componentWillUnmount() {
@@ -168,7 +171,9 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
             }),
             isEditing,
             isEditing,
-            action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1))
+            action(() => {
+                wasSelected && (InkStrokeProperties.Instance._currentPoint = -1);
+            })
         );
     };
 
@@ -332,26 +337,28 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
     };
 
     _subContentView: ViewBoxInterface | undefined;
-    setSubContentView = (doc: ViewBoxInterface) => (this._subContentView = doc);
-    @computed get fillColor() {
+    setSubContentView = (doc: ViewBoxInterface) => {
+        this._subContentView = doc;
+    };
+    @computed get fillColor(): string {
         const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
         return isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FillColor) ?? 'transparent';
     }
     @computed get strokeColor() {
         const { inkData } = this.inkScaledData();
-        const fillColor = this.fillColor;
+        const { fillColor } = this;
         return !InkingStroke.IsClosed(inkData) && fillColor && fillColor !== 'transparent' ? fillColor : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) ?? StrCast(this.layoutDoc.color);
     }
     render() {
         TraceMobx();
-        const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();
+        const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY } = this.inkScaledData();
 
         const startMarker = StrCast(this.layoutDoc.stroke_startMarker);
         const endMarker = StrCast(this.layoutDoc.stroke_endMarker);
         const markerScale = NumCast(this.layoutDoc.stroke_markerScale, 1);
         const closed = InkingStroke.IsClosed(inkData);
         const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
-        const fillColor = this.fillColor;
+        const { fillColor } = this;
 
         // bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be.
         if (isInkMask && (this.layoutDoc._width !== Math.round(NumCast(this.layoutDoc._width)) || this.layoutDoc._height !== Math.round(NumCast(this.layoutDoc._height)))) {
@@ -392,7 +399,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
         );
         const higlightMargin = Math.min(12, Math.max(2, 0.3 * inkStrokeWidth));
         // Invisible polygonal line that enables the ink to be selected by the user.
-        const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, mask: boolean = false) =>
+        const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, mask: boolean = false): any =>
             InteractionUtils.CreatePolyline(
                 inkData,
                 inkLeft,
@@ -420,16 +427,24 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
         // 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
         // see: https://bibwild.wordpress.com/2019/06/10/bootstrap-3-to-4-changes-in-how-font-size-line-height-and-spacing-is-done-or-what-happened-to-line-height-computed/
-        const lineHeightGuess = +getComputedStyle(document.body).lineHeight.replace('px', '') / +getComputedStyle(document.body).fontSize.replace('px', '');
+        //  const lineHeightGuess = +getComputedStyle(document.body).lineHeight.replace('px', '') / +getComputedStyle(document.body).fontSize.replace('px', '');
         const interactions = {
-            onPointerLeave: action(() => (this._nearestScrPt = undefined)),
+            onPointerLeave: action(() => {
+                this._nearestScrPt = undefined;
+            }),
             onPointerMove: this._props.isSelected() ? this.onPointerMove : undefined,
             onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(),
             onContextMenu: () => {
                 const cm = ContextMenu.Instance;
                 !Doc.noviceMode && cm?.addItem({ description: 'Recognize Writing', event: this.analyzeStrokes, icon: 'paint-brush' });
                 cm?.addItem({ description: 'Toggle Mask', event: () => InkingStroke.toggleMask(this.dataDoc), icon: 'paint-brush' });
-                cm?.addItem({ description: 'Edit Points', event: action(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton)), icon: 'paint-brush' });
+                cm?.addItem({
+                    description: 'Edit Points',
+                    event: action(() => {
+                        InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton;
+                    }),
+                    icon: 'paint-brush',
+                });
             },
         };
         return (
@@ -441,6 +456,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
                         cursor: this._props.isSelected() ? 'default' : undefined,
                     }}
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...interactions}>
                     {clickableLine(this.onPointerDown, isInkMask)}
                     {isInkMask ? null : inkLine}
@@ -454,18 +470,19 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             width: NumCast(this.layoutDoc._width),
                             transform: `scale(${this._props.NativeDimScaling?.() || 1})`,
                             transformOrigin: 'top left',
-                            //top: (this._props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this._props.NativeDimScaling?.() || 1)) / 2,
+                            // top: (this._props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this._props.NativeDimScaling?.() || 1)) / 2,
                         }}>
                         <FormattedTextBox
+                            // eslint-disable-next-line react/jsx-props-no-spreading
                             {...this._props}
                             setHeight={undefined}
                             setContentViewBox={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
                             yPadding={10}
                             xPadding={10}
                             fieldKey="text"
-                            //dontRegisterView={true}
-                            noSidebar={true}
-                            dontScale={true}
+                            // dontRegisterView={true}
+                            noSidebar
+                            dontScale
                             isContentActive={this._props.isContentActive}
                         />
                     </div>
@@ -474,37 +491,6 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
         );
     }
 }
-
-export function SetActiveInkWidth(width: string): void {
-    !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
-}
-export function SetActiveBezierApprox(bezier: string): void {
-    ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier);
-}
-export function SetActiveInkColor(value: string) {
-    ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
-}
-export function SetActiveIsInkMask(value: boolean) {
-    ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
-}
-export function SetActiveInkHideTextLabels(value: boolean) {
-    ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value);
-}
-export function SetActiveFillColor(value: string) {
-    ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
-}
-export function SetActiveArrowStart(value: string) {
-    ActiveInkPen() && (ActiveInkPen().activeArrowStart = value);
-}
-export function SetActiveArrowEnd(value: string) {
-    ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value);
-}
-export function SetActiveArrowScale(value: number) {
-    ActiveInkPen() && (ActiveInkPen().activeArrowScale = value);
-}
-export function SetActiveDash(dash: string): void {
-    !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash);
-}
 export function ActiveInkPen(): Doc {
     return Doc.UserDoc();
 }
@@ -538,3 +524,34 @@ export function ActiveInkWidth(): number {
 export function ActiveInkBezierApprox(): string {
     return StrCast(ActiveInkPen()?.activeInkBezier);
 }
+
+export function SetActiveInkWidth(width: string): void {
+    !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
+}
+export function SetActiveBezierApprox(bezier: string): void {
+    ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier);
+}
+export function SetActiveInkColor(value: string) {
+    ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
+}
+export function SetActiveIsInkMask(value: boolean) {
+    ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
+}
+export function SetActiveInkHideTextLabels(value: boolean) {
+    ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value);
+}
+export function SetActiveFillColor(value: string) {
+    ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
+}
+export function SetActiveArrowStart(value: string) {
+    ActiveInkPen() && (ActiveInkPen().activeArrowStart = value);
+}
+export function SetActiveArrowEnd(value: string) {
+    ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value);
+}
+export function SetActiveArrowScale(value: number) {
+    ActiveInkPen() && (ActiveInkPen().activeArrowScale = value);
+}
+export function SetActiveDash(dash: string): void {
+    !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash);
+}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b0156846f..a13fe1cb7 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable node/no-unpublished-import */
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
 import * as far from '@fortawesome/free-regular-svg-icons';
@@ -6,9 +7,10 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+// eslint-disable-next-line import/no-relative-packages
 import '../../../node_modules/browndash-components/dist/styles/global.min.css';
 import { ClientUtils, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils';
-import { Utils, emptyFunction } from '../../Utils';
+import { emptyFunction } from '../../Utils';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { DocData } from '../../fields/DocSymbols';
 import { DocCast, StrCast } from '../../fields/Types';
@@ -625,8 +627,8 @@ export class MainView extends ObservableReactComponent<{}> {
     waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined);
     headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
     mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
-    addHeaderDoc = (doc: Doc | Doc[], annotationKey?: string) => (doc instanceof Doc ? [doc] : doc).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
-    removeHeaderDoc = (doc: Doc | Doc[], annotationKey?: string) => (doc instanceof Doc ? [doc] : doc).reduce((done, doc) => Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
+    addHeaderDoc = (docs: Doc | Doc[]) => (docs instanceof Doc ? [docs] : docs).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
+    removeHeaderDoc = (docs: Doc | Doc[]) => (docs instanceof Doc ? [docs] : docs).reduce((done, doc) => Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
     @computed get headerBarDocView() {
         return (
             <div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
@@ -734,8 +736,8 @@ export class MainView extends ObservableReactComponent<{}> {
         setupMoveUpEvents(
             this,
             e,
-            action(e => {
-                this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0);
+            action(ev => {
+                this._leftMenuFlyoutWidth = Math.max(ev.clientX - 58, 0);
                 return false;
             }),
             () => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(),
@@ -827,7 +829,7 @@ export class MainView extends ObservableReactComponent<{}> {
         if (willOpen) {
             switch ((this._panelContent = title)) {
                 case 'Settings':
-                    SettingsManager.Instance.open();
+                    SettingsManager.Instance.openMgr();
                     break;
                 case 'Help':
                     break;
@@ -907,19 +909,19 @@ export class MainView extends ObservableReactComponent<{}> {
         // setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
 
         this._sidebarContent.proto = DocCast(button.target);
-        SettingsManager.Instance.SetLastPressedBtn(button);
+        SettingsManager.Instance.LastPressedBtn = button;
     });
 
     closeFlyout = action(() => {
-        SettingsManager.Instance.SetLastPressedBtn(undefined);
+        SettingsManager.Instance.LastPressedBtn = undefined;
         this._panelContent = 'none';
         this._sidebarContent.proto = undefined;
         this._leftMenuFlyoutWidth = 0;
     });
 
-    remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
-    moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
-    addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true);
+    remButtonDoc = (docs: Doc | Doc[]) => (docs instanceof Doc ? [docs] : docs).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
+    moveButtonDoc = (docs: Doc | Doc[], targetCol: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(docs) && addDocument(docs);
+    addButtonDoc = (docs: Doc | Doc[]) => (docs instanceof Doc ? [docs] : docs).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true);
 
     buttonBarXf = () => {
         if (!this._docBtnRef.current) return Transform.Identity();
@@ -965,14 +967,16 @@ export class MainView extends ObservableReactComponent<{}> {
         return !dragPar?.layoutDoc.freeform_snapLines ? null : (
             <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'} />
-                    ))}
+                    {[
+                        ...SnappingManager.HorizSnapLines.map((l, i) => (
+                            // eslint-disable-next-line react/no-array-index-key
+                            <line key={'horiz' + 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={'vert' + i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
+                        )),
+                    ]}
                 </svg>
             </div>
         );
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
deleted file mode 100644
index 9626f1653..000000000
--- a/src/client/views/MetadataEntryMenu.tsx
+++ /dev/null
@@ -1,196 +0,0 @@
-import { IReactionDisposer, action, observable, reaction, runInAction } from 'mobx';
-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, FieldType } from '../../fields/Doc';
-import { undoBatch } from '../util/UndoManager';
-import './MetadataEntryMenu.scss';
-import { KeyValueBox } from './nodes/KeyValueBox';
-
-export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
-export interface MetadataEntryProps {
-    docs: Doc[];
-    onError?: () => boolean;
-}
-
-@observer
-export class MetadataEntryMenu extends React.Component<MetadataEntryProps> {
-    @observable private _currentKey: string = '';
-    @observable private _currentValue: string = '';
-    private _addChildren: boolean = false;
-    @observable _allSuggestions: string[] = [];
-    _suggestionDispser: IReactionDisposer | undefined;
-    private userModified = false;
-
-    private autosuggestRef = React.createRef<Autosuggest>();
-
-    @action
-    onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
-        this._currentKey = newValue;
-        if (!this.userModified) {
-            this.previewValue();
-        }
-    };
-
-    previewValue = async () => {
-        let field: FieldType | undefined | null = null;
-        let onProto: boolean = false;
-        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);
-            if (field === null) {
-                field = v;
-            } else if (v !== field) {
-                value = 'multiple values';
-            }
-        }
-        if (value === undefined) {
-            if (field !== null && field !== undefined) {
-                value = (onProto ? '' : '= ') + Field.toScriptString(field);
-            } else {
-                value = '';
-            }
-        }
-        const s = value;
-        runInAction(() => (this._currentValue = s));
-    };
-
-    @action
-    onValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this._currentValue = e.target.value;
-        this.userModified = e.target.value.trim() !== '';
-    };
-
-    @undoBatch
-    @action
-    onValueKeyDown = async (e: React.KeyboardEvent) => {
-        if (e.key === 'Enter') {
-            e.stopPropagation();
-            const script = KeyValueBox.CompileKVPScript(this._currentValue);
-            if (!script) return;
-
-            let childSuccess = true;
-            if (this._addChildren) {
-                for (const document of this.props.docs) {
-                    const collectionChildren = DocListCast(document.data);
-                    if (collectionChildren) {
-                        childSuccess = collectionChildren.every(c => KeyValueBox.ApplyKVPScript(c, this._currentKey, script));
-                    }
-                }
-            }
-            const success = this.props.docs.every(d => KeyValueBox.ApplyKVPScript(d, this._currentKey, script)) && childSuccess;
-            if (!success) {
-                if (this.props.onError) {
-                    if (this.props.onError()) {
-                        this.clearInputs();
-                    }
-                } else {
-                    this.clearInputs();
-                }
-            } else {
-                this.clearInputs();
-            }
-        }
-    };
-
-    @action
-    clearInputs = () => {
-        this._currentKey = '';
-        this._currentValue = '';
-        this.userModified = false;
-        if (this.autosuggestRef.current) {
-            const input: HTMLInputElement = (this.autosuggestRef.current as any).input;
-            input && input.focus();
-        }
-    };
-
-    getKeySuggestions = (value: string) => {
-        value = value.toLowerCase();
-        const docs = this.props.docs;
-        const keys = new Set<string>();
-        docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
-        return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
-    };
-    getSuggestionValue = (suggestion: string) => suggestion;
-
-    renderSuggestion = (suggestion: string) => {
-        return null;
-    };
-    componentDidMount() {
-        this._suggestionDispser = reaction(
-            () => this._currentKey,
-            () => (this._allSuggestions = this.getKeySuggestions(this._currentKey)),
-            { fireImmediately: true }
-        );
-    }
-    componentWillUnmount() {
-        this._suggestionDispser && this._suggestionDispser();
-    }
-
-    onClick = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this._addChildren = !this._addChildren;
-    };
-
-    private get considerChildOptions() {
-        if (!this.props.docs.every(doc => doc._type_collection !== undefined)) {
-            return null;
-        }
-        return (
-            <div style={{ display: 'flex' }}>
-                Children:
-                <input type="checkbox" onChange={this.onClick}></input>
-            </div>
-        );
-    }
-
-    _ref = React.createRef<HTMLInputElement>();
-    render() {
-        return (
-            <div className="metadataEntry-outerDiv" id="metadataEntry-outer" onPointerDown={e => e.stopPropagation()}>
-                <div className="metadataEntry-inputArea">
-                    <div style={{ display: 'flex', flexDirection: 'row' }}>
-                        <span>Key:</span>
-                        <div className="metadataEntry-autoSuggester" onClick={e => this.autosuggestRef.current!.input?.focus()}>
-                            <Autosuggest
-                                // @ts-ignore
-                                inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
-                                getSuggestionValue={this.getSuggestionValue}
-                                suggestions={emptyPath}
-                                alwaysRenderSuggestions={false}
-                                renderSuggestion={this.renderSuggestion}
-                                onSuggestionsFetchRequested={emptyFunction}
-                                onSuggestionsClearRequested={emptyFunction}
-                                ref={this.autosuggestRef}
-                            />
-                        </div>
-                    </div>
-                    <div style={{ display: 'flex', flexDirection: 'row' }}>
-                        <span>Value:</span>
-                        <input className="metadataEntry-input" ref={this._ref} value={this._currentValue} onClick={e => this._ref.current!.focus()} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
-                    </div>
-                    {this.considerChildOptions}
-                </div>
-                <div className="metadataEntry-keys">
-                    <ul>
-                        {this._allSuggestions
-                            .slice()
-                            .sort()
-                            .map(s => (
-                                <li
-                                    key={s}
-                                    onClick={action(() => {
-                                        this._currentKey = s;
-                                        this.previewValue();
-                                    })}>
-                                    {s}
-                                </li>
-                            ))}
-                    </ul>
-                </div>
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 6539c0834..960282a08 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -18,6 +18,7 @@ import { ObservableReactComponent } from './ObservableReactComponent';
 import './OverlayView.scss';
 import { DefaultStyleProvider } from './StyleProvider';
 import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
+
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export type OverlayDisposer = () => void;
@@ -82,12 +83,12 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
         this.height = Math.max(this.height, 30);
     };
 
-    onPointerUp = (e: PointerEvent) => {
+    onPointerUp = () => {
         document.removeEventListener('pointermove', this.onPointerMove);
         document.removeEventListener('pointerup', this.onPointerUp);
     };
 
-    onResizerPointerUp = (e: PointerEvent) => {
+    onResizerPointerUp = () => {
         document.removeEventListener('pointermove', this.onResizerPointerMove);
         document.removeEventListener('pointerup', this.onResizerPointerUp);
     };
@@ -171,7 +172,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
                 {contents}
             </OverlayWindow>
         );
-        this._elements.push(contents);
+        this._elements.push(wincontents);
         return remove;
     }
 
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index c7720d834..d83fea2a5 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.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/no-unused-class-component-methods */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Dropdown, DropdownType, IListItemProps, Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, observable } from 'mobx';
@@ -12,7 +15,7 @@ import { MdClosedCaption, MdClosedCaptionDisabled, MdGridOff, MdGridOn, MdSubtit
 import { RxWidth } from 'react-icons/rx';
 import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb';
 import { TfiBarChart } from 'react-icons/tfi';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, Opt } from '../../fields/Doc';
 import { DocData } from '../../fields/DocSymbols';
 import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, ScriptCast } from '../../fields/Types';
@@ -32,6 +35,7 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 
 @observer
 export class PropertiesButtons extends React.Component<{}, {}> {
+    // eslint-disable-next-line no-use-before-define
     @observable public static Instance: PropertiesButtons;
 
     @computed get selectedDoc() {
@@ -44,61 +48,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         return !SelectionManager.SelectedSchemaDoc && SelectionManager.Views.lastElement()?.topMost;
     }
 
-    propertyToggleBtn = (label: (on?: any) => string, property: string, tooltip: (on?: any) => string, icon: (on?: any) => any, onClick?: (dv: Opt<DocumentView>, doc: Doc, property: string) => void, useUserDoc?: boolean) => {
-        const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedLayoutDoc;
-        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])}
-                tooltip={tooltip(BoolCast(targetDoc[property]))}
-                text={label(targetDoc?.[property])}
-                color={SettingsManager.userColor}
-                icon={icon(targetDoc?.[property] as any)}
-                iconPlacement="left"
-                align="flex-start"
-                fillWidth={true}
-                toggleType={ToggleType.BUTTON}
-                onClick={undoable(() => {
-                    if (SelectionManager.Views.length > 1) {
-                        SelectionManager.Views.forEach(dv => (onClick ?? onPropToggle)(dv, dv.Document, property));
-                    } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
-                }, property)}
-            />
-        );
-    };
-
-    // this implments a container pattern by marking the targetDoc (collection) as a lightbox
-    // that always fits its contents to its container and that hides all other documents when
-    // a link is followed that targets a 'lightbox' destination
-    @computed get isLightboxButton() {
-        return this.propertyToggleBtn(
-            on => 'Lightbox',
-            'isLightbox',
-            on => `${on ? 'Set' : 'Remove'} lightbox flag`,
-            on => 'window-restore',
-            onClick => {
-                SelectionManager.Views.forEach(dv => {
-                    const containerDoc = dv.Document;
-                    //containerDoc.followAllLinks =
-                    // containerDoc.noShadow =
-                    // containerDoc.layout_disableBrushing =
-                    // containerDoc._forceActive =
-                    //containerDoc._freeform_fitContentsToBox =
-                    containerDoc._isLightbox = !containerDoc._isLightbox;
-                    //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
-                    const containerContents = DocListCast(dv.dataDoc[Doc.LayoutFieldKey(containerDoc)]);
-                    //dv.Docuemnt.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' });
-                    containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false)));
-                });
-            }
-        );
-    }
-
     @computed get titleButton() {
         return this.propertyToggleBtn(
-            on => (!on ? 'SHOW TITLE' : this.selectedDoc?.['_layout_showTitle'] === 'title:hover' ? 'HIDE TITLE' : 'HOVER TITLE'),
+            on => (!on ? 'SHOW TITLE' : this.selectedDoc?._layout_showTitle === 'title:hover' ? 'HIDE TITLE' : 'HOVER TITLE'),
             '_layout_showTitle',
-            on => 'Switch between title styles',
+            () => 'Switch between title styles',
             on => (on ? <MdSubtitlesOff /> : <MdSubtitles />), // {currentIcon}, //(on ?  <MdSubtitles/> :)  , //,'text-width', on ?  <MdSubtitles/> : <MdSubtitlesOff/>,
             (dv, doc) => {
                 const tdoc = dv?.Document || doc;
@@ -119,7 +73,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     }
 
     @computed get maskButton() {
-        //highlight text while going down and reading through
+        // highlight text while going down and reading through
         return this.propertyToggleBtn(
             on => (on ? 'PLAIN INK' : 'HIGHLIGHTER MASK'),
             'stroke_isInkMask',
@@ -132,10 +86,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @computed get hideImageButton() {
         // put in developer -- can trace on top of object and drawing is still there
         return this.propertyToggleBtn(
-            on => (on ? 'SHOW BACKGROUND IMAGE' : 'HIDE BACKGROUND IMAGE'), //'Background',
+            on => (on ? 'SHOW BACKGROUND IMAGE' : 'HIDE BACKGROUND IMAGE'), // 'Background',
             '_hideImage',
             on => (on ? 'Show Image' : 'Show Background'),
-            on => (on ? <BiShow /> : <BiHide />) //'portrait'
+            on => (on ? <BiShow /> : <BiHide />) // 'portrait'
         );
     }
 
@@ -144,35 +98,35 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             on => (on ? 'DISABLE CLUSTERS' : 'HIGHLIGHT CLUSTERS'),
             '_freeform_useClusters',
             on => `${on ? 'Hide' : 'Show'} clusters`,
-            on => <FaBraille />
+            () => <FaBraille />
         );
     }
     @computed get panButton() {
         return this.propertyToggleBtn(
-            on => (on ? 'ENABLE PANNING' : 'DISABLE PANNING'), //'Lock\xA0View',
+            on => (on ? 'ENABLE PANNING' : 'DISABLE PANNING'), // 'Lock\xA0View',
             '_lockedTransform',
             on => `${on ? 'Unlock' : 'Lock'} panning of view`,
-            on => (on ? <TbHandStop /> : <TbHandOff />) //'lock'
+            on => (on ? <TbHandStop /> : <TbHandOff />) // 'lock'
         );
     }
 
     @computed get forceActiveButton() {
-        //select text
+        // select text
         return this.propertyToggleBtn(
             on => (on ? 'SELECT TO INTERACT' : 'ALWAYS INTERACTIVE'),
             '_forceActive',
             on => `${on ? 'Document must be selected to interact with its contents' : 'Contents always active (respond to click/drag events)'} `,
-            on => <MdTouchApp /> // 'eye'
+            () => <MdTouchApp /> // 'eye'
         );
     }
 
     @computed get verticalAlignButton() {
-        //select text
+        // select text
         return this.propertyToggleBtn(
             on => (on ? 'ALIGN TOP' : 'ALIGN CENTER'),
             '_layout_centered',
             on => `${on ? 'Text is aligned with top of document' : 'Text is aligned with center of document'} `,
-            on => <MdTouchApp /> // 'eye'
+            () => <MdTouchApp /> // 'eye'
         );
     }
 
@@ -192,10 +146,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
 
     @computed get fitContentButton() {
         return this.propertyToggleBtn(
-            on => (on ? 'PREVIOUS VIEW' : 'VIEW ALL'), //'View All',
+            on => (on ? 'PREVIOUS VIEW' : 'VIEW ALL'), // 'View All',
             '_freeform_fitContentsToBox',
             on => `${on ? "Don't" : 'Do'} fit content to container visible area`,
-            on => (on ? <CiGrid31 /> : <BsGrid3X3GapFill />) //'object-group'
+            on => (on ? <CiGrid31 /> : <BsGrid3X3GapFill />) // 'object-group'
         );
     }
 
@@ -228,7 +182,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
 
     @computed get layout_fitWidthButton() {
         return this.propertyToggleBtn(
-            on => (on ? 'SCALED VIEW' : 'READING VIEW'), //'Fit\xA0Width',
+            on => (on ? 'SCALED VIEW' : 'READING VIEW'), // 'Fit\xA0Width',
             '_layout_fitWidth',
             on =>
                 on
@@ -240,12 +194,14 @@ export class PropertiesButtons extends React.Component<{}, {}> {
 
     @computed get captionButton() {
         return this.propertyToggleBtn(
-            //DEVELOPER
-            on => (on ? 'HIDE CAPTION' : 'SHOW CAPTION'), //'Caption',
+            // DEVELOPER
+            on => (on ? 'HIDE CAPTION' : 'SHOW CAPTION'), // 'Caption',
             '_layout_showCaption',
             on => `${on ? 'Hide' : 'Show'} caption footer`,
-            on => (on ? <MdClosedCaptionDisabled /> : <MdClosedCaption />), //'closed-captioning',
-            (dv, doc) => ((dv?.Document || doc)._layout_showCaption = (dv?.Document || doc)._layout_showCaption === undefined ? 'caption' : undefined)
+            on => (on ? <MdClosedCaptionDisabled /> : <MdClosedCaption />), // 'closed-captioning',
+            (dv, doc) => {
+                (dv?.Document || doc)._layout_showCaption = (dv?.Document || doc)._layout_showCaption === undefined ? 'caption' : undefined;
+            }
         );
     }
 
@@ -256,7 +212,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             '_chromeHidden',
             on => `${on ? 'Show' : 'Hide'} editing UI`,
             on => (on ? <TbEditCircle /> : <TbEditCircleOff />), // 'edit',
-            (dv, doc) => ((dv?.Document || doc)._chromeHidden = !(dv?.Document || doc)._chromeHidden)
+            (dv, doc) => {
+                (dv?.Document || doc)._chromeHidden = !(dv?.Document || doc)._chromeHidden;
+            }
         );
     }
 
@@ -265,8 +223,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         return this.propertyToggleBtn(
             on => (on ? 'AUTO\xA0SIZE' : 'FIXED SIZE'),
             '_layout_autoHeight',
-            on => `Automatical vertical sizing to show all content`,
-            on => <FontAwesomeIcon icon="arrows-alt-v" size="lg" />
+            () => `Automatical vertical sizing to show all content`,
+            () => <FontAwesomeIcon icon="arrows-alt-v" size="lg" />
         );
     }
 
@@ -274,8 +232,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         return this.propertyToggleBtn(
             on => (on ? 'HIDE GRID' : 'DISPLAY GRID'),
             '_freeform_backgroundGrid',
-            on => `Display background grid in collection`,
-            on => (on ? <MdGridOff /> : <MdGridOn />) //'border-all'
+            () => `Display background grid in collection`,
+            on => (on ? <MdGridOff /> : <MdGridOn />) // 'border-all'
         );
     }
 
@@ -317,8 +275,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         return this.propertyToggleBtn(
             on => (on ? 'HIDE SNAP LINES' : 'SHOW SNAP LINES'),
             'freeform_snapLines',
-            on => `Display snapping lines when objects are dragged`,
-            on => <TfiBarChart />, //'th',
+            () => `Display snapping lines when objects are dragged`,
+            () => <TfiBarChart />, // 'th',
             undefined
         );
     }
@@ -361,7 +319,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @undoBatch
     handlePerspectiveChange = (e: any) => {
         this.selectedDoc && (this.selectedDoc._type_collection = e.target.value);
-        SelectionManager.Views.forEach(docView => (docView.layoutDoc._type_collection = e.target.value));
+        SelectionManager.Views.forEach(docView => {
+            docView.layoutDoc._type_collection = e.target.value;
+        });
     };
     @computed get onClickVal() {
         const linkButton = IsFollowLinkScript(this.selectedDoc.onClick);
@@ -369,10 +329,10 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
 
         if (followLoc === OpenWhere.lightbox && !linkedToLightboxView()) return 'linkInPlace';
-        else if (linkButton && followLoc === OpenWhere.addRight) return 'linkOnRight';
-        else if (linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView()) return 'enterPortal';
-        else if (ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail')) return 'toggleDetail';
-        else return 'nothing';
+        if (linkButton && followLoc === OpenWhere.addRight) return 'linkOnRight';
+        if (linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView()) return 'enterPortal';
+        if (ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail')) return 'toggleDetail';
+        return 'nothing';
     }
 
     @computed
@@ -385,20 +345,18 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             ['linkOnRight', 'Open Link on Right'],
         ];
 
-        const items: IListItemProps[] = buttonList.map(value => {
-            return {
-                text: value[1],
-                val: value[1],
-            };
-        });
+        const items: IListItemProps[] = buttonList.map(value => ({
+            text: value[1],
+            val: value[1],
+        }));
         return !this.selectedDoc ? null : (
             <Dropdown
-                tooltip={'Choose onClick behavior'}
+                tooltip="Choose onClick behavior"
                 items={items}
-                closeOnSelect={true}
+                closeOnSelect
                 selectedVal={this.onClickVal}
                 setSelectedVal={val => this.handleOptionChange(val as string)}
-                title={'Choose onClick behaviour'}
+                title="Choose onClick behaviour"
                 color={SettingsManager.userColor}
                 dropdownType={DropdownType.SELECT}
                 type={Type.SEC}
@@ -440,16 +398,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                     docView.toggleFollowLink(false, false);
                     docView.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined;
                     break;
+                default:
             }
         });
     };
 
-    @undoBatch
-    editOnClickScript = () => {
-        if (SelectionManager.Views.length) SelectionManager.Views.forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, undefined, 'onClick'));
-        else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick');
-    };
-
     @computed
     get onClickFlyout() {
         const buttonList = [
@@ -473,6 +426,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 case 'enterPortal': active = linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView(); break;
                 case 'toggleDetail':active = ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail'); break;
                 case 'nothing':     active = !linkButton && this.selectedDoc.onClick === undefined;break;
+                default:
             }
             return (
                 <div className="list-item" key={`${value}`} style={{ backgroundColor: active ? Colors.LIGHT_BLUE : undefined }} onClick={click}>
@@ -494,6 +448,36 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             </div>
         );
     }
+    @undoBatch
+    editOnClickScript = () => {
+        if (SelectionManager.Views.length) SelectionManager.Views.forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, undefined, 'onClick'));
+        else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick');
+    };
+
+    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];
+        };
+        return !targetDoc ? null : (
+            <Toggle
+                toggleStatus={BoolCast(targetDoc[property])}
+                tooltip={tooltip(BoolCast(targetDoc[property]))}
+                text={label(targetDoc?.[property])}
+                color={SettingsManager.userColor}
+                icon={icon(targetDoc?.[property] as any)}
+                iconPlacement="left"
+                align="flex-start"
+                fillWidth
+                toggleType={ToggleType.BUTTON}
+                onClick={undoable(() => {
+                    if (SelectionManager.Views.length > 1) {
+                        SelectionManager.Views.forEach(dv => (onClick ?? onPropToggle)(dv, dv.Document, property));
+                    } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
+                }, property)}
+            />
+        );
+    };
 
     render() {
         const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)];
@@ -505,11 +489,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         const isStacking = [CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.NoteTaking].includes(this.selectedDoc?._type_collection as any);
         const isFreeForm = this.selectedDoc?._type_collection === CollectionViewType.Freeform;
         const isTree = this.selectedDoc?._type_collection === CollectionViewType.Tree;
-        const isTabView = this.selectedTabView;
         const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) => (
             <div className="propertiesButtons-button" style={style}>
-                {' '}
-                {ele}{' '}
+                {ele}
             </div>
         );
         const isNovice = Doc.noviceMode;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 2a847e51a..8ac0f7d7b 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -11,8 +11,8 @@ import { IReactionDisposer, action, computed, makeObservable, observable, reacti
 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 { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../ClientUtils';
+import * as Icons from 'react-icons/bs'; // {BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
+import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, 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';
@@ -45,6 +45,7 @@ import { DocumentView, OpenWhere } from './nodes/DocumentView';
 import { StyleProviderFuncType } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
+
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface PropertiesViewProps {
@@ -218,7 +219,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
             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={returnEmptyString} SetValue={this.setKeyValue} />
                 </div>
             );
         }
@@ -264,13 +265,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @observable transform: Transform = Transform.Identity();
     getTransform = () => this.transform;
     propertiesDocViewRef = (ref: HTMLDivElement) => {
-        const observer = new _global.ResizeObserver(
+        const resizeObserver = new _global.ResizeObserver(
             action(() => {
                 const cliRect = ref.getBoundingClientRect();
                 this.transform = new Transform(-cliRect.x, -cliRect.y, 1);
             })
         );
-        ref && observer.observe(ref);
+        ref && resizeObserver.observe(ref);
     };
 
     @computed get contexts() {
@@ -359,9 +360,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         if (permission === '-multiple-') dropdownValues.unshift(permission);
         return (
             <select className="propertiesView-permissions-select" value={permission} onChange={e => this.changePermissions(e, user)}>
-                {dropdownValues.map(permission => (
-                    <option className="propertiesView-permisssions-select" key={permission} value={permission}>
-                        {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
+                {dropdownValues.map(permissionVal => (
+                    <option className="propertiesView-permisssions-select" key={permissionVal} value={permissionVal}>
+                        {concat(ReverseHierarchyMap.get(permissionVal)?.image, ' ', permissionVal)}
                     </option>
                 ))}
             </select>
@@ -404,10 +405,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     /**
      * @returns a row of the permissions panel
      */
-    sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) {
-        if (name === ClientUtils.CurrentUserEmail()) {
-            name = 'Me';
-        }
+    sharingItem(nameIn: string, admin: boolean, permission: string, showExpansionIcon?: boolean) {
+        const name = nameIn === ClientUtils.CurrentUserEmail() ? 'Me' : nameIn;
         return (
             <div
                 className="propertiesView-sharingTable-item"
@@ -466,7 +465,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         const docs = SelectionManager.Views.length < 2 && this.selectedDoc ? [this.selectedDoc] : SelectionManager.Views.map(docView => docView.Document);
         const target = docs[0];
 
-        const showAdmin = GetEffectiveAcl(target) == AclAdmin;
+        const showAdmin = GetEffectiveAcl(target) === AclAdmin;
         const individualTableEntries = [];
         const usersAdded: string[] = []; // all shared users being added - organized by denormalized email
 
@@ -841,7 +840,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     colorButton(value: string, type: string, setter: () => void) {
         return (
-            <div className="color-button" key="color" onPointerDown={action(e => setter())}>
+            <div className="color-button" key="color" onPointerDown={action(() => setter())}>
                 <div
                     className="color-button-preview"
                     style={{
@@ -858,9 +857,11 @@ 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((col: ColorResult) => setter(col.hex)),
+                    'set stroke color property'
+                )}
                 presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
                 color={color}
             />
@@ -928,13 +929,6 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         this.selectedDoc && (this.selectedDoc[DocData].stroke_endMarker = value);
     }
 
-    @computed get stkInput() {
-        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); }); // prettier-ignore
-    }
-
     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)} />
@@ -1640,7 +1634,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                         <div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}>
                             <p>Zoom %</p>
                             <div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'inline-flex' }}>
-                                <input className="presBox-input" style={{ width: '100%', color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} readOnly={true} type="number" value={zoom} />
+                                <input className="presBox-input" style={{ width: '100%', color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }} readOnly 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" />
@@ -1766,7 +1760,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                                     color: SettingsManager.userColor,
                                     backgroundColor: this.openPresTransitions ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
                                 }}>
-                                &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={'rocket'} /> &nbsp; Transitions
+                                &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>
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index db3f29fae..6195dcde8 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -197,7 +199,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                 </div>
             );
         };
-        const renderMeta = (tag: string, dflt: FieldResult<FieldType>) => {
+        const renderMeta = (tag: string) => {
             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)}>
@@ -228,13 +230,12 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                 <div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight() }} onWheel={e => e.stopPropagation()}>
                     {this.allUsers.length > 1 ? this.allUsers.map(renderUsers) : null}
                     {this.allHashtags.map(renderTag)}
-                    {Array.from(this.allMetadata.keys())
-                        .sort()
-                        .map(key => renderMeta(key, this.allMetadata.get(key)))}
+                    {Array.from(this.allMetadata.keys()).sort().map(renderMeta)}
                 </div>
 
                 <div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
                     <CollectionStackingView
+                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
                         setContentViewBox={emptyFunction}
                         NativeWidth={returnZero}
@@ -248,11 +249,11 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                         isAnnotationOverlay={false}
                         select={emptyFunction}
                         NativeDimScaling={returnOne}
-                        //childlayout_showTitle={this.layout_showTitle}
+                        // childlayout_showTitle={this.layout_showTitle}
                         isAnyChildContentActive={returnFalse}
                         childDocumentsActive={this._props.isContentActive}
                         whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                        childHideDecorationTitle={true}
+                        childHideDecorationTitle
                         removeDocument={this.removeDocument}
                         moveDocument={this.moveDocument}
                         addDocument={this.addDocument}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index d8adfa68a..75f1a7d80 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable jsx-a11y/no-static-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 { Tooltip } from '@mui/material';
@@ -74,11 +77,10 @@ function togglePaintView(e: React.MouseEvent, doc: Opt<Doc>, props: Opt<FieldVie
 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) => {
+    const replacer = (match: any, expr: string) =>
         // bcz: this executes a script to convert a property expression string:  { script }  into a value
-        return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? '';
-    };
-    divKeys.map((prop: string) => {
+        ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? '';
+    divKeys.forEach((prop: string) => {
         const p = (props as any)[prop];
         typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
     });
@@ -105,20 +107,18 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
     const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent');
     const isAnnotated = property.includes(':annotated');
     const layoutDoc = doc ? Doc.Layout(doc) : doc;
-    const isInk = () => layoutDoc?._layout_isSvg && !props?.LayoutTemplateString;
     const isOpen = property.includes(':open');
-    const isEmpty = property.includes(':empty');
     const boxBackground = property.includes(':box');
     const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : '';
+    const isInk = () => layoutDoc?._layout_isSvg && !props?.LayoutTemplateString;
     const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
     const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight);
     const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1));
-    const color = () => props?.styleProvider?.(doc, props, StyleProp.Color);
     const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
-    const layout_showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
+    const layoutShowTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
     // prettier-ignore
     switch (property.split(':')[0]) {
-        case StyleProp.TreeViewIcon:
+        case StyleProp.TreeViewIcon: {
             const img = ImageCast(doc?.icon, ImageCast(doc?.data));
             if (img) {
                 const ext = extname(img.url.href);
@@ -126,13 +126,15 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 return <img src={url} width={20} height={15} style={{ margin: 'auto', display: 'block', objectFit: 'contain' }} />;
             }
             return Doc.toIcon(doc, isOpen);
-        case StyleProp.TreeViewSortings:
+        }
+        case StyleProp.TreeViewSortings: {
             const allSorts: { [key: string]: { color: string; icon: JSX.Element | string } | undefined } = {};
             allSorts[TreeSort.AlphaDown] = { color: Colors.MEDIUM_BLUE, icon: <BsArrowDown/> };
             allSorts[TreeSort.AlphaUp] = { color: 'crimson', icon: <BsArrowUp/> };
             if (doc?._type_collection === CollectionViewType.Freeform) allSorts[TreeSort.Zindex] = { color: 'green', icon: 'Z' };
             allSorts[TreeSort.WhenAdded] = { color: 'darkgray', icon: <BsArrowDownUp/> };
             return allSorts;
+        }
         case StyleProp.Highlighting:
             if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined;            
             if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
@@ -178,7 +180,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                     )) ||
                 ''
             );
-        case StyleProp.Color:
+        case StyleProp.Color: {
             if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userBackgroundColor;
             if (Doc.IsSystem(doc!)) return SettingsManager.userColor;
             if (doc?.type === DocumentType.FONTICON) return SettingsManager.userColor;
@@ -186,30 +188,33 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             if (docColor) return docColor;
             const backColor = backgroundCol();
             return backColor ? lightOrDark(backColor) : undefined;
-        case StyleProp.BorderRounding:
+        }
+        case StyleProp.BorderRounding: {
             const rounding = StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
             return (doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc || doc?.isTemplateDoc) ? StrCast(doc._layout_borderRounding,rounding) : rounding;
-        case StyleProp.BorderPath:
+        }
+        case StyleProp.BorderPath: {
             const borderPath = Doc.IsComicStyle(doc) &&
                 props?.renderDepth &&
                 !doc?.layout_isSvg && { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 };
-            return !borderPath
-                ? null
-                : {
-                      clipPath: `path('${borderPath.path}')`,
-                      jsx: (
-                          <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
-                              <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${props.PanelWidth()} ${props.PanelHeight()}`}>
-                                  <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
-                              </svg>
-                          </div>
-                      ),
-                  };
+                return !borderPath
+                    ? null
+                    : {
+                        clipPath: `path('${borderPath.path}')`,
+                        jsx: (
+                            <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
+                                <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${props.PanelWidth()} ${props.PanelHeight()}`}>
+                                    <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
+                                </svg>
+                            </div>
+                        ),
+                    };
+        }
         case StyleProp.HeaderMargin:
             return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._type_collection as any) ||
-                (doc?.type === DocumentType.RTF && !layout_showTitle()?.includes('noMargin')) ||
+                (doc?.type === DocumentType.RTF && !layoutShowTitle()?.includes('noMargin')) ||
                 doc?.type === DocumentType.LABEL) &&
-                layout_showTitle() &&
+                layoutShowTitle() &&
                 !StrCast(doc?.layout_showTitle).includes(':hover')
                 ? titleHeight()
                 : 0;
@@ -248,7 +253,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                             (Colors.DARK_GRAY)
                             : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
                     break;
-                //if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
+                // if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
                 default: docColor = docColor || (Colors.WHITE);
             }
             if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && props?.styleProvider) {
@@ -273,6 +278,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
 
                 case DocumentType.LABEL:
                     if (doc?.annotationOn !== undefined) return 'black 2px 2px 1px';
+                // eslint-disable-next-line no-fallthrough
                 default:
                     return doc.z
                         ? `#9c9396  ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
@@ -299,7 +305,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 isGroup )? undefined: 'all' 
             if (props?.isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
-        case StyleProp.Decorations:
+        case StyleProp.Decorations: {
             const lock = () => doc?.pointerEvents !== 'none' ? null : (
                         <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
                             <FontAwesomeIcon icon='lock' size="lg" />
@@ -316,16 +322,17 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                     StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
                         ? 'green' // #18c718bd' //'hasFilter'
                         : props?.childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || props?.childFiltersByRanges().length
-                        ? 'orange' //'inheritsFilter'
+                        ? 'orange' // 'inheritsFilter'
                         : undefined;
                 return !showFilterIcon ? null : (
                     <div className="styleProvider-filter">
                         <Dropdown
                             type={Type.TERT}
                             dropdownType={DropdownType.CLICK}
-                        fillWidth
-                            iconProvider={(active:boolean) => <div className='styleProvider-filterShift'><FaFilter/></div>}
-                            closeOnSelect={true}
+                            fillWidth
+                            // eslint-disable-next-line react/no-unstable-nested-components
+                            iconProvider={() => <div className='styleProvider-filterShift'><FaFilter/></div>}
+                            closeOnSelect
                             setSelectedVal={
                                 action((dv) => {
                                     (dv as any).select(false);
@@ -378,6 +385,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                     {audio()}
                 </>
             );
+        }
+        default:
     }
 }
 
diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx
index 068143225..2d461c0ab 100644
--- a/src/client/views/UndoStack.tsx
+++ b/src/client/views/UndoStack.tsx
@@ -1,6 +1,7 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { Tooltip } from '@mui/material';
 import { Popup, Type } from 'browndash-components';
-import { observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { StrCast } from '../../fields/Types';
@@ -15,19 +16,19 @@ export class UndoStack extends React.Component<UndoStackProps> {
         const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.userVariantColor;
         const color = UndoManager.batchCounter.get() ? 'black' : SettingsManager.userColor;
         return (
-            <Tooltip title={'undo stack (if it stays yellow, undo is broken - you should reload Dash)'}>
+            <Tooltip title="undo stack (if it stays yellow, undo is broken - you should reload Dash)">
                 <div>
                     <div className="undoStack-outerContainer">
                         <Popup
                             text="stack"
                             color={color}
                             background={background}
-                            placement={`top-start`}
+                            placement="top-start"
                             type={Type.TERT}
                             popup={
                                 <div
                                     className="undoStack-commandsContainer"
-                                    ref={r => r?.scroll({ behavior: 'auto', top: r?.scrollHeight + 20 })}
+                                    ref={r => r?.scroll({ behavior: 'auto', top: (r?.scrollHeight ?? 0) + 20 })}
                                     style={{
                                         background,
                                         color,
@@ -35,12 +36,13 @@ export class UndoStack extends React.Component<UndoStackProps> {
                                     {Array.from(UndoManager.undoStackNames).map((name, i) => (
                                         <div
                                             className="undoStack-resultContainer"
+                                            // eslint-disable-next-line react/no-array-index-key
                                             key={i}
-                                            onClick={e => {
+                                            onClick={() => {
                                                 const size = UndoManager.undoStackNames.length;
                                                 for (let n = 0; n < size - i; n++) UndoManager.Undo();
                                             }}>
-                                            <div className="undoStack-commandString">{StrCast(name).replace(/[^\.]*\./, '')}</div>
+                                            <div className="undoStack-commandString">{StrCast(name).replace(/[^.]*\./, '')}</div>
                                         </div>
                                     ))}
                                     {Array.from(UndoManager.redoStackNames)
@@ -48,12 +50,13 @@ export class UndoStack extends React.Component<UndoStackProps> {
                                         .map((name, i) => (
                                             <div
                                                 className="undoStack-resultContainer"
+                                                // eslint-disable-next-line react/no-array-index-key
                                                 key={i}
-                                                onClick={e => {
+                                                onClick={() => {
                                                     for (let n = 0; n <= i; n++) UndoManager.Redo();
                                                 }}>
                                                 <div className="undoStack-commandString" style={{ fontWeight: 'bold', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
-                                                    {StrCast(name).replace(/[^\.]*\./, '')}
+                                                    {StrCast(name).replace(/[^.]*\./, '')}
                                                 </div>
                                             </div>
                                         ))}
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index c3e513154..f03e316a2 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -9,6 +9,7 @@ import { Utils, emptyFunction } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { BoolCast, NumCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
+// eslint-disable-next-line import/extensions
 import clamp from '../../util/clamp';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { FieldViewProps } from '../nodes/FieldView';
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 490a14be5..1e4ed74be 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -73,7 +73,7 @@ export class Track extends ObservableReactComponent<IProps> {
         this._timelineVisibleReaction?.();
         this._autoKfReaction?.();
     }
-    ////////////////////////////////
+    // //////////////////////////////
 
     getLastRegionTime = () => {
         let lastTime: number = 0;
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 2d906dfe7..8803f6f79 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -1,10 +1,13 @@
+/* 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, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
 import { emptyFunction, numberRange } from '../../../Utils';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
@@ -52,13 +55,19 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
     @observable private collapsed: boolean = false;
     @observable private _paletteOn = false;
     private set _heading(value: string) {
-        runInAction(() => this._props.headingObject && (this._props.headingObject.heading = this.heading = value));
+        runInAction(() => {
+            this._props.headingObject && (this._props.headingObject.heading = this.heading = value);
+        });
     }
     private set _color(value: string) {
-        runInAction(() => this._props.headingObject && (this._props.headingObject.color = this.color = value));
+        runInAction(() => {
+            this._props.headingObject && (this._props.headingObject.color = this.color = value);
+        });
     }
     private set _collapsed(value: boolean) {
-        runInAction(() => this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value));
+        runInAction(() => {
+            this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value);
+        });
     }
 
     private _dropDisposer?: DragManager.DragDropDisposer;
@@ -88,7 +97,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
         if (this.collapsed) {
             this._props.setDocHeight(this.heading, 20);
         } else {
-            const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; //+ 15 accounts for the group header
+            const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; // +15 accounts for the group header
             const transformScale = this._props.screenToLocalTransform().Scale;
             const trueHeight = rawHeight * transformScale;
             this._props.setDocHeight(this.heading, trueHeight);
@@ -102,7 +111,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
             const key = this._props.pivotField;
             const castedValue = this.getValue(this.heading);
             if (this._props.parent.onInternalDrop(e, de)) {
-                key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !this.onLayoutDoc(key)));
+                key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
             }
             return true;
         }
@@ -118,7 +127,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
     };
 
     @action
-    headingChanged = (value: string, shiftDown?: boolean) => {
+    headingChanged = (value: string /* , shiftDown?: boolean */) => {
         this._createEmbeddingSelected = false;
         const key = this._props.pivotField;
         const castedValue = this.getValue(value);
@@ -141,7 +150,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
         this._color = color;
     };
 
-    pointerEnteredRow = action(() => SnappingManager.IsDragging && (this._background = '#b4b4b4'));
+    pointerEnteredRow = action(() => {
+        SnappingManager.IsDragging && (this._background = '#b4b4b4');
+    });
 
     @action
     pointerLeaveRow = () => {
@@ -153,13 +164,13 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
     addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
         if (!value && !forceEmptyNote) return false;
         this._createEmbeddingSelected = false;
-        const key = this._props.pivotField;
+        const { pivotField } = this._props;
         const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
         FormattedTextBox.SetSelectOnLoad(newDoc);
         FormattedTextBox.SelectOnLoadChar = value;
-        key && ((this.onLayoutDoc(key) ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading));
+        pivotField && (newDoc[DocData][pivotField] = this.getValue(this._props.heading));
         const docs = this._props.parent.childDocList;
-        return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
+        return docs ? !!docs.splice(0, 0, newDoc) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
     };
 
     deleteRow = undoBatch(
@@ -198,21 +209,11 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
     @action
     headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
         if (e.button === 0 && !e.ctrlKey) {
-            setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this._props.chromeHidden && this.collapseSection(e));
+            setupMoveUpEvents(this, e, this.headerMove, emptyFunction, clickEv => !this._props.chromeHidden && this.collapseSection(clickEv));
             this._createEmbeddingSelected = false;
         }
     };
 
-    /**
-     * Returns true if a key is on the layout doc of the documents in the collection.
-     */
-    onLayoutDoc = (key: string): boolean => {
-        DocListCast(this._props.parent.Document.data).forEach(doc => {
-            if (Doc.Get(doc, key, true)) return true;
-        });
-        return false;
-    };
-
     renderColorPicker = () => {
         const selected = this.color;
 
@@ -229,27 +230,29 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
         return (
             <div className="collectionStackingView-colorPicker">
                 <div className="colorOptions">
-                    <div className={'colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)}></div>
-                    <div className={'colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)}></div>
-                    <div className={'colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)}></div>
-                    <div className={'colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)}></div>
-                    <div className={'colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)}></div>
-                    <div className={'colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)}></div>
-                    <div className={'colorPicker' + (selected === green ? ' active' : '')} style={{ backgroundColor: green }} onClick={() => this.changeColumnColor(green!)}></div>
-                    <div className={'colorPicker' + (selected === cyan ? ' active' : '')} style={{ backgroundColor: cyan }} onClick={() => this.changeColumnColor(cyan!)}></div>
-                    <div className={'colorPicker' + (selected === orange ? ' active' : '')} style={{ backgroundColor: orange }} onClick={() => this.changeColumnColor(orange!)}></div>
+                    <div className={'colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)} />
+                    <div className={'colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)} />
+                    <div className={'colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)} />
+                    <div className={'colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)} />
+                    <div className={'colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)} />
+                    <div className={'colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)} />
+                    <div className={'colorPicker' + (selected === green ? ' active' : '')} style={{ backgroundColor: green }} onClick={() => this.changeColumnColor(green!)} />
+                    <div className={'colorPicker' + (selected === cyan ? ' active' : '')} style={{ backgroundColor: cyan }} onClick={() => this.changeColumnColor(cyan!)} />
+                    <div className={'colorPicker' + (selected === orange ? ' active' : '')} style={{ backgroundColor: orange }} onClick={() => this.changeColumnColor(orange!)} />
                 </div>
             </div>
         );
     };
 
-    toggleEmbedding = action(() => (this._createEmbeddingSelected = true));
-    toggleVisibility = () => (this._collapsed = !this.collapsed);
+    toggleEmbedding = action(() => {
+        this._createEmbeddingSelected = true;
+    });
+    toggleVisibility = () => {
+        this._collapsed = !this.collapsed;
+    };
 
     @action
-    textCallback = (char: string) => {
-        return this.addDocument('', false);
-    };
+    textCallback = (/* char: string */) => this.addDocument('', false);
 
     @computed get contentLayout() {
         const rows = Math.max(1, Math.min(this._props.docList.length, Math.floor((this._props.parent._props.PanelWidth() - 2 * this._props.parent.xMargin) / (this._props.parent.columnWidth + this._props.parent.gridGap))));
@@ -263,22 +266,22 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
                         className="collectionStackingView-addDocumentButton"
                         style={
                             {
-                                //width: style.columnWidth / style.numGroupColumns,
-                                //padding: `${NumCast(this._props.parent.layoutDoc._yPadding, this._props.parent.yMargin)}px 0px 0px 0px`,
+                                // width: style.columnWidth / style.numGroupColumns,
+                                // padding: `${NumCast(this._props.parent.layoutDoc._yPadding, this._props.parent.yMargin)}px 0px 0px 0px`,
                             }
                         }>
-                        <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents={'+ NEW'} />
+                        <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents="+ NEW" />
                     </div>
                 ) : null}
                 <div
-                    className={`collectionStackingView-masonryGrid`}
+                    className="collectionStackingView-masonryGrid"
                     ref={this._contRef}
                     style={{
                         padding: stackPad,
                         minHeight: this._props.showHandle && this._props.parent._props.isContentActive() ? '10px' : undefined,
                         width: this._props.parent.NodeWidth,
                         gridGap: this._props.parent.gridGap,
-                        gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this._props.parent.columnWidth}px`, ''),
+                        gridTemplateColumns: numberRange(rows).reduce(list => list + ` ${this._props.parent.columnWidth}px`, ''),
                     }}>
                     {this._props.parent.children(this._props.docList)}
                 </div>
@@ -290,7 +293,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
         const noChrome = this._props.chromeHidden;
         const key = this._props.pivotField;
         const evContents = this.heading ? this.heading : this._props.type && this._props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`;
-        const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />;
+        const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine />;
         return this._props.Document.miniHeaders ? (
             <div className="collectionStackingView-miniHeader">{editableHeaderView}</div>
         ) : !this._props.headingObject ? null : (
@@ -304,6 +307,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
                     {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : (
                         <div className="collectionStackingView-sectionColor">
                             <button
+                                type="button"
                                 className="collectionStackingView-sectionColorButton"
                                 onPointerDown={e =>
                                     setupMoveUpEvents(
@@ -311,7 +315,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
                                         e,
                                         returnFalse,
                                         emptyFunction,
-                                        action(e => (this._paletteOn = !this._paletteOn))
+                                        action(() => {
+                                            this._paletteOn = !this._paletteOn;
+                                        })
                                     )
                                 }>
                                 <FontAwesomeIcon icon="palette" size="lg" />
@@ -320,13 +326,13 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
                         </div>
                     )}
                     {noChrome ? null : (
-                        <button className="collectionStackingView-sectionDelete" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, noChrome ? emptyFunction : this.collapseSection)}>
+                        <button type="button" className="collectionStackingView-sectionDelete" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, noChrome ? emptyFunction : this.collapseSection)}>
                             <FontAwesomeIcon icon={this.collapsed ? 'chevron-down' : 'chevron-up'} size="lg" />
                         </button>
                     )}
                     {noChrome || evContents === `NO  ${key.toUpperCase()} VALUE` ? null : (
                         <div className="collectionStackingView-sectionOptions" onPointerDown={e => e.stopPropagation()}>
-                            <button className="collectionStackingView-sectionOptionButton" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}>
+                            <button type="button" className="collectionStackingView-sectionOptionButton" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}>
                                 <FontAwesomeIcon icon="trash" size="lg" />
                             </button>
                         </div>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index a23725348..5a509128d 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -36,6 +36,7 @@ interface CollectionMenuProps {
 
 @observer
 export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
+    // eslint-disable-next-line no-use-before-define
     @observable static Instance: CollectionMenu;
     @observable SelectedCollection: DocumentView | undefined = undefined;
 
@@ -63,7 +64,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
     }
 
     @action
-    toggleMenuPin = (e: React.MouseEvent) => {
+    toggleMenuPin = () => {
         Doc.UserDoc()['menuCollections-pinned'] = this.Pinned = !this.Pinned;
         if (!this.Pinned && this._left < 0) {
             this.jumpTo(300, 300);
@@ -165,7 +166,9 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
 }
 
 interface CollectionViewMenuProps {
+    // eslint-disable-next-line react/no-unused-prop-types
     type: CollectionViewType;
+    // eslint-disable-next-line react/no-unused-prop-types
     fieldKey: string;
     docView: DocumentView;
 }
@@ -377,16 +380,18 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         setupMoveUpEvents(
             this,
             e,
-            (e, down, delta) => {
+            moveEv => {
                 const vtype = this.props.type;
                 const c = {
                     params: ['target'],
                     title: vtype,
                     script: `this.target._type_collection = '${StrCast(this.props.type)}'`,
-                    immediate: (source: Doc[]) => (this.document._type_collection = Doc.getDocTemplate(source?.[0])),
+                    immediate: (source: Doc[]) => {
+                        this.document._type_collection = Doc.getDocTemplate(source?.[0]);
+                    },
                     initialize: emptyFunction,
                 };
-                DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.document }, c.params, c.initialize, e.clientX, e.clientY);
+                DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.document }, c.params, c.initialize, moveEv.clientX, moveEv.clientY);
                 return true;
             },
             emptyFunction,
@@ -397,8 +402,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         setupMoveUpEvents(
             this,
             e,
-            (e, down, delta) => {
-                this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.document }, c.params, c.initialize, e.clientX, e.clientY));
+            moveEv => {
+                this._buttonizableCommands
+                    ?.filter(c => c.title === this._currentKey)
+                    .map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.document }, c.params, c.initialize, moveEv.clientX, moveEv.clientY));
                 return true;
             },
             emptyFunction,
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 4ceeb631d..2ba6f5bf4 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -59,7 +59,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     }
 
     @computed get chromeHidden() {
-        return BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClickScript?.() ? true : false;
+        return !!(BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClickScript?.());
     }
     // columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns
     @computed get colHeaderData() {
@@ -68,7 +68,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         if (needsUnsetCategory || colHeaderData === undefined || colHeaderData.length === 0) {
             setTimeout(() => {
                 const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null) ?? []);
-                const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset'));
                 if (needsUnsetCategory || columnHeaders.length === 0) {
                     columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1));
                     this.resizeColumns(columnHeaders);
@@ -112,12 +111,12 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     // to render the docs you see within an individual column.
     children = (docs: Doc[]) => {
         TraceMobx();
-        return docs.map((d, i) => {
+        return docs.map(d => {
             const height = () => this.getDocHeight(d);
             const width = () => this.getDocWidth(d);
             const style = { width: width(), marginTop: this.gridGap, height: height() };
             return (
-                <div className={`collectionNoteTakingView-columnDoc`} key={d[Id]} style={style}>
+                <div className="collectionNoteTakingView-columnDoc" key={d[Id]} style={style}>
                     {this.getDisplayDoc(d, width)}
                 </div>
             );
@@ -136,7 +135,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const sections = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
         const rowCol = this.docsDraggedRowCol;
         // this will sort the docs into the correct columns (minus the ones you're currently dragging)
-        docs.map(d => {
+        docs.forEach(d => {
             const sectionValue = (d[this.notetakingCategoryField] as object) ?? `unset`;
             // look for if header exists already
             const existingHeader = columnHeaders.find(sh => sh.heading === sectionValue.toString());
@@ -154,7 +153,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
 
     removeDocDragHighlight = () => {
         setTimeout(
-            action(() => (this.docsDraggedRowCol.length = 0)),
+            action(() => {
+                this.docsDraggedRowCol.length = 0;
+            }),
             100
         );
     };
@@ -192,13 +193,11 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         Object.keys(this._disposers).forEach(key => this._disposers[key]());
     }
 
-    moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
-        return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
-    };
+    moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean) => !!(this._props.removeDocument?.(doc) && addDocument?.(doc));
 
     createRef = (ele: HTMLDivElement | null) => {
         this._masonryGridRef = ele;
-        this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
+        this.createDashEventsTarget(ele!);
     };
 
     @computed get onChildClickHandler() {
@@ -218,14 +217,15 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         Doc.BrushDoc(doc);
         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 && Math.ceil(this._props.PanelHeight()) < (this._mainCont?.scrollHeight || 0)) {
-                let focusSpeed = options.zoomTime ?? 500;
+                const focusSpeed = options.zoomTime ?? 500;
                 smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
                 return focusSpeed;
             }
         }
+        return undefined;
     };
 
     styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
@@ -240,6 +240,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                     return this._props.childOpacity();
                 }
                 break;
+            default:
         }
         return this._props.styleProvider?.(doc, props, property);
     };
@@ -255,7 +256,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const noteTakingDocTransform = () => this.getDocTransform(doc, dref);
         return (
             <DocumentView
-                ref={r => (dref = r || undefined)}
+                ref={r => {
+                    dref = r || undefined;
+                }}
                 Document={doc}
                 TemplateDataDocument={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
                 pointerEvents={this.blockPointerEventsWhenDragging}
@@ -267,8 +270,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 layout_fitWidth={this._props.childLayoutFitWidth}
                 isContentActive={emptyFunction}
                 onKey={this.onKeyDown}
-                //TODO: change this from a prop to a parameter passed into a function
-                dontHideOnDrag={true}
+                // TODO: change this from a prop to a parameter passed into a function
+                dontHideOnDrag
                 isDocumentActive={this.isContentActive}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
@@ -302,7 +305,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
+        this._scroll; // required for document decorations to update when the text box container is scrolled
         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);
@@ -347,7 +350,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     // Adding example: column widths are [0.6, 0.4] --> user adds column at end --> column widths are [0.4, 0.267, 0.33]
     @action
     resizeColumns = (headers: SchemaHeaderField[]) => {
-        const n = headers.length;
         const curWidths = headers.reduce((sum, hdr) => sum + Math.abs(hdr.width), 0);
         const scaleFactor = 1 / curWidths;
         this.dataDoc[this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>(
@@ -385,7 +387,11 @@ export class CollectionNoteTakingView extends CollectionSubView() {
             // we alter the pivot fields of the docs in case they are moved to a new column.
             const colIndex = this.getColumnFromXCoord(xCoord);
             const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading);
-            DragManager.docsBeingDragged.map(doc => doc[DocData]).forEach(d => (d[this.notetakingCategoryField] = colHeader));
+            DragManager.docsBeingDragged
+                .map(doc => doc[DocData])
+                .forEach(d => {
+                    d[this.notetakingCategoryField] = colHeader;
+                });
             // used to notify sections to re-render
             this.docsDraggedRowCol.length = 0;
             const columnFromCoord = this.getColumnFromXCoord(xCoord);
@@ -396,7 +402,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     // getColumnFromXCoord returns the column index for a given x-coordinate (currently always the client's mouse coordinate).
     // This function is used to know which document a column SHOULD be in while it is being dragged.
     getColumnFromXCoord = (xCoord: number): number | undefined => {
-        let colIndex: number | undefined = undefined;
+        let colIndex: number | undefined;
         const numColumns = this.colHeaderData.length;
         const coords = [];
         let colStartXCoord = 0;
@@ -419,10 +425,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const docsMatchingHeader: Doc[] = [];
         const colIndex = this.getColumnFromXCoord(xCoord);
         const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading);
-        this.childDocs?.map(d => {
+        this.childDocs?.forEach(d => {
             if (d instanceof Promise) return;
             const sectionValue = (d[this.notetakingCategoryField] as object) ?? 'unset';
-            if (sectionValue.toString() == colHeader) {
+            if (sectionValue.toString() === colHeader) {
                 docsMatchingHeader.push(d);
             }
         });
@@ -438,6 +444,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
             FormattedTextBox.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
+        return undefined;
     };
 
     // onInternalDrop is used when dragging and dropping a document within the view, such as dragging
@@ -575,6 +582,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 alert('You cannot use an existing column name. Please try a new column name');
                 return value;
             }
+            return undefined;
         });
         const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
         const newColWidth = 1 / (this.numGroupColumns + 1);
@@ -593,17 +601,33 @@ export class CollectionNoteTakingView extends CollectionSubView() {
             const subItems: ContextMenuProps[] = [];
             subItems.push({
                 description: `${this.layoutDoc._notetaking_columns_autoCreate ? 'Manually' : 'Automatically'} Create columns`,
-                event: () => (this.layoutDoc._notetaking_columns_autoCreate = !this.layoutDoc._notetaking_columns_autoCreate),
+                event: () => {
+                    this.layoutDoc._notetaking_columns_autoCreate = !this.layoutDoc._notetaking_columns_autoCreate;
+                },
                 icon: 'computer',
             });
             subItems.push({ description: 'Remove Empty Columns', event: this.removeEmptyColumns, icon: 'computer' });
             subItems.push({
                 description: `${this.layoutDoc._notetaking_columns_autoSize ? 'Variable Size' : 'Autosize'} Columns`,
-                event: () => (this.layoutDoc._notetaking_columns_autoSize = !this.layoutDoc._notetaking_columns_autoSize),
+                event: () => {
+                    this.layoutDoc._notetaking_columns_autoSize = !this.layoutDoc._notetaking_columns_autoSize;
+                },
                 icon: 'plus',
             });
-            subItems.push({ description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: 'plus' });
-            subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' });
+            subItems.push({
+                description: `${this.layoutDoc._layout_autoHeight ? 'Variable Height' : 'Auto Height'}`,
+                event: () => {
+                    this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight;
+                },
+                icon: 'plus',
+            });
+            subItems.push({
+                description: 'Clear All',
+                event: () => {
+                    this.dataDoc.data = new List([]);
+                },
+                icon: 'times',
+            });
             ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' });
         }
     };
@@ -628,6 +652,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const sections = Array.from(this.Sections.entries());
         return sections.reduce((list, sec, i) => {
             list.push(this.sectionNoteTaking(sec[0], sec[1]));
+            // eslint-disable-next-line react/no-array-index-key
             i !== sections.length - 1 && list.push(<CollectionNoteTakingViewDivider key={`divider${i}`} isContentActive={this.isContentActive} index={i} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />);
             return list;
         }, [] as JSX.Element[]);
@@ -661,14 +686,18 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                     background: this.backgroundColor(),
                     pointerEvents: this.backgroundEvents,
                 }}
-                onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
-                onPointerLeave={action(e => (this.docsDraggedRowCol.length = 0))}
+                onScroll={action(e => {
+                    this._scroll = e.currentTarget.scrollTop;
+                })}
+                onPointerLeave={action(() => {
+                    this.docsDraggedRowCol.length = 0;
+                })}
                 onPointerMove={e => e.buttons && this.onPointerMove(false, e.clientX, e.clientY)}
                 onDragOver={e => this.onPointerMove(true, e.clientX, e.clientY)}
                 onDrop={this.onExternalDrop.bind(this)}
                 onContextMenu={this.onContextMenu}
                 onWheel={e => this._props.isContentActive() && e.stopPropagation()}>
-                <>{this.renderedSections}</>
+                {this.renderedSections}
                 <div className="collectionNotetaking-pivotField" style={{ right: 0, top: 0, position: 'absolute' }}>
                     <FieldsDropdown
                         Document={this.Document}
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index e39f1c700..6dfc38e2e 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { action, computed, IReactionDisposer, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -67,6 +69,7 @@ export class CollectionPileView extends CollectionSubView() {
         return (
             <div className="collectionPileView-innards" style={{ pointerEvents: this.contentEvents }}>
                 <CollectionFreeFormView
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props} //
                     layoutEngine={this.layoutEngine}
                     addDocument={this.addPileDoc}
@@ -117,16 +120,16 @@ export class CollectionPileView extends CollectionSubView() {
         setupMoveUpEvents(
             this,
             e,
-            (e: PointerEvent, down: number[], delta: number[]) => {
-                if (this.layoutEngine() === 'pass' && this.childDocs.length && e.shiftKey) {
+            (moveEv: PointerEvent, down: number[], delta: number[]) => {
+                if (this.layoutEngine() === 'pass' && this.childDocs.length && moveEv.shiftKey) {
                     dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
                     if (dist > 100) {
                         if (!this._undoBatch) {
                             this._undoBatch = UndoManager.StartBatch('layout pile');
                         }
                         const doc = this.childDocs[0];
-                        doc.x = e.clientX;
-                        doc.y = e.clientY;
+                        doc.x = moveEv.clientX;
+                        doc.y = moveEv.clientY;
                         this._props.addDocTab(doc, OpenWhere.inParentFromScreen) && (this._props.removeDocument?.(doc) || false);
                         dist = 0;
                     }
@@ -155,7 +158,7 @@ export class CollectionPileView extends CollectionSubView() {
 
     render() {
         return (
-            <div className={`collectionPileView`} onClick={this.onClick} onPointerDown={this.pointerDown} style={{ width: this._props.PanelWidth(), height: '100%' }}>
+            <div className="collectionPileView" onClick={this.onClick} onPointerDown={this.pointerDown} style={{ width: this._props.PanelWidth(), height: '100%' }}>
                 {this.contents}
             </div>
         );
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 3f6638b25..7adf44a5c 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,3 +1,8 @@
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable no-use-before-define */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
@@ -22,7 +27,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { CollectionSubView } from '../collections/CollectionSubView';
+import { CollectionSubView } from './CollectionSubView';
 import { LightboxView } from '../LightboxView';
 import { AudioWaveform } from '../nodes/audio/AudioWaveform';
 import { DocumentView, OpenWhere } from '../nodes/DocumentView';
@@ -58,9 +63,14 @@ export enum TrimScope {
 
 @observer
 export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
+    // eslint-disable-next-line no-use-before-define
     public static SelectingRegions: Set<CollectionStackedTimeline> = new Set();
     public static StopSelecting() {
-        this.SelectingRegions.forEach(action(region => (region._selectingRegion = false)));
+        this.SelectingRegions.forEach(
+            action(region => {
+                region._selectingRegion = false;
+            })
+        );
         this.SelectingRegions.clear();
     }
     constructor(props: any) {
@@ -131,7 +141,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     componentWillUnmount() {
         document.removeEventListener('keydown', this.keyEvents, true);
         if (this._selectingRegion) {
-            runInAction(() => (this._selectingRegion = false));
+            runInAction(() => {
+                this._selectingRegion = false;
+            });
             CollectionStackedTimeline.SelectingRegions.delete(this);
         }
     }
@@ -175,9 +187,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null);
 
     // converts screen pixel offset to time
-    toTimeline = (screen_delta: number, width: number) => {
-        return Math.max(this.clipStart, Math.min(this.clipEnd, (screen_delta / width) * this.clipDuration + this.clipStart));
-    };
+    // prettier-ignore
+    toTimeline = (screenDelta: number, width: number) => //
+        Math.max(this.clipStart, Math.min(this.clipEnd, (screenDelta / width) * this.clipDuration + this.clipStart));
 
     @computed get rangeClick() {
         // prettier-ignore
@@ -235,6 +247,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                     this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
                     e.stopPropagation();
                     break;
+                default:
             }
         }
     };
@@ -254,17 +267,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     @action
     onPointerDownTimeline = (e: React.PointerEvent): void => {
         const rect = this._timeline?.getBoundingClientRect();
-        const clientX = e.clientX;
-        const diff = rect ? clientX - rect?.x : null;
-        const shiftKey = e.shiftKey;
+        const { clientX, shiftKey } = e;
         if (rect && this._props.isContentActive()) {
             const wasPlaying = this._props.playing();
             if (wasPlaying) this._props.Pause();
-            var wasSelecting = this._markerEnd !== undefined;
+            let wasSelecting = this._markerEnd !== undefined;
             setupMoveUpEvents(
                 this,
                 e,
-                action(e => {
+                action(() => {
                     if (!wasSelecting) {
                         this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width);
                         wasSelecting = true;
@@ -273,8 +284,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                     this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
                     return false;
                 }),
-                action((e, movement, isClick) => {
-                    this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
+                action((upEvent, movement, isClick) => {
+                    this._markerEnd = this.toTimeline(upEvent.clientX - rect.x, rect.width);
                     if (this._markerEnd < this._markerStart) {
                         const tmp = this._markerStart;
                         this._markerStart = this._markerEnd;
@@ -287,8 +298,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                     (!isClick || !wasSelecting) && (this._markerEnd = undefined);
                     this._timelineWrapper && (this._timelineWrapper.style.cursor = '');
                 }),
-                (e, doubleTap) => {
-                    if (e.button !== 2) {
+                (clickEv, doubleTap) => {
+                    if (clickEv.button !== 2) {
                         this._props.select(false);
                         !wasPlaying && doubleTap && this._props.Play();
                     }
@@ -310,7 +321,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     onHover = (e: React.MouseEvent): void => {
         e.stopPropagation();
         const rect = this._timeline?.getBoundingClientRect();
-        const clientX = e.clientX;
+        const { clientX } = e;
         if (rect) {
             this._hoverTime = this.toTimeline(clientX - rect.x, rect.width);
             if (this.thumbnails) {
@@ -328,14 +339,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         setupMoveUpEvents(
             this,
             e,
-            action((e, [], []) => {
+            action(moveEv => {
                 if (rect && this._props.isContentActive()) {
-                    this._trimStart = Math.min(Math.max(this.trimStart + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength);
+                    this._trimStart = Math.min(Math.max(this.trimStart + (moveEv.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength);
                 }
                 return false;
             }),
             emptyFunction,
-            action((e, doubleTap) => {
+            action((clickEv, doubleTap) => {
                 doubleTap && (this._trimStart = this.clipStart);
             })
         );
@@ -348,14 +359,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         setupMoveUpEvents(
             this,
             e,
-            action((e, [], []) => {
+            action(moveEv => {
                 if (rect && this._props.isContentActive()) {
-                    this._trimEnd = Math.max(Math.min(this.trimEnd + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength);
+                    this._trimEnd = Math.max(Math.min(this.trimEnd + (moveEv.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength);
                 }
                 return false;
             }),
             emptyFunction,
-            action((e, doubleTap) => {
+            action((clickEv, doubleTap) => {
                 doubleTap && (this._trimEnd = this.clipEnd);
             })
         );
@@ -384,7 +395,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     // handles dragging and dropping markers in timeline
     @action
-    internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
+    internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) {
         if (super.onInternalDrop(e, de)) {
             // determine x coordinate of drop and assign it to the documents being dragged  --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
             const localPt = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
@@ -404,7 +415,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     }
 
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
-        if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, 0);
+        if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
         return false;
     };
 
@@ -442,7 +453,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     }
 
     @action
-    playOnClick = (anchorDoc: Doc, clientX: number) => {
+    playOnClick = (anchorDoc: Doc /* , clientX: number */) => {
         const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
         const endTime = this.anchorEnd(anchorDoc);
         if (this.layoutDoc.autoPlayAnchors) {
@@ -451,17 +462,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 this._props.playFrom(seekTimeInSeconds, endTime);
                 this.scrollToTime(seekTimeInSeconds);
             }
-        } else {
-            if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) {
-                if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) {
-                    this._props.Pause();
-                } else {
-                    this._props.Play();
-                }
+        } else if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) {
+            if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) {
+                this._props.Pause();
             } else {
-                this._props.playFrom(seekTimeInSeconds, endTime);
-                this.scrollToTime(seekTimeInSeconds);
+                this._props.Play();
             }
+        } else {
+            this._props.playFrom(seekTimeInSeconds, endTime);
+            this.scrollToTime(seekTimeInSeconds);
         }
         return { select: true };
     };
@@ -480,19 +489,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 const rect = this._timeline?.getBoundingClientRect();
                 rect && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width));
             }
+        } else if (this.layoutDoc.autoPlayAnchors) {
+            this._props.playFrom(seekTimeInSeconds, endTime);
         } else {
-            if (this.layoutDoc.autoPlayAnchors) {
-                this._props.playFrom(seekTimeInSeconds, endTime);
-            } else {
-                this._props.setTime(seekTimeInSeconds);
-            }
+            this._props.setTime(seekTimeInSeconds);
         }
         return { select: true };
     };
 
     // makes sure no anchors overlaps each other by setting the correct position and width
     getLevel = (m: Doc, placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]) => {
-        const timelineContentWidth = this.timelineContentWidth;
+        const { timelineContentWidth } = this;
         const x1 = this.anchorStart(m);
         const x2 = this.anchorEnd(m, x1 + (10 / timelineContentWidth) * this.clipDuration);
         let max = 0;
@@ -504,6 +511,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                     max = Math.max(max, p.level);
                     return p.level;
                 }
+                return undefined;
             })
         );
         let level = max + 1;
@@ -565,10 +573,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                     onWheel={e => this.isContentActive() && e.stopPropagation()}
                     onScroll={this.setScroll}
                     onMouseMove={e => this.isContentActive() && this.onHover(e)}
-                    ref={wrapper => (this._timelineWrapper = wrapper)}>
+                    ref={wrapper => {
+                        this._timelineWrapper = wrapper;
+                    }}>
                     <div
                         className="collectionStackedTimeline"
-                        ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
+                        ref={(timeline: HTMLDivElement | null) => {
+                            this._timeline = timeline;
+                        }}
                         onClick={e => this.isContentActive() && StopEvent(e)}
                         onPointerDown={e => this.isContentActive() && this.onPointerDownTimeline(e)}
                         style={{ width: this.timelineContentWidth }}>
@@ -583,7 +595,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                             const height = this._props.PanelHeight() / maxLevel;
                             return this.Document.hideAnchors ? null : (
                                 <div
-                                    className={'collectionStackedTimeline-marker-timeline'}
+                                    className="collectionStackedTimeline-marker-timeline"
                                     key={d.anchor[Id]}
                                     style={{
                                         left,
@@ -593,6 +605,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                                         pointerEvents: 'none',
                                     }}>
                                     <StackedTimelineAnchor
+                                        // eslint-disable-next-line react/jsx-props-no-spreading
                                         {...this._props}
                                         mark={d.anchor}
                                         containerViewPath={this._props.containerViewPath}
@@ -647,7 +660,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
                         {this.IsTrimming !== TrimScope.None && (
                             <>
-                                <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }}></div>
+                                <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }} />
 
                                 <div
                                     className="collectionStackedTimeline-trim-controls"
@@ -655,8 +668,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                                         left: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%`,
                                         width: `${((this.trimEnd - this.trimStart) / this.clipDuration) * 100}%`,
                                     }}>
-                                    <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimLeft}></div>
-                                    <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimRight}></div>
+                                    <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimLeft} />
+                                    <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimRight} />
                                 </div>
 
                                 <div
@@ -664,7 +677,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                                     style={{
                                         left: `${((this.trimEnd - this.clipStart) / this.clipDuration) * 100}%`,
                                         width: `${((this.clipEnd - this.trimEnd) / this.clipDuration) * 100}%`,
-                                    }}></div>
+                                    }}
+                                />
                             </>
                         )}
                     </div>
@@ -686,7 +700,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 interface StackedTimelineAnchorProps {
     mark: Doc;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
-    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
+    addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
     rangeClickScript: () => ScriptField;
     rangePlayScript: () => ScriptField;
     left: number;
@@ -736,13 +750,13 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
         this._disposer = reaction(
             () => this._props.currentTimecode(),
             time => {
-                const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null);
-                const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc);
+                // const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null);
+                // const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc);
                 if (
                     !LightboxView.LightboxDoc &&
                     // bcz: when should links be followed?  we don't want to move away from the video to follow a link but we can open it in a sidebar/etc.  But we don't know that upfront.
                     // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
-                    /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc))*/
+                    /* (isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc)) */
                     !this._props.layoutDoc.dontAutoFollowLinks &&
                     LinkManager.Links(this._props.mark).length &&
                     time > NumCast(this._props.mark[this._props.startTag]) &&
@@ -764,34 +778,33 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
     // starting the drag event for anchor resizing
     @action
     onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
-        //this._props._timeline?.setPointerCapture(e.pointerId);
-        const newTime = (e: PointerEvent) => {
-            const rect = (e.target as any).getBoundingClientRect();
-            return this._props.toTimeline(e.clientX - rect.x, rect.width);
+        const newTime = (timeDownEv: PointerEvent) => {
+            const rect = (timeDownEv.target as any).getBoundingClientRect();
+            return this._props.toTimeline(timeDownEv.clientX - rect.x, rect.width);
         };
-        const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => {
+        const changeAnchor = (time: number | undefined) => {
             const timelineOnly = Cast(anchor[this._props.startTag], 'number', null) !== undefined;
             if (timelineOnly) {
-                if (!left && time !== undefined && time <= NumCast(anchor[this._props.startTag])) time = undefined;
-                Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, time, true);
-                if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', time !== undefined ? undefined : '100%', true);
+                const timeMod = !left && time !== undefined && time <= NumCast(anchor[this._props.startTag]) ? undefined : time;
+                Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, timeMod, true);
+                if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', timeMod !== undefined ? undefined : '100%', true);
             } else {
                 anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time;
             }
             return false;
         };
         this.noEvents = true;
-        var undo: UndoManager.Batch | undefined;
+        let undo: UndoManager.Batch | undefined;
         setupMoveUpEvents(
             this,
             e,
-            e => {
+            moveEv => {
                 if (!undo) undo = UndoManager.StartBatch('drag anchor');
-                this._props.setTime(newTime(e));
-                return changeAnchor(anchor, left, newTime(e));
+                this._props.setTime(newTime(moveEv));
+                return changeAnchor(newTime(moveEv));
             },
-            action(e => {
-                this._props.setTime(newTime(e));
+            action(upEv => {
+                this._props.setTime(newTime(upEv));
                 undo?.end();
                 this.noEvents = false;
             }),
@@ -829,7 +842,9 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
                     {...this._props}
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
-                    ref={action((r: DocumentView | null) => (anchor.view = r))}
+                    ref={action((r: DocumentView | null) => {
+                        anchor.view = r;
+                    })}
                     Document={mark}
                     TemplateDataDocument={undefined}
                     containerViewPath={this._props.containerViewPath}
@@ -852,7 +867,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
                     onClickScript={script}
                     onDoubleClickScript={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
                     ignoreAutoHeight={false}
-                    hideResizeHandles={true}
+                    hideResizeHandles
                     contextMenuItems={this.contextMenuItems}
                 />
             ),
@@ -878,12 +893,15 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
         );
     }
 }
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function formatToTime(time: number): any {
     return formatTime(time);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function min(num1: number, num2: number): number {
     return Math.min(num1, num2);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function max(num1: number, num2: number): number {
     return Math.max(num1, num2);
 });
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 7d93f4074..f3dedaedf 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -3,11 +3,12 @@ import * as React from 'react';
 import * as rp from 'request-promise';
 import { ClientUtils, returnFalse } from '../../../ClientUtils';
 import CursorField from '../../../fields/CursorField';
-import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, 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';
+import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
@@ -61,8 +62,8 @@ export interface SubCollectionViewProps extends CollectionViewProps {
     isAnyChildContentActive: () => boolean;
 }
 
-export function CollectionSubView<X>(moreProps?: X) {
-    class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps>() {
+export function CollectionSubView<X>() {
+    class CollectionSubViewInternal extends ViewBoxBaseComponent<X & SubCollectionViewProps>() {
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected _mainCont?: HTMLDivElement;
@@ -167,23 +168,24 @@ export function CollectionSubView<X>(moreProps?: X) {
                 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 FieldType).includes(CollectionView.name);
-                    const data = d[annos ? fieldKey + '_annotations' : fieldKey];
-                    const side = annos && d[fieldKey + '_sidebar'];
-                    if (data !== undefined || side !== undefined) {
-                        let subDocs = [...DocListCast(data), ...DocListCast(side)];
+                    const isAnnotatableDoc = d[fieldKey] instanceof List && !(d[fieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
+                    const docChildDocs = d[isAnnotatableDoc ? fieldKey + '_annotations' : fieldKey];
+                    const sidebarDocs = isAnnotatableDoc && d[fieldKey + '_sidebar'];
+                    if (docChildDocs !== undefined || sidebarDocs !== undefined) {
+                        let subDocs = [...DocListCast(docChildDocs), ...DocListCast(sidebarDocs)];
                         if (subDocs.length > 0) {
                             let newarray: Doc[] = [];
                             notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, childFiltersByRanges, d).length);
                             while (subDocs.length > 0 && !notFiltered) {
                                 newarray = [];
+                                // eslint-disable-next-line no-loop-func
                                 subDocs.forEach(t => {
-                                    const fieldKey = Doc.LayoutFieldKey(t);
-                                    const annos = !Field.toString(Doc.LayoutField(t) as FieldType).includes(CollectionView.name);
+                                    const docFieldKey = Doc.LayoutFieldKey(t);
+                                    const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
                                     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));
-                                    annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
+                                    DocListCast(t[isSubDocAnnotatable ? docFieldKey + '_annotations' : docFieldKey]).forEach(newdoc => newarray.push(newdoc));
+                                    isSubDocAnnotatable && DocListCast(t[docFieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
                                 });
                                 subDocs = newarray;
                             }
@@ -227,6 +229,7 @@ export function CollectionSubView<X>(moreProps?: X) {
         }
 
         @undoBatch
+        // eslint-disable-next-line @typescript-eslint/no-unused-vars
         protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {}
 
         protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) {
@@ -245,7 +248,7 @@ export function CollectionSubView<X>(moreProps?: X) {
 
         addDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.addDocument?.(doc, annotationKey) || false;
         removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.removeDocument?.(doc, annotationKey) || false;
-        moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this._props.moveDocument?.(doc, targetCollection, addDocument) || false;
+        moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean) => this._props.moveDocument?.(doc, targetCollection, addDocument) || false;
 
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
             const { docDragData } = de.complete;
@@ -531,5 +534,5 @@ export function CollectionSubView<X>(moreProps?: X) {
         };
     }
 
-    return CollectionSubView;
+    return CollectionSubViewInternal;
 }
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 7f234ffe9..e1d2e3c40 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -16,7 +18,6 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { FieldsDropdown } from '../FieldsDropdown';
 import { DocumentView } from '../nodes/DocumentView';
-import { FocusViewOptions } from '../nodes/FieldView';
 import { PresBox } from '../nodes/trails';
 import { CollectionSubView } from './CollectionSubView';
 import './CollectionTimeView.scss';
@@ -68,7 +69,7 @@ export class CollectionTimeView extends CollectionSubView() {
     };
 
     @action
-    scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: FocusViewOptions) => {
+    scrollPreview = (docView: DocumentView, anchor: Doc /* , focusSpeed: number, options: FocusViewOptions */) => {
         // if in preview, then override document's fields with view spec
         this._focusFilters = StrListCast(anchor.config_docFilters);
         this._focusRangeFilters = StrListCast(anchor.config_docRangeFilters);
@@ -77,13 +78,15 @@ export class CollectionTimeView extends CollectionSubView() {
     };
 
     layoutEngine = () => this._layoutEngine;
-    toggleVisibility = action(() => (this._collapsed = !this._collapsed));
+    toggleVisibility = action(() => {
+        this._collapsed = !this._collapsed;
+    });
 
     onMinDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(
             this,
             e,
-            action((e: PointerEvent, down: number[], delta: number[]) => {
+            action((moveEv: PointerEvent, down: number[], delta: number[]) => {
                 const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0));
                 const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10));
                 this.Document[this._props.fieldKey + '-timelineMinReq'] = minReq + ((maxReq - minReq) * delta[0]) / this._props.PanelWidth();
@@ -99,7 +102,7 @@ export class CollectionTimeView extends CollectionSubView() {
         setupMoveUpEvents(
             this,
             e,
-            action((e: PointerEvent, down: number[], delta: number[]) => {
+            action((moveEv, down: number[], delta: number[]) => {
                 const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0));
                 const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10));
                 this.Document[this._props.fieldKey + '-timelineMaxReq'] = maxReq + ((maxReq - minReq) * delta[0]) / this._props.PanelWidth();
@@ -114,7 +117,7 @@ export class CollectionTimeView extends CollectionSubView() {
         setupMoveUpEvents(
             this,
             e,
-            action((e: PointerEvent, down: number[], delta: number[]) => {
+            action((moveEv: PointerEvent, down: number[], delta: number[]) => {
                 const minReq = NumCast(this.Document[this._props.fieldKey + '-timelineMinReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMin'], 0));
                 const maxReq = NumCast(this.Document[this._props.fieldKey + '-timelineMaxReq'], NumCast(this.Document[this._props.fieldKey + '-timelineMax'], 10));
                 this.Document[this._props.fieldKey + '-timelineMinReq'] = minReq - ((maxReq - minReq) * delta[0]) / this._props.PanelWidth();
@@ -134,7 +137,7 @@ export class CollectionTimeView extends CollectionSubView() {
     };
 
     @action
-    contentsDown = (e: React.MouseEvent) => {
+    contentsDown = () => {
         const prevFilterIndex = NumCast(this.layoutDoc._prevFilterIndex);
         if (prevFilterIndex > 0) {
             this.goTo(prevFilterIndex - 1);
@@ -147,6 +150,7 @@ export class CollectionTimeView extends CollectionSubView() {
         return (
             <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
                 <CollectionFreeFormView
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props}
                     engineProps={{ pivotField: this.pivotField, childFilters: this.childDocFilters, childFiltersByRanges: this.childDocRangeFilters }}
                     fitContentsToBox={returnTrue}
@@ -162,7 +166,7 @@ export class CollectionTimeView extends CollectionSubView() {
         const fieldKey = Doc.LayoutFieldKey(doc);
         doc[fieldKey + '-timelineCur'] = ComputedField.MakeFunction("(activePresentationItem()[this._pivotField || 'year'] || 0)");
     }
-    specificMenu = (e: React.MouseEvent) => {
+    specificMenu = () => {
         const layoutItems: ContextMenuProps[] = [];
         const doc = this.layoutDoc;
 
@@ -194,7 +198,7 @@ export class CollectionTimeView extends CollectionSubView() {
 
     render() {
         let nonNumbers = 0;
-        this.childDocs.map(doc => {
+        this.childDocs.forEach(doc => {
             const num = NumCast(doc[this.pivotField], Number(StrCast(doc[this.pivotField])));
             if (isNaN(num)) {
                 nonNumbers++;
@@ -226,13 +230,20 @@ export class CollectionTimeView extends CollectionSubView() {
                     </>
                 )}
                 <div style={{ right: 0, top: 0, position: 'absolute' }}>
-                    <FieldsDropdown Document={this.Document} selectFunc={fieldKey => (this.layoutDoc._pivotField = fieldKey)} placeholder={StrCast(this.layoutDoc._pivotField)} />
+                    <FieldsDropdown
+                        Document={this.Document}
+                        selectFunc={fieldKey => {
+                            this.layoutDoc._pivotField = fieldKey;
+                        }}
+                        placeholder={StrCast(this.layoutDoc._pivotField)}
+                    />
                 </div>
             </div>
         );
     }
 }
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
     const pivotField = StrCast(pivotDoc._pivotField, 'author');
     let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
@@ -249,6 +260,7 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou
             const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
             if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
                 if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
+                    // eslint-disable-next-line prefer-destructuring
                     pivotDoc._pivotField = filterVals[0];
                 }
             }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 32473f20b..538a6fd5e 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -23,13 +25,13 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { DocumentView } from '../nodes/DocumentView';
-import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
 import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionSubView } from './CollectionSubView';
 import './CollectionTreeView.scss';
 import { TreeView } from './TreeView';
+
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export type collectionTreeViewProps = {
@@ -94,8 +96,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
 
     // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent
     @observable _isAnyChildContentActive = false;
-    whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
-    isContentActive = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this._props.isContentActive() ? true : false);
+    whenChildContentsActiveChanged = action((isActive: boolean) => {
+        this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
+    });
+    isContentActive = () => (this._isAnyChildContentActive ? true : !!this._props.isContentActive());
 
     componentWillUnmount() {
         this._isDisposing = true;
@@ -105,7 +109,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     }
 
     componentDidMount() {
-        //this._props.setContentView?.(this);
+        // this._props.setContentView?.(this);
         this._disposers.autoheight = reaction(
             () => this.layoutDoc.layout_autoHeight,
             auto => auto && this.computeHeight(),
@@ -128,20 +132,19 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     observeHeight = (ref: any) => {
         if (ref) {
             this.refList.add(ref);
-            this.observer = new _global.ResizeObserver(
-                action((entries: any) => {
-                    if (this.layoutDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.IsDragging) {
-                        this.computeHeight();
-                    }
-                })
-            );
+            this.observer = new _global.ResizeObserver(() => {
+                if (this.layoutDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.IsDragging) {
+                    this.computeHeight();
+                }
+            });
             this.layoutDoc.layout_autoHeight && this.computeHeight();
             this.observer.observe(ref);
         }
     };
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this._treedropDisposer?.();
-        if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this));
+        this._mainEle = ele;
+        if (ele) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this));
     };
 
     protected onInternalDrop(e: Event, de: DragManager.DropEvent) {
@@ -169,7 +172,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         }
     };
 
-    dragConfig = (dragData: DragManager.DocumentDragData) => (dragData.treeViewDoc = this.Document);
+    dragConfig = (dragData: DragManager.DocumentDragData) => { dragData.treeViewDoc = this.Document; }; // prettier-ignore
 
     screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight);
 
@@ -198,10 +201,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
 
     @action
     addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
-        const doclist = docs instanceof Doc ? [docs] : docs;
-        const addDocRelativeTo = (doc: Doc | Doc[]) => doclist.reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true);
+        const addDocRelativeTo = (adocs: Doc | Doc[]) => (adocs as Doc[]).reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true);
         if (this.Document.resolvedDataDoc instanceof Promise) return false;
-        const res = relativeTo === undefined ? this._props.addDocument?.(docs) || false : addDocRelativeTo(docs);
+        const doclist = docs instanceof Doc ? [docs] : docs;
+        const res = relativeTo === undefined ? this._props.addDocument?.(doclist) || false : addDocRelativeTo(doclist);
         res &&
             doclist.forEach(doc => {
                 Doc.SetContainer(doc, this.Document);
@@ -209,7 +212,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             });
         return res;
     };
-    onContextMenu = (e: React.MouseEvent): void => {
+    onContextMenu = (): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
         const layoutItems: ContextMenuProps[] = [];
         const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick).script.originalScript === CollectionTreeView.AddTreeFunc;
@@ -217,11 +220,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         if (!Doc.noviceMode) {
             layoutItems.push({
                 description: 'Make tree state ' + (this.Document.treeView_OpenIsTransient ? 'persistent' : 'transient'),
-                event: () => (this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient),
+                event: () => { this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient; }, // prettier-ignore
                 icon: 'paint-brush',
             });
-            layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields), icon: 'paint-brush' });
-            layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle), icon: 'paint-brush' });
+            layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => { this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields; }, icon: 'paint-brush' }); // prettier-ignore
+            layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => { this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle; }, icon: 'paint-brush' }); // prettier-ignore
         }
         ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
         if (!Doc.noviceMode) {
@@ -240,9 +243,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         return (
             <EditableView
                 contents={this.dataDoc.title}
-                display={'block'}
+                display="block"
                 maxHeight={72}
-                height={'auto'}
+                height="auto"
                 GetValue={() => StrCast(this.dataDoc.title)}
                 SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => {
                     if (enter && this.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren);
@@ -253,22 +256,24 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         );
     }
 
-    onKey = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+    onKey = (e: React.KeyboardEvent /* , fieldProps: FieldViewProps */) => {
         if (this.outlineMode && e.key === 'Enter') {
             e.stopPropagation();
             this.makeTextCollection(this.treeChildren);
             return true;
         }
+        return undefined;
     };
     get documentTitle() {
         return (
             <FormattedTextBox
+                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props}
                 fieldKey="text"
                 renderDepth={this._props.renderDepth + 1}
                 isContentActive={this.isContentActive}
                 isDocumentActive={this.isContentActive}
-                forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not layout_autoHeight
+                forceAutoHeight // needed to make the title resize even if the rest of the tree view is not layout_autoHeight
                 PanelWidth={this.documentTitleWidth}
                 PanelHeight={this.documentTitleHeight}
                 NativeDimScaling={returnOne}
@@ -296,7 +301,12 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         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 moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false;
-        if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
+        if (this._renderCount < this.treeChildren.length)
+            setTimeout(
+                action(() => {
+                    this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20);
+                })
+            );
         return TreeView.GetChildElements(
             this.treeChildren,
             this,
@@ -326,7 +336,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             this.observeHeight,
             this.unobserveHeight,
             this.childContextMenuItems(),
-            //TODO: [AL] add these
             this._props.AddToMap,
             this._props.RemFromMap,
             this._props.hierarchyIndex,
@@ -337,7 +346,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         return this.dataDoc === null ? null : (
             <div
                 className="collectionTreeView-titleBar"
-                ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.ScreenToLocalBoxXf().Scale))}
+                ref={action((r: any) => {
+                    (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.ScreenToLocalBoxXf().Scale);
+                })}
                 key={this.Document[Id]}
                 style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
                 {this.outlineMode ? this.documentTitle : this.editableTitle}
@@ -420,7 +431,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         return (
             <div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}>
                 {!this.buttonMenu && !this.noviceExplainer ? null : (
-                    <div className="documentButtonMenu" ref={action((r: HTMLDivElement | null) => r && (this._headerHeight = DivHeight(r)))}>
+                    <div
+                        className="documentButtonMenu"
+                        ref={action((r: HTMLDivElement | null) => {
+                            r && (this._headerHeight = DivHeight(r));
+                        })}>
                         {this.buttonMenu}
                         {this.noviceExplainer}
                     </div>
@@ -453,7 +468,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                                 minHeight: '100%',
                             }}
                             onWheel={e => e.stopPropagation()}
-                            onClick={e => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())}
+                            onClick={() => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())}
                             onDrop={this.onTreeDrop}>
                             <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul>
                         </div>
@@ -470,13 +485,14 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
                 {!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? (
                     <CollectionFreeFormView
+                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
                         setContentViewBox={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
                         pointerEvents={this._props.isContentActive() && SnappingManager.IsDragging ? returnAll : returnNone}
-                        isAnnotationOverlay={true}
-                        isAnnotationOverlayScrollable={true}
+                        isAnnotationOverlay
+                        isAnnotationOverlayScrollable
                         childDocumentsActive={this._props.isContentActive}
                         fieldKey={this._props.fieldKey + '_annotations'}
                         dropAction={dropActionType.move}
@@ -500,6 +516,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     }
 }
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function addTreeFolder(doc: Doc) {
     CollectionTreeView.addTreeFolder(doc);
 });
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 8a2e83ed9..a661cf6a2 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -32,7 +32,7 @@ import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
 import { Colors } from '../global/globalEnums';
 import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
+import { FieldViewProps } from '../nodes/FieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { PresBox, PresMovement } from '../nodes/trails';
 import { CollectionDockingView } from './CollectionDockingView';
@@ -42,6 +42,146 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 
 const _global = (window /* browser */ || global) /* node */ as any;
 
+interface TabMinimapViewProps {
+    document: Doc;
+    tabView: () => DocumentView | undefined;
+    addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
+    PanelWidth: () => number;
+    PanelHeight: () => number;
+    background: () => string;
+}
+interface TabMiniThumbProps {
+    miniWidth: () => number;
+    miniHeight: () => number;
+    miniTop: () => number;
+    miniLeft: () => number;
+}
+
+@observer
+class TabMiniThumb extends React.Component<TabMiniThumbProps> {
+    render() {
+        const { miniWidth, miniHeight, miniLeft, miniTop } = this.props;
+        return <div className="miniThumb" style={{ width: `${miniWidth()}%`, height: `${miniHeight()}%`, left: `${miniLeft()}%`, top: `${miniTop()}%` }} />;
+    }
+}
+@observer
+export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> {
+    static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
+        if (doc) {
+            switch (property.split(':')[0]) {
+                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';
+                            case DocumentType.IMG:    return 'blue';
+                            case DocumentType.MAP:    return 'orange';
+                            case DocumentType.VID:    return 'purple';
+                            case DocumentType.RTF:    return 'yellow';
+                            case DocumentType.COL:    return undefined;
+                            default:                  return 'gray';
+                        } // 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
+        }
+        return undefined;
+    };
+
+    @computed get renderBounds() {
+        const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView;
+        const bounds = compView?.freeformData?.(true)?.bounds;
+        if (!bounds) return undefined;
+        const xbounds = bounds.r - bounds.x;
+        const ybounds = bounds.b - bounds.y;
+        const dim = Math.max(xbounds, ybounds);
+        return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
+    }
+    @computed get xPadding() {
+        return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l));
+    }
+    @computed get yPadding() {
+        return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l));
+    }
+    childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null);
+    returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150);
+    miniDown = (e: React.PointerEvent) => {
+        const doc = this._props.document;
+        const miniSize = this.returnMiniSize();
+        doc &&
+            setupMoveUpEvents(
+                this,
+                e,
+                action((moveEv, down: number[], delta: number[]) => {
+                    const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 };
+                    doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
+                    doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
+                    return false;
+                }),
+                emptyFunction,
+                emptyFunction
+            );
+    };
+    popup = () => {
+        if (!this.renderBounds) return <div />;
+        const { renderBounds } = this;
+        const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+        const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+        const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
+        const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
+        const miniSize = this.returnMiniSize();
+        return (
+            <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
+                <CollectionFreeFormView
+                    Document={this._props.document}
+                    docViewPath={returnEmptyDocViewList}
+                    childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
+                    noOverlay // don't render overlay Docs since they won't scale
+                    isContentActive={emptyFunction}
+                    isAnyChildContentActive={returnFalse}
+                    select={emptyFunction}
+                    isSelected={returnFalse}
+                    dontRegisterView
+                    fieldKey={Doc.LayoutFieldKey(this._props.document)}
+                    addDocument={returnFalse}
+                    moveDocument={returnFalse}
+                    removeDocument={returnFalse}
+                    PanelWidth={this.returnMiniSize}
+                    PanelHeight={this.returnMiniSize}
+                    ScreenToLocalTransform={Transform.Identity}
+                    renderDepth={0}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    focus={emptyFunction}
+                    styleProvider={TabMinimapView.miniStyleProvider}
+                    addDocTab={this._props.addDocTab}
+                    // eslint-disable-next-line no-use-before-define
+                    pinToPres={TabDocView.PinDoc}
+                    childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
+                    childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
+                    searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
+                    fitContentsToBox={returnTrue}
+                    xPadding={this.xPadding}
+                    yPadding={this.yPadding}
+                />
+                <div className="miniOverlay" onPointerDown={this.miniDown}>
+                    <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
+                </div>
+            </div>
+        );
+    };
+    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={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
+            </div>
+        );
+    }
+}
+
 interface TabDocViewProps {
     documentId: FieldId;
     keyValue?: boolean;
@@ -92,7 +232,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     @action
     init = (tab: any, doc: Opt<Doc>) => {
         if (tab.contentItem === tab.header.parent.getActiveContentItem()) this._activated = true;
-        if (tab.DashDoc !== doc && doc && tab.hasOwnProperty('contentItem') && tab.contentItem.config.type !== 'stack') {
+        if (tab.DashDoc !== doc && doc && tab.contentItem?.config.type !== 'stack') {
             tab._disposers = {} as { [name: string]: IReactionDisposer };
             tab.contentItem.config.fixed && (tab.contentItem.parent.config.fixed = true);
             tab.DashDoc = doc;
@@ -130,17 +270,17 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                     setupMoveUpEvents(
                         this,
                         e,
-                        e =>
-                            !e.defaultPrevented &&
-                            DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY, undefined, () => {
+                        moveEv =>
+                            !moveEv.defaultPrevented &&
+                            DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), moveEv.clientX, moveEv.clientY, undefined, () => {
                                 CollectionDockingView.CloseSplit(doc);
                             }),
                         returnFalse,
-                        action(e => {
+                        action(clickEv => {
                             if (this.view) {
                                 SelectionManager.SelectView(this.view, false);
                                 const child = getChild();
-                                simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
+                                simulateMouseClick(child, clickEv.clientX, clickEv.clientY + 30, clickEv.screenX, clickEv.screenY + 30);
                             } else {
                                 this._activated = true;
                                 setTimeout(() => this.view && SelectionManager.SelectView(this.view, false));
@@ -160,7 +300,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                     ({ variant, degree, highlight }) => {
                         const color = highlight?.highlightIndex === Doc.DocBrushStatus.highlighted ? highlight.highlightColor : degree ? ['transparent', variant, variant, 'orange'][degree] : variant;
 
-                        const textColor = color === variant ? SnappingManager.userColor : lightOrDark(color);
+                        const textColor = color === variant ? SnappingManager.userColor ?? '' : lightOrDark(color);
                         titleEle.style.color = textColor;
                         iconWrap.style.color = textColor;
                         closeWrap.style.color = textColor;
@@ -186,19 +326,19 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                 );
             }
             // shifts the focus to this tab when another tab is dragged over it
-            tab.element[0].onmouseenter = (e: MouseEvent) => {
+            tab.element[0].onmouseenter = () => {
                 if (SnappingManager.IsDragging && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
                     tab.header.parent.setActiveContentItem(tab.contentItem);
                     tab.setActive(true);
                 }
                 this._document && Doc.BrushDoc(this._document);
             };
-            tab.element[0].onmouseleave = (e: MouseEvent) => {
+            tab.element[0].onmouseleave = () => {
                 this._document && Doc.UnBrushDoc(this._document);
             };
 
             tab.element[0].oncontextmenu = (e: MouseEvent) => {
-                let child = getChild();
+                const child = getChild();
                 if (child) {
                     simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
                     e.stopPropagation();
@@ -220,12 +360,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                 () => SelectionManager.IsSelected(this._document),
                 action(selected => {
                     if (selected) this._activated = true;
-                    const toggle = tab.element[0].children[2].children[0] as HTMLInputElement;
                     if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
                         undoable(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch')();
                     }
-                    //toggle.style.fontWeight = selected ? 'bold' : '';
-                    // toggle.style.textTransform = selected ? "uppercase" : "";
                 }),
                 { fireImmediately: true }
             );
@@ -233,14 +370,16 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
             // highlight the tab when the tab document is brushed in any part of the UI
             tab._disposers.reactionDisposer = reaction(
                 () => doc?.title,
-                title => (titleEle.value = title),
+                title => {
+                    titleEle.value = title;
+                },
                 { fireImmediately: true }
             );
 
             // clean up the tab when it is closed
             tab.closeElement
-                .off('click') //unbind the current click handler
-                .click(function () {
+                .off('click') // unbind the current click handler
+                .click(() => {
                     Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
                     SelectionManager.DeselectAll();
                     UndoManager.RunInBatch(() => tab.contentItem.remove(), 'delete tab');
@@ -250,7 +389,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
 
     /**
      * Adds a document to the presentation view
-     **/
+     * */
     @action
     public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) {
         const docList = docs instanceof Doc ? [docs] : docs;
@@ -310,7 +449,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
             if (curPres.expandBoolean) pinDoc.presentation_expandInlineButton = true;
             Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement());
             PresBox.Instance?.clearSelectedArray();
-            pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array
+            pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); // Update selected array
         });
         if (
             // open the presentation trail if it's not already opened
@@ -328,6 +467,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     componentDidMount() {
         new _global.ResizeObserver(
             action((entries: any) => {
+                // eslint-disable-next-line no-restricted-syntax
                 for (const entry of entries) {
                     this._panelWidth = entry.contentRect.width;
                     this._panelHeight = entry.contentRect.height;
@@ -374,29 +514,30 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     //                                  "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name,
     //                                   "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
     //  lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox
-    addDocTab = (doc: Doc, location: OpenWhere) => {
+    addDocTab = (docsIn: Doc | Doc[], location: OpenWhere) => {
+        const docs = docsIn instanceof Doc ? [docsIn] : docsIn;
         SelectionManager.DeselectAll();
         const whereFields = location.split(':');
         const keyValue = whereFields.includes(OpenWhereMod.keyvalue);
         const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
         const panelName = whereFields.length > 1 ? whereFields.lastElement() : '';
-        if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc);
+        if (docs[0]?.dockingConfig && !keyValue) return DashboardView.openDashboard(docs[0]);
         // prettier-ignore
         switch (whereFields[0]) {
             case undefined:
             case OpenWhere.lightbox:   if (this.layoutDoc?._isLightbox) {
-                                            const lightboxView = !doc.annotationOn && DocCast(doc.embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.embedContainer)) : undefined;
+                                            const lightboxView = !docs[0].annotationOn && DocCast(docs[0].embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(docs[0].embedContainer)) : undefined;
                                             const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.Document)];
                                             if (lightboxView && (!data || data instanceof List)) {
-                                                lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>([doc]);
+                                                lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>(docs);
                                                 return true;
                                             }
                                        }
-                                       return LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox);
-            case OpenWhere.close:      return CollectionDockingView.CloseSplit(doc, whereMods);
-            case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, panelName, undefined, keyValue);
-            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue);
-            case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue);
+                                       return LightboxView.Instance.AddDocTab(docs[0], OpenWhere.lightbox);
+            case OpenWhere.close:      return CollectionDockingView.CloseSplit(docs[0], whereMods);
+            case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(docs[0], whereMods, this.stack, panelName, undefined, keyValue);
+            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(docs[0], whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue);
+            case OpenWhere.add:default:return CollectionDockingView.AddSplit(docs[0], whereMods, this.stack, undefined, keyValue);
         }
     };
     remDocTab = (doc: Doc | Doc[]) => {
@@ -410,12 +551,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
 
     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);
+        const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(findTab => findTab.DashDoc === tabDoc && !findTab.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)
         return tab !== undefined;
     };
     @action
-    focusFunc = (doc: Doc, options: FocusViewOptions) => {
+    focusFunc = () => {
         if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
             this.tab.header.parent.setActiveContentItem(this.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)
         }
@@ -498,8 +639,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                         }
                         this._lastTab = this.tab;
                         (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
-                        DocServer.GetRefField(this._props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document)));
-                        new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref);
+                        DocServer.GetRefField(this._props.documentId).then(
+                            action(doc => {
+                                doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document);
+                            })
+                        );
+                        new _global.ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(ref);
                     }
                 }}>
                 {this.docView}
@@ -507,141 +652,3 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         );
     }
 }
-
-interface TabMinimapViewProps {
-    document: Doc;
-    tabView: () => DocumentView | undefined;
-    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
-    PanelWidth: () => number;
-    PanelHeight: () => number;
-    background: () => string;
-}
-interface TabMiniThumbProps {
-    miniWidth: () => number;
-    miniHeight: () => number;
-    miniTop: () => number;
-    miniLeft: () => number;
-}
-
-@observer
-class TabMiniThumb extends React.Component<TabMiniThumbProps> {
-    render() {
-        const { miniWidth, miniHeight, miniLeft, miniTop } = this.props;
-        return <div className="miniThumb" style={{ width: `${miniWidth()}%`, height: `${miniHeight()}%`, left: `${miniLeft()}%`, top: `${miniTop()}%` }} />;
-    }
-}
-@observer
-export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> {
-    static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
-        if (doc) {
-            switch (property.split(':')[0]) {
-                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';
-                            case DocumentType.IMG:    return 'blue';
-                            case DocumentType.MAP:    return 'orange';
-                            case DocumentType.VID:    return 'purple';
-                            case DocumentType.RTF:    return 'yellow';
-                            case DocumentType.COL:    return undefined;
-                            default:                  return 'gray';
-                        } // 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
-        }
-    };
-
-    @computed get renderBounds() {
-        const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView;
-        const bounds = compView?.freeformData?.(true)?.bounds;
-        if (!bounds) return undefined;
-        const xbounds = bounds.r - bounds.x;
-        const ybounds = bounds.b - bounds.y;
-        const dim = Math.max(xbounds, ybounds);
-        return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
-    }
-    @computed get xPadding() {
-        return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l));
-    }
-    @computed get yPadding() {
-        return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l));
-    }
-    childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null);
-    returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150);
-    miniDown = (e: React.PointerEvent) => {
-        const doc = this._props.document;
-        const miniSize = this.returnMiniSize();
-        doc &&
-            setupMoveUpEvents(
-                this,
-                e,
-                action((e: PointerEvent, down: number[], delta: number[]) => {
-                    const renderBounds = this.renderBounds ?? { l: 0, r: 0, t: 0, b: 0, dim: 1 };
-                    doc._freeform_panX = clamp(NumCast(doc._freeform_panX) + (delta[0] / miniSize) * renderBounds.dim, renderBounds.l, renderBounds.l + renderBounds.dim);
-                    doc._freeform_panY = clamp(NumCast(doc._freeform_panY) + (delta[1] / miniSize) * renderBounds.dim, renderBounds.t, renderBounds.t + renderBounds.dim);
-                    return false;
-                }),
-                emptyFunction,
-                emptyFunction
-            );
-    };
-    popup = () => {
-        if (!this.renderBounds) return <></>;
-        const renderBounds = this.renderBounds;
-        const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
-        const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
-        const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
-        const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
-        const miniSize = this.returnMiniSize();
-        return (
-            <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
-                <CollectionFreeFormView
-                    Document={this._props.document}
-                    docViewPath={returnEmptyDocViewList}
-                    childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
-                    noOverlay={true} // don't render overlay Docs since they won't scale
-                    isContentActive={emptyFunction}
-                    isAnyChildContentActive={returnFalse}
-                    select={emptyFunction}
-                    isSelected={returnFalse}
-                    dontRegisterView={true}
-                    fieldKey={Doc.LayoutFieldKey(this._props.document)}
-                    addDocument={returnFalse}
-                    moveDocument={returnFalse}
-                    removeDocument={returnFalse}
-                    PanelWidth={this.returnMiniSize}
-                    PanelHeight={this.returnMiniSize}
-                    ScreenToLocalTransform={Transform.Identity}
-                    renderDepth={0}
-                    whenChildContentsActiveChanged={emptyFunction}
-                    focus={emptyFunction}
-                    styleProvider={TabMinimapView.miniStyleProvider}
-                    addDocTab={this._props.addDocTab}
-                    pinToPres={TabDocView.PinDoc}
-                    childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
-                    childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
-                    searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
-                    fitContentsToBox={returnTrue}
-                    xPadding={this.xPadding}
-                    yPadding={this.yPadding}
-                />
-                <div className="miniOverlay" onPointerDown={this.miniDown}>
-                    <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
-                </div>
-            </div>
-        );
-    };
-    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={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 20323a521..fab8e3892 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -55,7 +55,7 @@ export interface TreeViewProps {
     treeViewParent: Doc;
     renderDepth: number;
     dragAction: dropActionType;
-    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
+    addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
     panelWidth: () => number;
     panelHeight: () => number;
     addDocument: (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => boolean;
@@ -200,11 +200,11 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         }
         return false;
     };
-    @undoBatch remove = (doc: Doc | Doc[], key: string) => {
+    @undoBatch remove = (docs: Doc | Doc[], key: string) => {
         this.treeView._props.select(false);
-        const ind = DocListCast(this.dataDoc[key]).indexOf(doc instanceof Doc ? doc : doc.lastElement());
+        const ind = DocListCast(this.dataDoc[key]).indexOf(docs instanceof Doc ? docs : docs.lastElement());
 
-        const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
+        const res = (docs instanceof Doc ? [docs] : docs).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
         res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView.DocumentView?.())?.select(false);
         return res;
     };
@@ -318,7 +318,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex);
     }
 
-    onDragUp = (e: PointerEvent) => {
+    onDragUp = () => {
         document.removeEventListener('pointerup', this.onDragUp, true);
         document.removeEventListener('pointermove', this.onDragMove, true);
     };
@@ -403,7 +403,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         if (!this._header.current) return false;
         const rect = this._header.current.getBoundingClientRect();
         const before = pt[1] < rect.top + rect.height / 2;
-        const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
+        const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || !!(!before && this.treeViewOpen && this.childDocs?.length);
         if (de.complete.linkDragData) {
             const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
             const destDoc = this.Document;
@@ -431,14 +431,14 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         return false;
     };
 
-    localAdd = (doc: Doc | Doc[]) => {
-        const innerAdd = (doc: Doc) => {
+    localAdd = (docs: Doc | Doc[]): boolean => {
+        const innerAdd = (doc: Doc): boolean => {
             const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
             const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
             dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
             return added;
         };
-        return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
+        return (docs instanceof Doc ? [docs] : docs).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
     };
 
     dropping: boolean = false;
@@ -513,16 +513,16 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             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);
-                const moveDoc = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(doc, target, addDoc);
-                const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => {
-                    const innerAdd = (doc: Doc) => {
+                const remDoc = (docs: Doc | Doc[]) => this.remove(docs, key);
+                const moveDoc = (docs: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(docs, target, addDoc);
+                const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => {
+                    const innerAdd = (iDoc: Doc) => {
                         const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
-                        const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
-                        dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
+                        const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, iDoc, addBefore, before, false, true);
+                        dataIsComputed && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer));
                         return added;
                     };
-                    return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
+                    return (docs instanceof Doc ? [docs] : docs).reduce((flg, iDoc) => flg && innerAdd(iDoc), true as boolean);
                 };
                 contentElement = TreeView.GetChildElements(
                     contents instanceof Doc ? [contents] : DocListCast(contents),
@@ -643,7 +643,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
 
                 return added;
             };
-            const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true);
+            const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => (docs instanceof Doc ? [docs] : docs).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;
             let downY = 0;
@@ -911,7 +911,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         // 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.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;
@@ -1192,7 +1192,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         const pt = [de.clientX, de.clientY];
         const rect = this._header.current!.getBoundingClientRect();
         const before = pt[1] < rect.top + rect.height / 2;
-        const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
+        const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || !!(!before && this.treeViewOpen && this.childDocs?.length);
 
         this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, dropActionType.copy, undefined, undefined, false, false));
     };
@@ -1259,7 +1259,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         remove: undefined | ((doc: Doc | Doc[]) => boolean),
         move: DragManager.MoveFunction,
         dragAction: dropActionType,
-        addDocTab: (doc: Doc, where: OpenWhere) => boolean,
+        addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean,
         styleProvider: undefined | StyleProviderFuncType,
         screenToLocalXf: () => Transform,
         isContentActive: (outsideReaction?: boolean) => boolean,
@@ -1282,11 +1282,6 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         hierarchyIndex?: number[],
         renderCount?: number
     ) {
-        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(treeViewParent.treeView_SortCriterion, TreeSort.WhenAdded));
         const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView._props.NativeDimScaling?.() || 1);
         const treeViewRefs = new Map<Doc, TreeView | undefined>();
@@ -1327,7 +1322,6 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                         dataDoc={pair.data}
                         treeViewParent={treeViewParent}
                         prevSibling={docs[i]}
-                        // TODO: [AL] add these
                         hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined}
                         AddToMap={AddToMap}
                         RemFromMap={RemFromMap}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 29d835b28..65a529d62 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -4,7 +4,6 @@ import * as React from 'react';
 import { Doc, DocListCast, FieldType, FieldResult } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
 import { StrCast } from '../../../../fields/Types';
-import { DocumentManager } from '../../../util/DocumentManager';
 import { LinkManager } from '../../../util/LinkManager';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
@@ -49,8 +48,8 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
     setupStates = () => {
         this._originalbackground = StrCast(this._props.Document[DocData].backgroundColor);
         //  state entry functions
-        const setBackground = (colour: string) => () => {this._props.Document[DocData].backgroundColor = colour;} // prettier-ignore
-        const setOpacity = (opacity: number) => () => {this._props.LayoutDoc.opacity = opacity;} // prettier-ignore
+        // 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.childDocs().length ? this._props.childDocs()[0] : undefined);
         const numDocs = () => this._props.childDocs().length;
@@ -73,7 +72,6 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
 
         let trail: number;
 
-        const trailView = () => DocumentManager.Instance.DocumentViews.find(view => view.Document === Doc.MyTrails);
         const presentationMode = () => Doc.ActivePresentation?.presentation_status;
 
         // set of states
@@ -83,6 +81,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
                 docCreated: [() => numDocs(), () => {
                     docX = firstDoc()?.x;
                     docY = firstDoc()?.y;
+                    // eslint-disable-next-line no-use-before-define
                     return oneDoc;
                 }],
             }
@@ -93,18 +92,20 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
             {
                 // docCreated: [() => numDocs() > 1, () => multipleDocs],
                 docDeleted: [() => numDocs() < 1, () => start],
-                docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => {
+                docMoved: [() => (docX && docX !== docNewX()) || (docY && docY !== docNewY()), () => {
                     docX = firstDoc()?.x;
                     docY = firstDoc()?.y;
+                    // eslint-disable-next-line no-use-before-define
                     return movedDoc;
                 }],
             }
         ); // prettier-ignore
 
         const movedDoc = InfoState(
-            'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (\":\")',
+            'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (":")',
             {
-                docCreated: [() => numDocs() == 2, () => multipleDocs],
+                // eslint-disable-next-line no-use-before-define
+                docCreated: [() => numDocs() === 2, () => multipleDocs],
                 docDeleted: [() => numDocs() < 1, () => start],
             },
             'dash-colon-menu.gif',
@@ -114,6 +115,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
         const multipleDocs = InfoState(
             'Let\'s create a new link. Click the link icon on one of your documents.',
             {
+                // eslint-disable-next-line no-use-before-define
                 linkStarted: [() => linkStart(), () => startedLink],
                 docRemoved: [() => numDocs() < 2, () => oneDoc],
             },
@@ -124,6 +126,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
             'Now click the highlighted link icon on your other document.',
             {
                 linkUnstart: [() => linkUnstart(), () => multipleDocs],
+                // eslint-disable-next-line no-use-before-define
                 linkCreated: [() => numDocLinks(), () => madeLink],
                 docRemoved: [() => numDocs() < 2, () => oneDoc],
             },
@@ -136,6 +139,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
                 linkCreated: [() => !numDocLinks(), () => multipleDocs],
                 linkViewed: [() => linkMenuOpen(), () => {
                     alert(numDocLinks() + " cheer for " + numDocLinks() + " link!");
+                    // eslint-disable-next-line no-use-before-define
                     return viewedLink;
                 }],
             },
@@ -147,10 +151,12 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
             {
                 linkDeleted: [() => !numDocLinks(), () => multipleDocs],
                 docRemoved: [() => numDocs() < 2, () => oneDoc],
-                docCreated: [() => numDocs() == 3, () => {
+                docCreated: [() => numDocs() === 3, () => {
                     trail = pin().length;
+                    // eslint-disable-next-line no-use-before-define
                     return presentDocs;
                 }],
+                // eslint-disable-next-line no-use-before-define
                 activePen: [() => activeTool() === InkTool.Pen, () => penMode],
             },
             'documentation.png',
@@ -164,6 +170,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
                     () => pin().length > trail,
                     () => {
                         trail = pin().length;
+                        // eslint-disable-next-line no-use-before-define
                         return pinnedDoc1;
                     },
                 ],
@@ -186,11 +193,13 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
                 () => pin().length > trail,
                 () => {
                     trail = pin().length;
+                    // eslint-disable-next-line no-use-before-define
                     return pinnedDoc2;
                 },
             ],
             // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
             // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+            // eslint-disable-next-line no-use-before-define
             autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
             docRemoved: [() => numDocs() < 3, () => viewedLink],
         });
@@ -200,11 +209,13 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
                 () => pin().length > trail,
                 () => {
                     trail = pin().length;
+                    // eslint-disable-next-line no-use-before-define
                     return pinnedDoc3;
                 },
             ],
             // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
             // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+            // eslint-disable-next-line no-use-before-define
             autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
             docRemoved: [() => numDocs() < 3, () => viewedLink],
         });
@@ -219,6 +230,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
             ],
             // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
             // manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
+            // eslint-disable-next-line no-use-before-define
             autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
             docRemoved: [() => numDocs() < 3, () => viewedLink],
         });
@@ -236,15 +248,18 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
 
         const manualPresentationMode = InfoState("You're in manual presentation mode.", {
             // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
+            // eslint-disable-next-line no-use-before-define
             autoPresentation: [() => presentationMode() === 'auto', () => autoPresentationMode],
             docRemoved: [() => numDocs() < 3, () => viewedLink],
-            docCreated: [() => numDocs() == 4, () => completed],
+            // eslint-disable-next-line no-use-before-define
+            docCreated: [() => numDocs() === 4, () => completed],
         });
 
         const autoPresentationMode = InfoState("You're in auto presentation mode.", {
             // editPresentation: [() => presentationMode() === 'edit', () => editPresentationMode],
             manualPresentation: [() => presentationMode() === 'manual', () => manualPresentationMode],
             docRemoved: [() => numDocs() < 3, () => viewedLink],
+            // eslint-disable-next-line no-use-before-define
             docCreated: [() => numDocs() === 4, () => completed],
         });
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index a0b96c75a..3970c6807 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable no-use-before-define */
 import { Doc, Field, FieldType, FieldResult } from '../../../../fields/Doc';
 import { Id, ToString } from '../../../../fields/FieldSymbols';
 import { ObjectField } from '../../../../fields/ObjectField';
@@ -48,7 +49,7 @@ export interface PoolData {
 export interface ViewDefResult {
     ele: JSX.Element;
     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
+    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<FieldType>) {
     if (typeof target === 'number' || Number(target)) {
@@ -84,9 +85,9 @@ interface PivotColumn {
     filters: string[];
 }
 
-export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
+export function computePassLayout(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>();
-    childPairs.forEach(({ layout, data }, i) => {
+    childPairs.forEach(({ layout, data }) => {
         docMap.set(layout[Id], {
             x: NumCast(layout.x),
             y: NumCast(layout.y),
@@ -97,10 +98,15 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
             replica: '',
         });
     });
+    // eslint-disable-next-line no-use-before-define
     return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
 }
 
-export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
+function toNumber(val: FieldResult<FieldType>) {
+    return val === undefined ? undefined : NumCast(val, Number(StrCast(val)));
+}
+
+export function computeStarburstLayout(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 burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
     const burstScale = NumCast(pivotDoc._starburstDocScale, 1);
@@ -132,7 +138,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
 
     let nonNumbers = 0;
     const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author';
-    childPairs.map(pair => {
+    childPairs.forEach(pair => {
         const listValue = Cast(pair.layout[pivotFieldKey], listSpec('string'), null);
 
         const num = toNumber(pair.layout[pivotFieldKey]);
@@ -184,11 +190,11 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
     const textlen = Array.from(pivotColumnGroups.keys())
         .map(c => getTextWidth(toLabel(c), desc))
         .reduce((p, c) => Math.max(p, c), 0 as number);
-    const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2);
+    const maxText = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2);
     const maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.docs.length), 1);
 
     const colWidth = panelDim[0] / pivotColumnGroups.size;
-    const colHeight = panelDim[1] - max_text;
+    const colHeight = panelDim[1] - maxText;
     let numCols = 0;
     let bestArea = 0;
     let pivotAxisWidth = 0;
@@ -212,7 +218,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
     let x = 0;
     const sortedPivotKeys = pivotNumbers ? Array.from(pivotColumnGroups.keys()).sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : Array.from(pivotColumnGroups.keys()).sort();
     sortedPivotKeys.forEach(key => {
-        const val = pivotColumnGroups.get(key)!;
+        const val = pivotColumnGroups.get(key);
         let y = 0;
         let xCount = 0;
         const text = toLabel(key);
@@ -222,11 +228,11 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
             x,
             y: pivotAxisWidth,
             width: pivotAxisWidth * expander * numCols,
-            height: max_text,
+            height: maxText,
             fontSize,
             payload: val,
         });
-        val.docs.forEach((doc, i) => {
+        val?.docs.forEach((doc, i) => {
             const layoutDoc = Doc.Layout(doc);
             let wid = pivotAxisWidth;
             let hgt = pivotAxisWidth / (Doc.NativeAspect(layoutDoc) || 1);
@@ -262,14 +268,11 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
         payload: pivotColumnGroups.get(key)!.filters,
     }));
     groupNames.push(...dividers);
-    return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
-}
-
-function toNumber(val: FieldResult<FieldType>) {
-    return val === undefined ? undefined : NumCast(val, Number(StrCast(val)));
+    // eslint-disable-next-line no-use-before-define
+    return normalizeResults(panelDim, maxText, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
 }
 
-export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps?: any) {
+export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps?: any */) {
     const fieldKey = 'data';
     const pivotDateGroups = new Map<number, Doc[]>();
     const docMap = new Map<string, PoolData>();
@@ -340,6 +343,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc:
         if (!stack && (curTime === undefined || Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth)) {
             groupNames.push({ type: 'text', text: toLabel(key), x: x, y: stack * 25, height: fontHeight, fontSize, payload: undefined });
         }
+        // eslint-disable-next-line no-use-before-define
         layoutDocsAtTime(keyDocs, key);
     });
     if (sortedKeys.length && curTime !== undefined && curTime > sortedKeys[sortedKeys.length - 1]) {
@@ -404,7 +408,7 @@ function normalizeResults(
 
     Array.from(docMap.entries())
         .filter(ele => ele[1].pair)
-        .map(ele => {
+        .forEach(ele => {
             const newPosRaw = ele[1];
             if (newPosRaw) {
                 const newPos: PoolData = {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a70713429..d9c988d54 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/jsx-props-no-spreading */
 /* eslint-disable jsx-a11y/click-events-have-key-events */
 /* eslint-disable jsx-a11y/no-static-element-interactions */
 import { Bezier } from 'bezier-js';
@@ -22,7 +23,6 @@ import { TraceMobx } from '../../../../fields/util';
 import { Gestures, PointData } from '../../../../pen-gestures/GestureTypes';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
 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';
@@ -48,6 +48,7 @@ import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
 import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { PresBox } from '../../nodes/trails/PresBox';
+// eslint-disable-next-line import/extensions
 import { CreateImage } from '../../nodes/WebBoxRenderer';
 import { StyleProp } from '../../StyleProvider';
 import { CollectionSubView } from '../CollectionSubView';
@@ -76,7 +77,7 @@ export interface collectionFreeformViewProps {
 @observer
 export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() {
     public get displayName() {
-        return 'CollectionFreeFormView(' + this.Document.title?.toString() + ')';
+        return 'CollectionFreeFormView(' + (this.Document.title?.toString() ?? '') + ')';
     } // this makes mobx trace() statements more descriptive
 
     @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
@@ -94,8 +95,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         super(props);
         makeObservable(this);
     }
-    @observable
-    public static ShowPresPaths = false;
 
     private _panZoomTransitionTimer: any;
     private _lastX: number = 0;
@@ -238,7 +237,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
     @observable _keyframeEditing = false;
-    @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
+    @action setKeyFrameEditing = (set: boolean) => {
+        this._keyframeEditing = set;
+    };
     getKeyFrameEditing = () => this._keyframeEditing;
     onBrowseClickHandler = () => this._props.onBrowseClickScript?.() || ScriptCast(this.layoutDoc.onBrowseClick);
     onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
@@ -256,7 +257,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
     panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
     panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
-    zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); //, NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
+    zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
     PanZoomCenterXf = () => (this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.centeringShiftX}px, ${this.centeringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`);
     ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy();
     getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
@@ -272,7 +273,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     addDocument = (newBox: Doc | Doc[]) => {
         let retVal = false;
         if (newBox instanceof Doc) {
-            if ((retVal = this._props.addDocument?.(newBox) || false)) {
+            retVal = this._props.addDocument?.(newBox) || false;
+            if (retVal) {
                 this.bringToFront(newBox);
                 this.updateCluster(newBox);
             }
@@ -282,15 +284,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
         if (retVal) {
             const newBoxes = newBox instanceof Doc ? [newBox] : newBox;
-            for (const newBox of newBoxes) {
-                if (newBox.activeFrame !== undefined) {
-                    const vals = CollectionFreeFormDocumentView.animFields.map(field => newBox[field.key]);
-                    CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field.key}_indexed`]);
-                    CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field.key]);
-                    delete newBox.activeFrame;
-                    CollectionFreeFormDocumentView.animFields.forEach((field, i) => field.key !== 'opacity' && (newBox[field.key] = vals[i]));
+            newBoxes.forEach(box => {
+                if (box.activeFrame !== undefined) {
+                    const vals = CollectionFreeFormDocumentView.animFields.map(field => box[field.key]);
+                    CollectionFreeFormDocumentView.animFields.forEach(field => delete box[`${field.key}_indexed`]);
+                    CollectionFreeFormDocumentView.animFields.forEach(field => delete box[field.key]);
+                    delete box.activeFrame;
+                    CollectionFreeFormDocumentView.animFields.forEach((field, i) => {
+                        field.key !== 'opacity' && (box[field.key] = vals[i]);
+                    });
                 }
-            }
+            });
             if (this.Document._currentFrame !== undefined && !this._props.isAnnotationOverlay) {
                 CollectionFreeFormDocumentView.setupKeyframes(newBoxes, NumCast(this.Document._currentFrame), true);
             }
@@ -313,14 +317,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     focus = (anchor: Doc, options: FocusViewOptions) => {
-        if (this._lightboxDoc) return;
+        if (this._lightboxDoc) return undefined;
         if (anchor === this.Document) {
             // if (options.willZoomCentered && options.zoomScale) {
             //     this.fitContentOnce();
             //     options.didMove = true;
             // }
         }
-        if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor) && !this.childLayoutPairs.map(pair => pair.layout).includes(anchor)) return;
+        // prettier-ignore
+        if (anchor.type !== DocumentType.CONFIG && 
+            !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor) && //
+            !this.childLayoutPairs.map(pair => pair.layout).includes(anchor)) { 
+            return undefined;
+        }
         const xfToCollection = options?.docTransform ?? Transform.Identity();
         const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
         const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
@@ -336,12 +345,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             this.setPan(panX, panY, focusTime, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
             return focusTime;
         }
+        return undefined;
     };
 
     getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> =>
         new Promise<Opt<DocumentView>>(res => {
             if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false);
-            if (doc === this.Document) return res(this.DocumentView?.());
+            if (doc === this.Document) {
+                res(this.DocumentView?.());
+                return;
+            }
             const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
             findDoc(dv => res(dv));
         });
@@ -357,7 +370,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             .map(pair => pair.layout)
             .slice()
             .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
-        zsorted.forEach((doc, index) => (doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1));
+        zsorted.forEach((doc, index) => {
+            doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1;
+        });
         const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
         const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
 
@@ -410,7 +425,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             let added = false;
             //  do nothing if link is dropped into any freeform view parent of dragged document
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x, y, title: 'dropped annotation' });
-            added = this._props.addDocument?.(source) ? true : false;
+            added = !!this._props.addDocument?.(source);
             de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
             if (de.complete.linkDocument) {
                 de.complete.linkDocument.layout_isSvg = true;
@@ -425,8 +440,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de, de.complete.annoDragData);
-        else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData);
-        else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
+        if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData);
+        if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
         return false;
     };
 
@@ -487,8 +502,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     @action
-    updateClusters(_freeform_useClusters: boolean) {
-        this.Document._freeform_useClusters = _freeform_useClusters;
+    updateClusters(useClusters: boolean) {
+        this.Document._freeform_useClusters = useClusters;
         this._clusterSets.length = 0;
         this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
     }
@@ -498,12 +513,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
         if (this.Document._freeform_useClusters) {
             const docFirst = docs[0];
-            docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
+            docs.forEach(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
             const preferredInd = NumCast(docFirst.layout_cluster);
-            docs.map(doc => (doc.layout_cluster = -1));
+            docs.forEach(doc => {
+                doc.layout_cluster = -1;
+            });
             docs.map(doc =>
                 this._clusterSets.map((set, i) =>
-                    set.map(member => {
+                    set.forEach(member => {
                         if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) {
                             docFirst.layout_cluster = i;
                         }
@@ -518,19 +535,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             ) {
                 docFirst.layout_cluster = preferredInd;
             }
-            this._clusterSets.map((set, i) => {
+            this._clusterSets.forEach((set, i) => {
                 if (docFirst.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
                     docFirst.layout_cluster = i;
                 }
             });
             if (docFirst.layout_cluster === -1) {
-                docs.map(doc => {
+                docs.forEach(doc => {
                     doc.layout_cluster = this._clusterSets.length;
                     this._clusterSets.push([doc]);
                 });
             } else if (this._clusterSets.length) {
                 for (let i = this._clusterSets.length; i <= NumCast(docFirst.layout_cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]);
-                docs.map(doc => this._clusterSets[(doc.layout_cluster = NumCast(docFirst.layout_cluster))].push(doc));
+                docs.forEach(doc => {
+                    this._clusterSets[(doc.layout_cluster = NumCast(docFirst.layout_cluster))].push(doc);
+                });
             }
             childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.layout_cluster === i) && this.updateCluster(child));
         }
@@ -573,17 +592,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (doc && this.childDocList?.includes(doc))
             switch (property.split(':')[0]) {
                 case StyleProp.BackgroundColor:
-                    const cluster = NumCast(doc?.layout_cluster);
-                    if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
-                        if (this._clusterSets.length <= cluster) {
-                            setTimeout(() => doc && this.updateCluster(doc));
-                        } else {
-                            // choose a cluster color from a palette
-                            const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
-                            styleProp = colors[cluster % colors.length];
-                            const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
-                            // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document
-                            set?.map(s => (styleProp = StrCast(s.backgroundColor)));
+                    {
+                        const cluster = NumCast(doc?.layout_cluster);
+                        if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
+                            if (this._clusterSets.length <= cluster) {
+                                setTimeout(() => doc && this.updateCluster(doc));
+                            } else {
+                                // choose a cluster color from a palette
+                                const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
+                                styleProp = colors[cluster % colors.length];
+                                const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
+                                // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document
+                                set?.forEach(s => {
+                                    styleProp = StrCast(s.backgroundColor);
+                                });
+                            }
                         }
                     }
                     break;
@@ -591,6 +614,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     if (doc && this.Document._currentFrame !== undefined) {
                         return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor;
                     }
+                    break;
+                default:
             }
         return styleProp;
     };
@@ -626,9 +651,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     case InkTool.None:
                         if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
                             this._hitCluster = this.pickCluster(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
-                            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
+                            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1, false);
                         }
                         break;
+                    default:
                 }
             }
         }
@@ -639,13 +665,34 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @undoBatch
     onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
         switch (ge.gesture) {
-            default:
+            // case Gestures.Rectangle:
+            //     {
+            //         const strokes = this.getActiveDocuments()
+            //             .filter(doc => doc.type === DocumentType.INK)
+            //             .map(i => {
+            //                 const d = Cast(i.stroke, InkField);
+            //                 const x = NumCast(i.x) - Math.min(...(d?.inkData.map(pd => pd.X) ?? [0]));
+            //                 const y = NumCast(i.y) - Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0]));
+            //                 return !d ? [] : d.inkData.map(pd => ({ X: x + pd.X, Y: y + pd.Y }));
+            //             });
+
+            //         CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {});
+            //     }
+            //     break;
+            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] }));
+                    e.stopPropagation();
+                }
+                break;
             case Gestures.Line:
             case Gestures.Circle:
             case Gestures.Rectangle:
             case Gestures.Triangle:
             case Gestures.Stroke:
-                const points = ge.points;
+            default: {
+                const { points } = ge;
                 const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
                 const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale;
                 const inkDoc = Docs.Create.InkDocument(
@@ -664,29 +711,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 }
                 this.addDocument(inkDoc);
                 e.stopPropagation();
-                break;
-            case Gestures.Rectangle:
-                const strokes = this.getActiveDocuments()
-                    .filter(doc => doc.type === DocumentType.INK)
-                    .map(i => {
-                        const d = Cast(i.stroke, InkField);
-                        const x = NumCast(i.x) - Math.min(...(d?.inkData.map(pd => pd.X) ?? [0]));
-                        const y = NumCast(i.y) - Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0]));
-                        return !d ? [] : d.inkData.map(pd => ({ X: x + pd.X, Y: y + pd.Y }));
-                    });
-
-                CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {});
-                break;
-            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] }));
-                    e.stopPropagation();
-                }
+            }
         }
     };
     @action
-    onEraserUp = (e: PointerEvent): void => {
+    onEraserUp = (): void => {
         this._deleteList.forEach(ink => ink._props.removeDocument?.(ink.Document));
         this._deleteList = [];
         this._batch?.end();
@@ -792,7 +821,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this.childDocs
             .map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.()))
             .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
-            .map(inkView => ({ inkViewBounds: inkView!.getBounds, inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
+            .map(inkView => inkView!)
+            .map(inkView => ({ inkViewBounds: inkView.getBounds, inkStroke: inkView.ComponentView as InkingStroke, inkView }))
             .filter(
                 ({ inkViewBounds }) =>
                     inkViewBounds && // bounding box of eraser segment and ink stroke overlap
@@ -807,7 +837,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     // Convert from screen space to ink space for the intersection.
                     const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint);
                     const currPointInkSpace = inkStroke.ptFromScreen(currPoint);
-                    for (var i = 0; i < inkData.length - 3; i += 4) {
+                    for (let i = 0; i < inkData.length - 3; i += 4) {
                         const rawIntersects = InkField.Segment(inkData, i).intersects({
                             // compute all unique intersections
                             p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y },
@@ -833,16 +863,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @action
     segmentInkStroke = (ink: DocumentView, excludeT: number): Segment[] => {
         const segments: Segment[] = [];
-        var segment: Segment = [];
-        var startSegmentT = 0;
+        let segment: Segment = [];
+        let startSegmentT = 0;
         const { inkData } = (ink?.ComponentView as InkingStroke).inkScaledData();
         // This iterates through all segments of the curve and splits them where they intersect another curve.
         // if 'excludeT' is specified, then any segment containing excludeT will be skipped (ie, deleted)
-        for (var i = 0; i < inkData.length - 3; i += 4) {
+        for (let i = 0; i < inkData.length - 3; i += 4) {
             const inkSegment = InkField.Segment(inkData, i);
             // Getting all t-value intersections of the current curve with all other curves.
             const tVals = this.getInkIntersections(i, ink, inkSegment).sort();
             if (tVals.length) {
+                // eslint-disable-next-line no-loop-func
                 tVals.forEach((t, index) => {
                     const docCurveTVal = t + Math.floor(i / 4);
                     if (excludeT < startSegmentT || excludeT > docCurveTVal) {
@@ -895,9 +926,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
                 const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
                 const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt));
-                for (var j = 0; j < otherCtrlPts.length - 3; j += 4) {
+                for (let j = 0; j < otherCtrlPts.length - 3; j += 4) {
                     const neighboringSegment = i === j || i === j - 4 || i === j + 4;
                     // Ensuring that the curve intersected by the eraser is not checked for further ink intersections.
+                    // eslint-disable-next-line no-continue
                     if (ink?.Document === otherInk.Document && neighboringSegment) continue;
 
                     const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
@@ -908,7 +940,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     if (apt.d !== undefined && apt.d < 1 && apt.t !== undefined && !tVals.includes(apt.t)) {
                         tVals.push(apt.t);
                     }
-                    this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => {
+                    this.bintersects(curve, otherCurve).forEach((val: string | number /* , i: number */) => {
                         // Converting the Bezier.js Split type to a t-value number.
                         const t = +val.toString().split('/')[0];
                         if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack!  don't know why but intersection points are doubled from bezier.js (but not identical).
@@ -971,8 +1003,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     this.scrollPan({ deltaX: -deltaX * this.screenToFreeformContentsXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToFreeformContentsXf.Scale });
                     break;
                 }
-            default:
+            // eslint-disable-next-line no-fallthrough
             case freeformScrollMode.Zoom:
+            default:
                 if ((e.ctrlKey || !scrollable) && this._props.isContentActive()) {
                     this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this._props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
                     // e.preventDefault();
@@ -982,7 +1015,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     @action
-    setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) {
+    setPan(panXIn: number, panYIn: number, panTime: number = 0, clamp: boolean = false) {
+        let panX = panXIn;
+        let panY = panYIn;
         // this is the easiest way to do this -> will talk with Bob about using mobx to do this to remove this line of code.
         if (Doc.UserDoc()?.presentationMode === 'watching') ReplayMovements.Instance.pauseFromInteraction();
 
@@ -1034,11 +1069,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 scale * NumCast(this.dataDoc._panY_max, nativeHeight) +
                 (!this._props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
             let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
-            if (false && NumCast(this.layoutDoc.layout_scrollTop) && NumCast(this.layoutDoc._freeform_scale, minScale) !== minScale) {
-            } else if (fitYscroll > 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) {
-                const maxPanY = minPanY + fitYscroll;
-                const relTop = (panY - minPanY) / (maxPanY - minPanY);
-                setTimeout(() => (this.layoutDoc.layout_scrollTop = relTop * maxScrollTop), 10);
+            if (fitYscroll > 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) {
+                const maxPanScrollY = minPanY + fitYscroll;
+                const relTop = (panY - minPanY) / (maxPanScrollY - minPanY);
+                setTimeout(() => {
+                    this.layoutDoc.layout_scrollTop = relTop * maxScrollTop;
+                }, 10);
                 newPanY = minPanY;
             }
             !this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX);
@@ -1090,7 +1126,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this._panZoomTransition = transitionTime;
         this._panZoomTransitionTimer && clearTimeout(this._panZoomTransitionTimer);
         this._panZoomTransitionTimer = setTimeout(
-            action(() => (this._panZoomTransition = 0)),
+            action(() => {
+                this._panZoomTransition = 0;
+            }),
             transitionTime
         );
     };
@@ -1173,6 +1211,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             e.stopPropagation?.();
             return this.createTextDocCopy(fieldProps, !e.altKey && e.key !== 'Tab');
         }
+        return undefined;
     };
     @computed get childPointerEvents() {
         const engine = this._props.layoutEngine?.() || StrCast(this.Document._layoutEngine);
@@ -1194,6 +1233,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const childData = entry.pair.data;
         return (
             <CollectionFreeFormDocumentView
+                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...OmitKeys(entry, ['replica', 'pair']).omit}
                 key={childLayout[Id] + (entry.replica || '')}
                 Document={childLayout}
@@ -1205,7 +1245,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 isGroupActive={this._props.isGroupActive}
                 renderDepth={this._props.renderDepth + 1}
                 hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)}
-                suppressSetHeight={this.layoutEngine ? true : false}
+                suppressSetHeight={!!this.layoutEngine}
                 RenderCutoffProvider={this.renderCutoffProvider}
                 CollectionFreeFormView={this}
                 LayoutTemplate={childLayout.z ? undefined : this._props.childLayoutTemplate}
@@ -1239,37 +1279,42 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             />
         );
     }
-    addDocTab = action((doc: Doc, where: OpenWhere) => {
-        if (this._props.isAnnotationOverlay) return this._props.addDocTab(doc, where);
+    addDocTab = action((docsIn: Doc | Doc[], where: OpenWhere) => {
+        const docs = docsIn instanceof Doc ? [docsIn] : docsIn;
+        if (this._props.isAnnotationOverlay) return this._props.addDocTab(docs, where);
         switch (where) {
             case OpenWhere.inParent:
-                return this._props.addDocument?.(doc) || false;
-            case OpenWhere.inParentFromScreen:
-                const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.embedContainer);
+                return this._props.addDocument?.(docs) || false;
+            case OpenWhere.inParentFromScreen: {
+                const docContext = DocCast(docs[0]?.embedContainer);
                 return (
                     (this.addDocument?.(
-                        (doc instanceof Doc ? [doc] : doc).map(doc => {
-                            const pt = this.screenToFreeformContentsXf.transformPoint(NumCast(doc.x), NumCast(doc.y));
-                            doc.x = pt[0];
-                            doc.y = pt[1];
+                        (docs instanceof Doc ? [docs] : docs).map(doc => {
+                            [doc.x, doc.y] = this.screenToFreeformContentsXf.transformPoint(NumCast(doc.x), NumCast(doc.y));
                             return doc;
                         })
                     ) &&
                         (!docContext || this._props.removeDocument?.(docContext))) ||
                     false
                 );
+            }
             case undefined:
             case OpenWhere.lightbox:
-                if (this.layoutDoc._isLightbox) {
-                    this._lightboxDoc = doc;
-                    return true;
-                }
-                if (doc === this.Document || this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc)) {
-                    if (doc.hidden) doc.hidden = false;
-                    return true;
+                {
+                    const firstDoc = docs[0];
+                    if (this.layoutDoc._isLightbox) {
+                        this._lightboxDoc = firstDoc;
+                        return true;
+                    }
+                    if (firstDoc === this.Document || this.childDocList?.includes(firstDoc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(firstDoc)) {
+                        if (firstDoc.hidden) firstDoc.hidden = false;
+                        return true;
+                    }
                 }
+                break;
+            default:
         }
-        return this._props.addDocTab(doc, where);
+        return this._props.addDocTab(docs, where);
     });
     @observable _lightboxDoc: Opt<Doc> = undefined;
 
@@ -1314,9 +1359,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         e.stopPropagation();
     };
 
-    viewDefsToJSX = (views: ViewDefBounds[]) => {
-        return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
-    };
+    viewDefsToJSX = (views: ViewDefBounds[]) => (!Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!));
 
     viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
         const { x, y, z } = viewDef;
@@ -1337,7 +1380,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                       ),
                       bounds: viewDef,
                   };
-        } else if (viewDef.type === 'div') {
+        }
+        if (viewDef.type === 'div') {
             return [x, y].some(val => val === undefined)
                 ? undefined
                 : {
@@ -1353,9 +1397,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                       bounds: viewDef,
                   };
         }
+        return undefined;
     }
 
     renderCutoffProvider = computedFn(
+        // eslint-disable-next-line prefer-arrow-callback
         function renderCutoffProvider(this: any, doc: Doc) {
             return this.Document.isTemplateDoc ? false : !this._renderCutoffData.get(doc[Id] + '');
         }.bind(this)
@@ -1369,7 +1415,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     doFreeformLayout(poolData: Map<string, PoolData>) {
-        this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions(pair)));
+        this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => poolData.set(pair.layout[Id], this.getCalculatedPositions(pair)));
         return [] as ViewDefResult[];
     }
 
@@ -1385,6 +1431,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             case computeTimelineLayout.name:  return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
             case computePivotLayout.name:     return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
             case computeStarburstLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeStarburstLayout) };
+            default:
         }
         return { newPool, computedElementData: this.doFreeformLayout(newPool) };
     }
@@ -1469,7 +1516,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
                 this._disposers.pointerevents = reaction(
                     () => this.childPointerEvents,
-                    pointerevents => (this._childPointerEvents = pointerevents as any),
+                    pointerevents => {
+                        this._childPointerEvents = pointerevents as any;
+                    },
                     { fireImmediately: true }
                 );
 
@@ -1643,7 +1692,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
 
-    onContextMenu = (e: React.MouseEvent) => {
+    onContextMenu = () => {
         if (this._props.isAnnotationOverlay || !ContextMenu.Instance) return;
 
         const appearance = ContextMenu.Instance.findByDescription('Appearance...');
@@ -1654,7 +1703,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
             return;
         }
-        !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
+        !Doc.noviceMode &&
+            Doc.UserDoc().defaultTextLayout &&
+            appearanceItems.push({
+                description: 'Reset default note style',
+                event: () => {
+                    Doc.UserDoc().defaultTextLayout = undefined;
+                },
+                icon: 'eye',
+            });
         appearanceItems.push({ description: `Pin View`, event: () => this._props.pinToPres(this.Document, { pinViewport: MarqueeView.CurViewBounds(this.dataDoc, this._props.PanelWidth(), this._props.PanelHeight()) }), icon: 'map-pin' });
         !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
         this._props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
@@ -1669,8 +1726,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const optionItems = options && 'subitems' in options ? options.subitems : [];
         !this._props.isAnnotationOverlay &&
             !Doc.noviceMode &&
-            optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' });
-        this._props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor)), icon: 'palette' });
+            optionItems.push({
+                description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline',
+                event: action(() => {
+                    this._showAnimTimeline = !this._showAnimTimeline;
+                }),
+                icon: 'eye',
+            });
+        this._props.renderDepth &&
+            optionItems.push({
+                description: 'Use Background Color as Default',
+                event: () => {
+                    Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor);
+                },
+                icon: 'palette',
+            });
         this._props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' });
         if (!Doc.noviceMode) {
             optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' });
@@ -1750,7 +1820,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         );
     }
 
-    showPresPaths = () => CollectionFreeFormView.ShowPresPaths;
+    showPresPaths = () => SnappingManager.ShowPresPaths;
     brushedView = () => this._brushedView;
     gridColor = () =>
         DashColor(lightOrDark(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor)))
@@ -1908,7 +1978,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 ) : (
                     <>
                         {this._firstRender ? this.placeholder : this.marqueeView}
-                        {this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
+                        {
+                            // eslint-disable-next-line no-use-before-define
+                            this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />
+                        }
                         {!this.GroupChildDrag ? null : <div className="collectionFreeForm-groupDropper" />}
                     </>
                 )}
@@ -1920,7 +1993,6 @@ 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
     }
 }
@@ -1959,6 +2031,7 @@ 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?.()));
+    return undefined;
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
@@ -1989,7 +2062,7 @@ ScriptingGlobals.add(function datavizFromSchema() {
         if (!view.layoutDoc.schema_columnKeys) {
             view.layoutDoc.schema_columnKeys = new List<string>(['title', 'type', 'author', 'author_date']);
         }
-        const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
+        const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key !== 'text');
         if (!keys) return;
 
         const children = DocListCast(view.Document[Doc.LayoutFieldKey(view.Document)]);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b03e435ce..2f9cc49e0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -196,7 +196,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         const eachCell = ns.join('\t').split('\t');
         let eachRow = [];
         for (let i = 1; i < eachCell.length; i++) {
-            eachRow.push(eachCell[i].replace(/\,/g, ''));
+            eachRow.push(eachCell[i].replace(/,/g, ''));
             if (i % headers.length === 0) {
                 csvRows.push(eachRow);
                 eachRow = [];
@@ -377,7 +377,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     });
 
     @undoBatch
-    pileup = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
+    pileup = action(() => {
         const selected = this.marqueeSelect(false);
         SelectionManager.DeselectAll();
         selected.forEach(d => this._props.removeDocument?.(d));
@@ -485,7 +485,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     });
 
     @undoBatch
-    summary = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
+    summary = action(() => {
         const selected = this.marqueeSelect(false).map(d => {
             this._props.removeDocument?.(d);
             d.x = NumCast(d.x) - this.Bounds.left;
@@ -530,8 +530,8 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             (e as any).propagationIsStopped = true;
             if (e.key === 'g') this.collection(e, true);
             if (e.key === 'c' || e.key === 't') this.collection(e);
-            if (e.key === 's' || e.key === 'S') this.summary(e);
-            if (e.key === 'p') this.pileup(e);
+            if (e.key === 's' || e.key === 'S') this.summary();
+            if (e.key === 'p') this.pileup();
             this.cleanupInteractions(false);
         }
         if (e.key === 'r' || e.key === ' ') {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 08eb35586..ee30006ae 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/alt-text */
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable no-use-before-define */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Popup, Size, Type } from 'browndash-components';
 import { action, computed, makeObservable, observable } from 'mobx';
@@ -26,7 +29,7 @@ import { EditableView } from '../../EditableView';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DefaultStyleProvider } from '../../StyleProvider';
 import { Colors } from '../../global/globalEnums';
-import { OpenWhere, returnEmptyDocViewList } from '../../nodes/DocumentView';
+import { returnEmptyDocViewList } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
@@ -64,8 +67,8 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
         makeObservable(this);
     }
 
-    static addFieldDoc = (doc: Doc, where: OpenWhere) => {
-        DocFocusOrOpen(doc);
+    static addFieldDoc = (doc: Doc | Doc[] /* , where: OpenWhere */) => {
+        DocFocusOrOpen(doc instanceof Doc ? doc : doc[0]);
         return true;
     };
     public static renderProps(props: SchemaTableCellProps) {
@@ -186,7 +189,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
         return (
             <div
                 className="schema-table-cell"
-                onPointerDown={action(e => !this.selected && this._props.selectCell(this._props.Document, this._props.col))}
+                onPointerDown={action(() => !this.selected && this._props.selectCell(this._props.Document, this._props.col))}
                 style={{ padding: this._props.padding, maxWidth: this._props.maxWidth?.(), width: this._props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
                 {this.content}
             </div>
@@ -207,7 +210,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 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
+        if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; // Why is this here — good question
 
         const ext = extname(url.href);
         return url.href.replace(ext, '_s' + ext);
@@ -246,7 +249,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
     };
 
     @action
-    removeHoverPreview = (e: React.PointerEvent) => {
+    removeHoverPreview = () => {
         if (!this._previewRef) return;
         document.body.removeChild(this._previewRef);
     };
@@ -258,7 +261,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
         const height = this._props.rowHeight() ? this._props.rowHeight() - (this._props.padding || 6) * 2 : undefined;
         const width = height ? height * aspect : undefined; // increase the width of the image if necessary to maintain proportionality
 
-        return <img src={this.url} width={width ? width : undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
+        return <img src={this.url} width={width || undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
     }
 }
 
@@ -282,15 +285,15 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
         // } else {
         // ^ DateCast is always undefined for some reason, but that is what the field should be set to
         date && (this._props.Document[this._props.fieldKey] = new DateField(date));
-        //}
+        // }
     }, 'date change');
 
     render() {
-        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
+        const { pointerEvents } = SchemaTableCell.renderProps(this._props);
         return (
             <>
                 <div style={{ pointerEvents: 'none' }}>
-                    <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={e => {}} />
+                    <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={emptyFunction} />
                 </div>
                 {pointerEvents === 'none' ? null : (
                     <Popup
@@ -301,7 +304,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
                         background={SettingsManager.userBackgroundColor}
                         popup={
                             <div style={{ width: 'fit-content', height: '200px' }}>
-                                <DatePicker open={true} dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} />
+                                <DatePicker open dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} />
                             </div>
                         }
                     />
@@ -329,7 +332,7 @@ export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps
         fieldProps.isContentActive = this.selectedFunc;
         return (
             <div className="schemaRTFCell" style={{ fontStyle: this.selected ? undefined : 'italic', color, textDecoration, cursor, pointerEvents }}>
-                {this.selected ? <FormattedTextBox {...fieldProps} autoFocus={true} onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+                {this.selected ? <FormattedTextBox {...fieldProps} autoFocus onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
             </div>
         );
     }
@@ -355,8 +358,8 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp
                     checked={BoolCast(this._props.Document[this._props.fieldKey])}
                     onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => {
                         if ((value?.nativeEvent as any).shiftKey) {
-                            this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
-                        } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+                            this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? ''));
+                        } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? ''));
                     })}
                 />
                 <EditableView
@@ -391,7 +394,7 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC
         return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
     }
     render() {
-        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
+        const { color, textDecoration, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
         const options = this._props.options?.map(facet => ({ value: facet, label: facet }));
         return (
             <div className="schemaSelectionCell" style={{ color, textDecoration, cursor, pointerEvents }}>
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 50cf26cdb..231bac541 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -21,6 +21,7 @@ import { ImageBox } from '../nodes/ImageBox';
 import { VideoBox } from '../nodes/VideoBox';
 import { WebBox } from '../nodes/WebBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+
 // import { InkTranscription } from '../InkTranscription';
 
 // eslint-disable-next-line prefer-arrow-callback
@@ -162,15 +163,15 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
     const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => any; setDoc: () => void;}> = new Map([
         ['font', {
             checkResult: () => RichTextMenu.Instance?.fontFamily,
-            setDoc: () => value && RichTextMenu.Instance?.setFontFamily(value),
+            setDoc: () => value && RichTextMenu.Instance?.setFontField(value, 'fontFamily'),
         }],
         ['highlight', {
             checkResult: () => RichTextMenu.Instance?.fontHighlight,
-            setDoc: () => value  && RichTextMenu.Instance?.setHighlight(value),
+            setDoc: () => value  && RichTextMenu.Instance?.setFontField(value, 'fontHighlight'),
         }],
         ['fontColor', {
             checkResult: () => RichTextMenu.Instance?.fontColor,
-            setDoc: () => value && RichTextMenu.Instance?.setColor(value),
+            setDoc: () => value && RichTextMenu.Instance?.setFontField(value, 'fontColor'),
         }],
         ['alignment', {
             checkResult: () => RichTextMenu.Instance?.textAlign,
@@ -182,7 +183,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
                 let fsize = value;
                 if (typeof fsize === 'number') fsize = fsize.toString();
                 if (fsize && Number(fsize).toString() === fsize) fsize += 'px';
-                RichTextMenu.Instance?.setFontSize(fsize);
+                RichTextMenu.Instance?.setFontField(fsize, 'fontSize');
             },
         }],
     ]);
@@ -207,7 +208,7 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?:
                                                                              (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):
+                        toggle: () => { editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle():
                                                                                  RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)):
                                                             where === 'vcent' ?  Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered:
                                                                                 (Doc.UserDoc().textAlign = where); }
@@ -470,7 +471,7 @@ ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
     if (checkResult) {
         return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily);
     }
-    if (editorView) RichTextMenu.Instance?.setFontFamily(key);
+    if (editorView) RichTextMenu.Instance?.setFontField(key, 'fontFamily');
     else Doc.UserDoc().fontFamily = key;
     return undefined;
 });
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 38ef08ef9..9be78a6cb 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-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 { Tooltip } from '@mui/material';
@@ -36,14 +38,14 @@ interface LinkMenuItemProps {
 
 // drag links and drop link targets (embedding them if needed)
 export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
-    const draggedDocs = (specificLinks ? specificLinks : LinkManager.Links(sourceDoc)).map(link => LinkManager.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
+    const draggedDocs = (specificLinks || LinkManager.Links(sourceDoc)).map(link => LinkManager.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
 
     if (draggedDocs.length) {
         const moddrag: Doc[] = [];
-        for (const draggedDoc of draggedDocs) {
+        draggedDocs.forEach(async draggedDoc => {
             const doc = await Cast(draggedDoc.annotationOn, Doc);
             if (doc) moddrag.push(doc);
-        }
+        });
 
         const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
         dragData.canEmbed = true;
@@ -108,7 +110,11 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                     LinkManager.Instance.currentLinkAnchor = LinkManager.Instance.currentLink ? this.sourceAnchor : undefined;
 
                     if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
-                        setTimeout(action(() => (SettingsManager.Instance.propertiesWidth = 250)));
+                        setTimeout(
+                            action(() => {
+                                SettingsManager.Instance.propertiesWidth = 250;
+                            })
+                        );
                     }
                 }
             })
@@ -166,8 +172,12 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
         return (
             <div
                 className="linkMenu-item"
-                onPointerEnter={action(e => (this._hover = true))}
-                onPointerLeave={action(e => (this._hover = false))}
+                onPointerEnter={action(() => {
+                    this._hover = true;
+                })}
+                onPointerLeave={action(() => {
+                    this._hover = false;
+                })}
                 style={{
                     fontSize: this._hover ? 'larger' : undefined,
                     fontWeight: this._hover ? 'bold' : undefined,
@@ -176,15 +186,15 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                 <div className="linkMenu-item-content expand-two">
                     <div
                         ref={this._drag}
-                        className="linkMenu-name" //title="drag to view target. click to customize."
+                        className="linkMenu-name" // title="drag to view target. click to customize."
                         onPointerDown={this.onLinkButtonDown}>
                         <div className="linkMenu-item-buttons">
-                            <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Edit Link</div>}>
+                            <Tooltip disableInteractive title={<div className="dash-tooltip">Edit Link</div>}>
                                 <div className="linkMenu-icon-wrapper" ref={this._editRef} onPointerDown={this.onEdit} onClick={e => e.stopPropagation()}>
                                     <FontAwesomeIcon className="linkMenu-icon" icon="edit" size="sm" />
                                 </div>
                             </Tooltip>
-                            <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Show/Hide Link</div>}>
+                            <Tooltip disableInteractive title={<div className="dash-tooltip">Show/Hide Link</div>}>
                                 <div className="linkMenu-icon-wrapper" onPointerDown={this.onIconDown}>
                                     <FontAwesomeIcon className="linkMenu-icon" icon={destinationIcon} size="sm" />
                                 </div>
@@ -213,7 +223,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                                 </p>
                             ) : null}
                             <div className="linkMenu-title-wrapper">
-                                <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Follow Link</div>}>
+                                <Tooltip disableInteractive title={<div className="dash-tooltip">Follow Link</div>}>
                                     <p className="linkMenu-destination-title">
                                         {this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
                                     </p>
@@ -223,7 +233,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                         </div>
 
                         <div className="linkMenu-item-buttons">
-                            <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Delete Link</div>}>
+                            <Tooltip disableInteractive title={<div className="dash-tooltip">Delete Link</div>}>
                                 <div className="linkMenu-deleteButton" onPointerDown={this.deleteLink} onClick={e => e.stopPropagation()}>
                                     <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" />
                                 </div>
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index f2681bf2e..9fb1c0fdc 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -1,15 +1,12 @@
-import { action, observable } from 'mobx';
+/* eslint-disable react/require-default-props */
 import { observer } from 'mobx-react';
-import { EditorView } from 'prosemirror-view';
 import * as React from 'react';
-import { emptyFunction } from '../../../Utils';
 import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { Transform } from '../../util/Transform';
-import { undoBatch } from '../../util/UndoManager';
 import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere, returnEmptyDocViewList } from '../nodes/DocumentView';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { returnEmptyDocViewList } from '../nodes/DocumentView';
 import { SearchBox } from '../search/SearchBox';
 import './LinkPopup.scss';
 
@@ -29,16 +26,6 @@ interface LinkPopupProps {
 
 @observer
 export class LinkPopup extends React.Component<LinkPopupProps> {
-    @observable private linkURL: string = '';
-    @observable public view?: EditorView = undefined;
-
-    // TODO: should check for valid URL
-    @undoBatch
-    makeLinkToURL = (target: string, lcoation: string) => ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, OpenWhere.addRight, target, target);
-
-    @action
-    onLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => (this.linkURL = e.target.value);
-
     getPWidth = () => 500;
     getPHeight = () => 500;
 
@@ -65,7 +52,7 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
                         docViewPath={returnEmptyDocViewList}
                         linkFrom={linkDoc}
                         linkCreateAnchor={this.props.linkCreateAnchor}
-                        linkSearch={true}
+                        linkSearch
                         linkCreated={this.props.linkCreated}
                         fieldKey="data"
                         isSelected={returnTrue}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 958d63267..6a86af6a7 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -97,7 +97,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         if (this.props.transition && !this.Document[TransitionTimer]) {
             const num = Number(this.props.transition.match(/([0-9.]+)s/)?.[1]) * 1000 || Number(this.props.transition.match(/([0-9.]+)ms/)?.[1]);
             this.Document[TransitionTimer] = setTimeout(
-                action(() => (this.Document[TransitionTimer] = this.Transition = undefined)),
+                action(() => {
+                    this.Document[TransitionTimer] = this.Transition = undefined;
+                }),
                 num
             );
         }
@@ -105,7 +107,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewProps & freeFormProps>>) {
         super.componentDidUpdate(prevProps);
-        this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
+        this.WrapperKeys.forEach(
+            action(keys => {
+                (this as any)[keys.upper] = (this.props as any)[keys.lower];
+            })
+        );
     }
 
     CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
@@ -121,6 +127,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 case StyleProp.Opacity:         return this.Opacity; // only change the opacity for this specific document, not its children
                 case StyleProp.BackgroundColor: return this.BackgroundColor;
                 case StyleProp.Color:           return this.Color;
+                default:
             } // prettier-ignore
         }
         return this._props.styleProvider?.(doc, props, property);
@@ -129,7 +136,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static getValues(doc: Doc, time: number, fillIn: boolean = true) {
         return CollectionFreeFormDocumentView.animFields.reduce(
             (p, val) => {
-                p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
+                p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce(
+                    (prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev),
+                    undefined as any as number
+                );
                 return p;
             },
             {} as { [val: string]: Opt<number> }
@@ -139,7 +149,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static getStringValues(doc: Doc, time: number) {
         return CollectionFreeFormDocumentView.animStringFields.reduce(
             (p, val) => {
-                p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
+                p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as any as string);
                 return p;
             },
             {} as { [val: string]: Opt<string> }
@@ -179,15 +189,21 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) {
         docs.forEach(doc => {
             if (doc.appearFrame === undefined) doc.appearFrame = currTimecode;
-            if (!doc['opacity_indexed']) {
+            if (!doc.opacity_indexed) {
                 // opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in
-                doc['opacity_indexed'] = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
+                doc.opacity_indexed = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
             }
-            CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val.key] = ComputedField.MakeInterpolatedNumber(val.key, 'activeFrame', doc, currTimecode, val.val)));
-            CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode)));
-            CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => (doc[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', doc, currTimecode)));
+            CollectionFreeFormDocumentView.animFields.forEach(val => {
+                doc[val.key] = ComputedField.MakeInterpolatedNumber(val.key, 'activeFrame', doc, currTimecode, val.val);
+            });
+            CollectionFreeFormDocumentView.animStringFields.forEach(val => {
+                doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode);
+            });
+            CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => {
+                doc[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', doc, currTimecode);
+            });
             const targetDoc = doc; // data fields, like rtf 'text' exist on the data doc, so
-            //doc !== targetDoc && (targetDoc.embedContainer = doc.embedContainer); // the computed fields don't see the layout doc -- need to copy the embedContainer to the data doc (HACK!!!) and set the activeFrame on the data doc (HACK!!!)
+            // doc !== targetDoc && (targetDoc.embedContainer = doc.embedContainer); // the computed fields don't see the layout doc -- need to copy the embedContainer to the data doc (HACK!!!) and set the activeFrame on the data doc (HACK!!!)
             targetDoc.activeFrame = ComputedField.MakeFunction('this.embedContainer?._currentFrame||0');
             targetDoc.dataTransition = 'inherit';
         });
@@ -202,16 +218,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             if (topDoc.z) {
                 const spt = screenXf.inverse().transformPoint(NumCast(topDoc.x), NumCast(topDoc.y));
                 topDoc.z = 0;
-                topDoc.x = spt[0];
-                topDoc.y = spt[1];
+                [topDoc.x, topDoc.y] = spt;
                 this._props.removeDocument?.(topDoc);
                 this._props.addDocTab(topDoc, OpenWhere.inParentFromScreen);
             } else {
                 const spt = this.screenToLocalTransform().inverse().transformPoint(0, 0);
                 const fpt = screenXf.transformPoint(spt[0], spt[1]);
                 topDoc.z = 1;
-                topDoc.x = fpt[0];
-                topDoc.y = fpt[1];
+                [topDoc.x, topDoc.y] = fpt;
             }
             setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, containerDocView), false), 0);
         }
@@ -259,6 +273,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     <div style={{ position: 'absolute', width: this.PanelWidth(), height: this.PanelHeight(), background: 'lightGreen' }} />
                 ) : (
                     <DocumentView
+                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore
                         DataTransition={this.DataTransition}
                         LocalRotation={this.localRotation}
@@ -274,6 +289,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         );
     }
 }
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) {
     CollectionFreeFormDocumentView.gotoKeyFrame(doc, newFrame);
 });
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 708536de0..c1aa1c699 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, makeObservable, observable, trace } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
@@ -69,7 +69,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 e,
                 this.onPointerMove,
                 emptyFunction,
-                action((e, doubleTap) => {
+                action((moveEv, doubleTap) => {
                     if (doubleTap) {
                         this._isAnyChildContentActive = true;
                         if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
@@ -140,14 +140,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         setupMoveUpEvents(
             this,
             e,
-            e => {
+            moveEv => {
                 const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], dropActionType.move);
                 de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
                     this.clearDoc(which);
                     return addDocument(doc);
                 };
                 de.canEmbed = true;
-                DragManager.StartDocumentDrag([this._closeRef.current!], de, e.clientX, e.clientY);
+                DragManager.StartDocumentDrag([this._closeRef.current!], de, moveEv.clientX, moveEv.clientY);
                 return true;
             },
             emptyFunction,
@@ -158,10 +158,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         if (property === StyleProp.PointerEvents) return 'none';
         return this._props.styleProvider?.(doc, props, property);
     };
-    moveDoc1 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
-    moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
-    remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
-    remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
+    moveDoc1 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (docs instanceof Doc ? [docs] : docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
+    moveDoc2 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (docs instanceof Doc ? [docs] : docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
+    remDoc1 = (docs: Doc | Doc[]) => (docs instanceof Doc ? [docs] : docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
+    remDoc2 = (docs: Doc | Doc[]) => (docs instanceof Doc ? [docs] : docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
 
     /**
      * Tests for whether a comparison box slot (ie, before or after) has renderable text content
@@ -196,7 +196,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
     _closeRef = React.createRef<HTMLDivElement>();
     render() {
-        trace();
         const clearButton = (which: string) => (
             <div
                 ref={this._closeRef}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 2a0bc42ee..b87fead4f 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/jsx-props-no-spreading */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Checkbox } from '@mui/material';
 import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
@@ -9,7 +10,6 @@ import { emptyFunction } from '../../../../Utils';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
-import { listSpec } from '../../../../fields/Schema';
 import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { CsvField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
@@ -62,9 +62,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
             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,
@@ -95,7 +95,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
 
     // all CSV records in the dataset (that aren't an empty row)
     @computed.struct get records() {
-        var records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+        const records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
         return records?.filter(record => Object.keys(record).some(key => record[key])) ?? [];
     }
 
@@ -110,11 +110,15 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
     @computed.struct get axes() {
         return StrListCast(this.layoutDoc._dataViz_axes);
     }
-    selectAxes = (axes: string[]) => (this.layoutDoc._dataViz_axes = new List<string>(axes));
+    selectAxes = (axes: string[]) => {
+        this.layoutDoc._dataViz_axes = new List<string>(axes);
+    };
     @computed.struct get titleCol() {
         return StrCast(this.layoutDoc._dataViz_titleCol);
     }
-    selectTitleCol = (titleCol: string) => (this.layoutDoc._dataViz_titleCol = titleCol);
+    selectTitleCol = (titleCol: string) => {
+        this.layoutDoc._dataViz_titleCol = titleCol;
+    };
 
     @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
     restoreView = (data: Doc) => {
@@ -124,7 +128,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
         this.layoutDoc.dataViz_histogram_barColors = Field.Copy(data.dataViz_histogram_barColors);
         this.layoutDoc.dataViz_histogram_defaultColor = data.dataViz_histogram_defaultColor;
         this.layoutDoc.dataViz_pie_sliceColors = Field.Copy(data.dataViz_pie_sliceColors);
-        Object.keys(this.layoutDoc).map(key => {
+        Object.keys(this.layoutDoc).forEach(key => {
             if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) {
                 this.layoutDoc['_' + key] = data[key];
             }
@@ -150,7 +154,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
                   annotationOn: this.Document,
                   // when we clear selection -> we should have it so chartBox getAnchor returns undefined
                   // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
-                  /*put in some options*/
+                  /* put in some options */
               });
         anchor.config_dataViz = this.dataVizView;
         anchor.config_dataVizAxes = this.axes.length ? new List<string>(this.axes) : undefined;
@@ -158,24 +162,21 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
         anchor.dataViz_histogram_barColors = Field.Copy(this.layoutDoc.dataViz_histogram_barColors);
         anchor.dataViz_histogram_defaultColor = this.layoutDoc.dataViz_histogram_defaultColor;
         anchor.dataViz_pie_sliceColors = Field.Copy(this.layoutDoc.dataViz_pie_sliceColors);
-        Object.keys(this.layoutDoc).map(key => {
+        Object.keys(this.layoutDoc).forEach(key => {
             if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) {
                 anchor[key] = this.layoutDoc[key];
             }
         });
 
         this.addDocument(anchor);
-        //addAsAnnotation && this.addDocument(anchor);
+        // addAsAnnotation && this.addDocument(anchor);
         return anchor;
     };
 
     createNoteAnnotation = () => {
-        const createFunc = undoable(
-            action(() => {
-                const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']);
-            }),
-            'create note annotation'
-        );
+        const createFunc = undoable(() => {
+            this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']);
+        }, 'create note annotation');
         if (!this.layoutDoc.layout_showSidebar) {
             this.toggleSidebar();
             setTimeout(createFunc);
@@ -192,7 +193,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
         this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
     };
     @computed get SidebarShown() {
-        return this.layoutDoc._layout_showSidebar ? true : false;
+        return !!this.layoutDoc._layout_showSidebar;
     }
     @computed get sidebarHandle() {
         return (
@@ -206,7 +207,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
                     backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                 }}
                 onPointerDown={this.sidebarBtnDown}>
-                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
             </div>
         );
     }
@@ -218,7 +219,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
         setupMoveUpEvents(
             this,
             e,
-            (e, down, delta) =>
+            (moveEv, down, delta) =>
                 runInAction(() => {
                     const localDelta = this._props
                         .ScreenToLocalTransform()
@@ -246,7 +247,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
             options.didMove = true;
             this.toggleSidebar();
         }
-        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));
+        });
     };
     @computed get sidebarWidthPercent() {
         return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
@@ -266,32 +269,31 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
         if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
         this._disposers.datavis = reaction(
             () => {
-                if (this.layoutDoc.dataViz_schemaLive == undefined) this.layoutDoc.dataViz_schemaLive = true;
+                if (this.layoutDoc.dataViz_schemaLive === undefined) this.layoutDoc.dataViz_schemaLive = true;
                 const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
-                const keys = Cast(getFrom?.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
-                if (!keys) return;
-                const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]);
-                var current: { [key: string]: string }[] = [];
+                const keys = StrListCast(getFrom?.schema_columnKeys).filter(key => key !== 'text');
+                const children = DocListCast(getFrom?.[Doc.LayoutFieldKey(getFrom)]);
+                const current: { [key: string]: string }[] = [];
                 children
                     .filter(child => child)
                     .forEach(child => {
                         const row: { [key: string]: string } = {};
                         keys.forEach(key => {
-                            var cell = child[key];
-                            if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+                            let cell = child[key];
+                            if (cell && (cell as string)) cell = cell.toString().replace(/,/g, '');
                             row[key] = StrCast(cell);
                         });
                         current.push(row);
                     });
                 if (!this.layoutDoc._dataViz_schemaOG) {
                     // makes a copy of the original table for the "live" toggle
-                    let csvRows = [];
+                    const csvRows = [];
                     csvRows.push(keys.join(','));
                     for (let i = 0; i < children.length - 1; i++) {
-                        let eachRow = [];
+                        const eachRow = [];
                         for (let j = 0; j < keys.length; j++) {
-                            var cell = children[i][keys[j]];
-                            if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+                            let cell = children[i][keys[j]];
+                            if (cell && (cell as string)) cell = cell.toString().replace(/,/g, '');
                             eachRow.push(cell);
                         }
                         csvRows.push(eachRow);
@@ -305,19 +307,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
                 }
                 const ogDoc = this.layoutDoc._dataViz_schemaOG as Doc;
                 const ogHref = CsvCast(ogDoc[this.fieldKey]) ? CsvCast(ogDoc[this.fieldKey]).url.href : undefined;
-                const href = CsvCast(this.Document[this.fieldKey]).url.href;
+                const { href } = CsvCast(this.Document[this.fieldKey]).url;
                 if (ogHref && !DataVizBox.datasetSchemaOG.has(href)) {
                     // sets original dataset to the var
                     const lastRow = current.pop();
                     DataVizBox.datasetSchemaOG.set(href, current);
                     current.push(lastRow!);
-                    fetch('/csvData?uri=' + ogHref).then(res => res.json().then(action(res => !res.errno && DataVizBox.datasetSchemaOG.set(href, res))));
+                    fetch('/csvData?uri=' + ogHref).then(res => res.json().then(action(jsonRes => !jsonRes.errno && DataVizBox.datasetSchemaOG.set(href, jsonRes))));
                 }
                 return current;
             },
             current => {
                 if (current) {
-                    const href = CsvCast(this.Document[this.fieldKey]).url.href;
+                    const { href } = CsvCast(this.Document[this.fieldKey]).url;
                     if (this.layoutDoc.dataViz_schemaLive) DataVizBox.dataset.set(href, current);
                     else DataVizBox.dataset.set(href, DataVizBox.datasetSchemaOG.get(href)!);
                 }
@@ -329,8 +331,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
     fetchData = () => {
         if (!this.Document.dataViz_asSchema) {
             DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
-            fetch('/csvData?uri=' + this.dataUrl?.url.href) //
-                .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, res))));
+            fetch('/csvData?uri=' + (this.dataUrl?.url.href ?? '')) //
+                .then(res => res.json().then(action(jsonRes => !jsonRes.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, jsonRes))));
         }
     };
 
@@ -343,7 +345,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
             records: this.records,
             axes: this.axes,
             titleCol: this.titleCol,
-            //width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
+            // width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
             height: (this._props.PanelHeight() / scale - 32) /* height of 'change view' button */ * 0.9,
             width: ((this._props.PanelWidth() - this.sidebarWidth()) / scale) * 0.9,
             margin: { top: 10, right: 25, bottom: 75, left: 45 },
@@ -351,11 +353,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
         if (!this.records.length) return 'no data/visualization';
         switch (this.dataVizView) {
             case DataVizView.TABLE:     return <TableBox  {...sharedProps} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
-            case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} vizBox={this} />;
-            case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
-            case DataVizView.PIECHART:  return <PieChart  {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} 
+            case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} vizBox={this} />;
+            case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} />;
+            case DataVizView.PIECHART:  return <PieChart  {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} 
             margin={{ top: 10, right: 15, bottom: 15, left: 15 }} />;
+            default:
         } // prettier-ignore
+        return null;
     }
 
     @action
@@ -367,10 +371,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
             this._marqueeing = [e.clientX, e.clientY];
             const target = e.target as any;
             if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
+                /* empty */
             } else {
                 // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
                 setTimeout(
-                    action(() => (this._marqueeing = undefined)),
+                    action(() => {
+                        this._marqueeing = undefined;
+                    }),
                     100
                 ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
 
@@ -405,9 +412,21 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
 
     render() {
         const scale = this._props.NativeDimScaling?.() || 1;
+        const toggleBtn = (name: string, type: DataVizView) => (
+            <Toggle
+                text={name}
+                toggleType={ToggleType.BUTTON}
+                type={Type.SEC}
+                color="black"
+                onClick={() => {
+                    this.layoutDoc._dataViz = type;
+                }}
+                toggleStatus={this.layoutDoc._dataViz === type}
+            />
+        );
         return !this.records.length ? (
             // displays how to get data into the DataVizBox if its empty
-            <div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command 'ctrl + p' to bring the data table to your canvas.</div>
+            <div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command (ctrl + p) to bring the data table to your canvas.</div>
         ) : (
             <div
                 className="dataViz-box"
@@ -422,14 +441,14 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
                 onWheel={e => e.stopPropagation()}
                 ref={this._mainCont}>
                 <div className="datatype-button">
-                    <Toggle text={'  TABLE  '} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz === DataVizView.TABLE} />
-                    <Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz === DataVizView.LINECHART} />
-                    <Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz === DataVizView.HISTOGRAM} />
-                    <Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == -DataVizView.PIECHART} />
+                    {toggleBtn('  TABLE  ', DataVizView.TABLE)}
+                    {toggleBtn('LINECHART', DataVizView.LINECHART)}
+                    {toggleBtn('HISTOGRAM', DataVizView.HISTOGRAM)}
+                    {toggleBtn('PIE CHART', DataVizView.PIECHART)}
                 </div>
 
                 {this.layoutDoc && this.layoutDoc.dataViz_asSchema ? (
-                    <div className={'liveSchema-checkBox'} style={{ width: this._props.width }}>
+                    <div className="liveSchema-checkBox" style={{ width: this._props.width }}>
                         <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
                         Display Live Updates to Canvas
                     </div>
@@ -445,7 +464,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
                         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/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 58cacef76..f0ffdbdcf 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -64,14 +64,13 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
     @computed get _histogramData() {
         if (this._props.axes.length < 1) return [];
         if (this._props.axes.length < 2) {
-            var ax0 = this._props.axes[0];
+            const ax0 = this._props.axes[0];
             if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) {
                 this.numericalXData = true;
             }
             return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]] }));
         }
-        var ax0 = this._props.axes[0];
-        var ax1 = this._props.axes[1];
+        const [ax0, ax1] = this._props.axes;
         if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) {
             this.numericalXData = true;
         }
@@ -82,11 +81,11 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
     }
 
     @computed get defaultGraphTitle() {
-        var ax0 = this._props.axes[0];
-        var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+        const [ax0, ax1] = this._props.axes;
         if (this._props.axes.length < 2 || !ax1 || !/\d/.test(this._props.records[0][ax1]) || !this.numericalYData) {
             return ax0 + ' Histogram';
-        } else return ax0 + ' by ' + ax1 + ' Histogram';
+        }
+        return ax0 + ' by ' + ax1 + ' Histogram';
     }
 
     @computed get parentViz() {
@@ -112,8 +111,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
         );
     }
 
-    @action
-    restoreView = (data: Doc) => {};
+    restoreView = () => {};
     // 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({
@@ -133,36 +131,36 @@ 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] || 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
             ? []
             : validData.map((d: { [x: string]: any }) =>
                   !this.numericalXData //
                       ? d[field]
-                      : +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')
+                      : +d[field!].replace(/\$/g, '').replace(/%/g, '').replace(/</g, '')
               );
     };
 
     // outlines the bar selected / hovered over
     highlightSelectedBar = (changeSelectedVariables: boolean, svg: any, eachRectWidth: any, pointerX: any, xAxisTitle: any, yAxisTitle: any, histDataSet: any) => {
-        var sameAsCurrent: boolean;
-        var barCounter = -1;
+        let sameAsCurrent: boolean;
+        let barCounter = -1;
         const selected = svg.selectAll('.histogram-bar').filter((d: any) => {
             barCounter++; // uses the order of bars and width of each bar to find which one the pointer is over
             if (barCounter * eachRectWidth <= pointerX && pointerX <= (barCounter + 1) * eachRectWidth) {
-                var showSelected = this.numericalYData
-                    ? this._histogramData.filter((data: { [x: string]: any }) => StrCast(data[xAxisTitle]).replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') == d[0])[0]
-                    : histDataSet.filter((data: { [x: string]: any }) => data[xAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') == d[0])[0];
+                let showSelected = this.numericalYData
+                    ? this._histogramData.filter((data: { [x: string]: any }) => StrCast(data[xAxisTitle]).replace(/$/g, '').replace(/%/g, '').replace(/</g, '') === d[0])[0]
+                    : histDataSet.filter((data: { [x: string]: any }) => data[xAxisTitle].replace(/$/g, '').replace(/%/g, '').replace(/</g, '') === d[0])[0];
                 if (this.numericalXData) {
                     // calculating frequency
-                    if (d[0] && d[1] && d[0] != d[1]) {
+                    if (d[0] && d[1] && d[0] !== d[1]) {
                         showSelected = { [xAxisTitle]: d3.min(d) + ' to ' + d3.max(d), frequency: d.length };
                     } else if (!this.numericalYData) showSelected = { [xAxisTitle]: showSelected[xAxisTitle], frequency: d.length };
                 }
                 if (changeSelectedVariables) {
                     // for when a bar is selected - not just hovered over
-                    sameAsCurrent = this._currSelected ? showSelected[xAxisTitle] == this._currSelected![xAxisTitle] && showSelected[yAxisTitle] == this._currSelected![yAxisTitle] : false;
+                    sameAsCurrent = this._currSelected ? showSelected[xAxisTitle] === this._currSelected![xAxisTitle] && showSelected[yAxisTitle] === this._currSelected![yAxisTitle] : false;
                     this._currSelected = sameAsCurrent ? undefined : showSelected;
                     this.selectedData = sameAsCurrent ? undefined : d;
                 } else this.hoverOverData = d;
@@ -185,16 +183,16 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
         const xAxisTitle = Object.keys(dataSet[0])[0];
         const yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency';
         const uniqueArr: unknown[] = [...new Set(data)];
-        var numBins = this.numericalXData && Number.isInteger(data[0]) ? this.rangeVals.xMax! - this.rangeVals.xMin! : uniqueArr.length;
-        var translateXAxis = !this.numericalXData || numBins < this.maxBins ? width / (numBins + 1) / 2 : 0;
+        let numBins = this.numericalXData && Number.isInteger(data[0]) ? this.rangeVals.xMax! - this.rangeVals.xMin! : uniqueArr.length;
+        let translateXAxis = !this.numericalXData || numBins < this.maxBins ? width / (numBins + 1) / 2 : 0;
         if (numBins > this.maxBins) numBins = this.maxBins;
         const startingPoint = this.numericalXData ? this.rangeVals.xMin! : 0;
         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] || isNaN(d[key])));
+        let 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 }[] = [];
+            const histStringDataSet: { [x: string]: unknown }[] = [];
             if (this.numericalYData) {
                 for (let i = 0; i < dataSet.length; i++) {
                     histStringDataSet.push({ [yAxisTitle]: dataSet[i][yAxisTitle], [xAxisTitle]: dataSet[i][xAxisTitle] });
@@ -204,15 +202,15 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
                     histStringDataSet.push({ [yAxisTitle]: 0, [xAxisTitle]: uniqueArr[i] });
                 }
                 for (let i = 0; i < data.length; i++) {
-                    let barData = histStringDataSet.filter(each => each[xAxisTitle] == data[i]);
-                    histStringDataSet.filter(each => each[xAxisTitle] == data[i])[0][yAxisTitle] = Number(barData[0][yAxisTitle]) + 1;
+                    const barData = histStringDataSet.filter(each => each[xAxisTitle] === data[i]);
+                    histStringDataSet.filter(each => each[xAxisTitle] === data[i])[0][yAxisTitle] = Number(barData[0][yAxisTitle]) + 1;
                 }
             }
             histDataSet = histStringDataSet;
         }
 
         // initial graph and binning data for histogram
-        var svg = (this._histogramSvg = d3
+        const svg = (this._histogramSvg = d3
             .select(this._histogramRef.current)
             .append('svg')
             .attr('class', 'graph')
@@ -220,23 +218,21 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
             .attr('height', height + this._props.margin.top + this._props.margin.bottom)
             .append('g')
             .attr('transform', 'translate(' + this._props.margin.left + ',' + this._props.margin.top + ')'));
-        var x = d3
+        let x = d3
             .scaleLinear()
             .domain(this.numericalXData ? [startingPoint!, endingPoint!] : [0, numBins])
             .range([0, width]);
-        var histogram = d3
+        const histogram = d3
             .histogram()
-            .value(function (d) {
-                return d;
-            })
+            .value(d => d)
             .domain([startingPoint!, endingPoint!])
             .thresholds(x.ticks(numBins));
-        var bins = histogram(data);
-        var eachRectWidth = width / bins.length;
-        var graphStartingPoint = bins[0].x1 && bins[1] ? bins[0].x1! - (bins[1].x1! - bins[1].x0!) : 0;
+        const bins = histogram(data);
+        let eachRectWidth = width / bins.length;
+        const graphStartingPoint = bins[0].x1 && bins[1] ? bins[0].x1! - (bins[1].x1! - bins[1].x0!) : 0;
         bins[0].x0 = graphStartingPoint;
         x = x.domain([graphStartingPoint, endingPoint]).range([0, Number.isInteger(this.rangeVals.xMin!) ? width - eachRectWidth : width]);
-        var xAxis;
+        let xAxis;
 
         // more calculations based on bins
         // x-axis
@@ -245,9 +241,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
             // uniqueArr.sort()
             histDataSet.sort();
             for (let i = 0; i < data.length; i++) {
-                var index = 0;
+                let index = 0;
                 for (let j = 0; j < uniqueArr.length; j++) {
-                    if (uniqueArr[j] == data[i]) {
+                    if (uniqueArr[j] === data[i]) {
                         index = j;
                     }
                 }
@@ -255,7 +251,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
             }
             bins.pop();
             eachRectWidth = width / bins.length;
-            bins.forEach(d => (d.x0 = d.x0!));
+            bins.forEach(d => {
+                d.x0 = d.x0!;
+            });
             xAxis = d3
                 .axisBottom(x)
                 .ticks(bins.length > 1 ? bins.length - 1 : 1)
@@ -265,12 +263,12 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
             x.domain([0, bins.length - 1]);
             translateXAxis = eachRectWidth / 2;
         } else {
-            var allSame = true;
-            for (var i = 0; i < bins.length; i++) {
+            let allSame = true;
+            for (let i = 0; i < bins.length; i++) {
                 if (bins[i] && bins[i][0]) {
-                    var compare = bins[i][0];
+                    const compare = bins[i][0];
                     for (let j = 1; j < bins[i].length; j++) {
-                        if (bins[i][j] != compare) allSame = false;
+                        if (bins[i][j] !== compare) allSame = false;
                     }
                 }
             }
@@ -279,8 +277,8 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
                 eachRectWidth = width / bins.length;
             } else {
                 eachRectWidth = width / (bins.length + 1);
-                var tickDiff = bins.length >= 2 ? bins[bins.length - 2].x1! - bins[bins.length - 2].x0! : 0;
-                var curDomain = x.domain();
+                const tickDiff = bins.length >= 2 ? bins[bins.length - 2].x1! - bins[bins.length - 2].x0! : 0;
+                const curDomain = x.domain();
                 x.domain([curDomain[0], curDomain[0] + tickDiff * bins.length]);
             }
 
@@ -288,16 +286,13 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
             x.range([0, width - eachRectWidth]);
         }
         // y-axis
-        const maxFrequency = this.numericalYData
-            ? d3.max(histDataSet, function (d: any) {
-                  return d[yAxisTitle] ? Number(d[yAxisTitle]!.replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '')) : 0;
-              })
-            : d3.max(bins, function (d) {
-                  return d.length;
-              });
-        var y = d3.scaleLinear().range([height, 0]);
+        const maxFrequency = this.numericalYData ? 
+              d3.max(histDataSet, (d: any) => (d[yAxisTitle] ? Number(d[yAxisTitle]!.replace(/\$/g, '')
+                .replace(/%/g, '').replace(/</g, '')) : 0)) : 
+              d3.max(bins, d => d.length); // prettier-ignore
+        const y = d3.scaleLinear().range([height, 0]);
         y.domain([0, +maxFrequency!]);
-        var yAxis = d3.axisLeft(y).ticks(maxFrequency!);
+        const yAxis = d3.axisLeft(y).ticks(maxFrequency!);
         if (this.numericalYData) {
             const yScale = scaleCreatorNumerical(0, Number(maxFrequency), height, 0);
             yAxisCreator(svg.append('g'), width, yScale);
@@ -312,18 +307,17 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
         const onPointClick = action((e: any) => this.highlightSelectedBar(true, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet));
         const onHover = action((e: any) => {
             this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet);
+            // eslint-disable-next-line no-use-before-define
             updateHighlights();
         });
-        const mouseOut = action((e: any) => {
+        const mouseOut = action(() => {
             this.hoverOverData = undefined;
+            // eslint-disable-next-line no-use-before-define
             updateHighlights();
         });
         const updateHighlights = () => {
-            const hoverOverBar = this.hoverOverData;
-            const selectedData = this.selectedData;
-            svg.selectAll('rect').attr('class', function (d: any) {
-                return (hoverOverBar && hoverOverBar[0] == d[0]) || (selectedData && selectedData[0] == d[0]) ? 'histogram-bar hover' : 'histogram-bar';
-            });
+            const { hoverOverData: hoverOverBar, selectedData } = this;
+            svg.selectAll('rect').attr('class', (d: any) => ((hoverOverBar && hoverOverBar[0] === d[0]) || (selectedData && selectedData[0] === d[0]) ? 'histogram-bar hover' : 'histogram-bar'));
         };
         svg.on('click', onPointClick).on('mouseover', onHover).on('mouseout', mouseOut);
 
@@ -333,7 +327,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
             .style('text-anchor', 'middle')
             .text(xAxisTitle);
         svg.append('text')
-            .attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
+            .attr('transform', 'rotate(-90) translate( 0, ' + -10 + ')')
             .attr('x', -(height / 2))
             .attr('y', -20)
             .style('text-anchor', 'middle')
@@ -341,7 +335,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
         d3.format('.0f');
 
         // draw bars
-        var selected = this.selectedData;
+        const selected = this.selectedData;
         svg.selectAll('rect')
             .data(bins)
             .enter()
@@ -349,49 +343,34 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
             .attr(
                 'transform',
                 this.numericalYData
-                    ? function (d) {
-                          const eachData = histDataSet.filter((data: { [x: string]: number }) => {
-                              return data[xAxisTitle] == d[0];
-                          });
-                          const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
+                    ? d => {
+                          const eachData = histDataSet.filter((hData: { [x: string]: number }) => hData[xAxisTitle] === d[0]);
+                          const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') : 0;
                           return 'translate(' + x(d.x0!) + ',' + y(length) + ')';
                       }
-                    : function (d) {
-                          return 'translate(' + x(d.x0!) + ',' + y(d.length) + ')';
-                      }
+                    : d => 'translate(' + x(d.x0!) + ',' + y(d.length) + ')'
             )
             .attr(
                 'height',
                 this.numericalYData
-                    ? function (d) {
-                          const eachData = histDataSet.filter((data: { [x: string]: number }) => {
-                              return data[xAxisTitle] == d[0];
-                          });
-                          const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, '') : 0;
+                    ? d => {
+                          const eachData = histDataSet.filter((hData: { [x: string]: number }) => hData[xAxisTitle] === d[0]);
+                          const length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/%/g, '').replace(/</g, '') : 0;
                           return height - y(length);
                       }
-                    : function (d) {
-                          return height - y(d.length);
-                      }
+                    : d => height - y(d.length)
             )
             .attr('width', eachRectWidth)
-            .attr(
-                'class',
-                selected
-                    ? function (d) {
-                          return selected && selected[0] === d[0] ? 'histogram-bar hover' : 'histogram-bar';
-                      }
-                    : function (d) {
-                          return 'histogram-bar';
-                      }
-            )
+            .attr('class', selected ? d => (selected && selected[0] === d[0] ? 'histogram-bar hover' : 'histogram-bar') : () => 'histogram-bar')
             .attr('fill', d => {
-                var barColor;
+                let barColor;
                 const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
                 barColors.forEach(each => {
-                    if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
+                    // eslint-disable-next-line prefer-destructuring
+                    if (d[0] && d[0].toString() && each[0] === d[0].toString()) barColor = each[1];
                     else {
                         const range = StrCast(each[0]).split(' to ');
+                        // eslint-disable-next-line prefer-destructuring
                         if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
                     }
                 });
@@ -401,7 +380,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
 
     @action changeSelectedColor = (color: string) => {
         this.curBarSelected.attr('fill', color);
-        const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+        const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/%/g, '').replace(/</g, ''));
 
         const barColors = Cast(this._props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
         barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
@@ -410,22 +389,24 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
 
     @action eraseSelectedColor = () => {
         this.curBarSelected.attr('fill', this._props.layoutDoc.dataViz_histogram_defaultColor);
-        const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+        const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/%/g, '').replace(/</g, ''));
 
         const barColors = Cast(this._props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
         barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
     };
 
     updateBarColors = () => {
-        var svg = this._histogramSvg;
+        const svg = this._histogramSvg;
         if (svg)
             svg.selectAll('rect').attr('fill', (d: any) => {
-                var barColor;
+                let barColor;
                 const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
                 barColors.forEach(each => {
-                    if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
+                    // eslint-disable-next-line prefer-destructuring
+                    if (d[0] && d[0].toString() && each[0] === d[0].toString()) barColor = each[1];
                     else {
                         const range = StrCast(each[0]).split(' to ');
+                        // eslint-disable-next-line prefer-destructuring
                         if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
                     }
                 });
@@ -436,38 +417,41 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
     render() {
         this.updateBarColors();
         this._histogramData;
-        var curSelectedBarName = '';
-        var titleAccessor: any = 'dataViz_histogram_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 curSelectedBarName = '';
+        let titleAccessor: any = 'dataViz_histogram_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_histogram_defaultColor) this._props.layoutDoc.dataViz_histogram_defaultColor = '#69b3a2';
         if (!this._props.layoutDoc.dataViz_histogram_barColors) this._props.layoutDoc.dataViz_histogram_barColors = new List<string>();
-        var selected = 'none';
+        let selected = 'none';
         if (this._currSelected) {
-            curSelectedBarName = StrCast(this._currSelected![this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+            curSelectedBarName = StrCast(this._currSelected![this._props.axes[0]].replace(/\$/g, '').replace(/%/g, '').replace(/</g, ''));
             selected = '{ ';
-            Object.keys(this._currSelected).forEach(key =>
+            Object.keys(this._currSelected).forEach(key => {
                 key //
                     ? (selected += key + ': ' + this._currSelected[key] + ', ')
-                    : ''
-            );
+                    : '';
+            });
             selected = selected.substring(0, selected.length - 2) + ' }';
-            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);
             }
         }
-        var selectedBarColor;
-        var barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
-        barColors.forEach(each => each[0] === curSelectedBarName && (selectedBarColor = each[1]));
+        let selectedBarColor;
+        const barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
+        barColors.forEach(each => {
+            // eslint-disable-next-line prefer-destructuring
+            each[0] === curSelectedBarName && (selectedBarColor = each[1]);
+        });
 
         if (this._histogramData.length > 0 || !this.parentViz) {
             return this._props.axes.length >= 1 ? (
@@ -476,45 +460,51 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
                         <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
                         />
                         &nbsp; &nbsp;
                         <ColorPicker
-                            tooltip={'Change Default Bar Color'}
+                            tooltip="Change Default Bar Color"
                             type={Type.SEC}
                             icon={<FaFillDrip />}
                             selectedColor={StrCast(this._props.layoutDoc.dataViz_histogram_defaultColor)}
-                            setFinalColor={undoable(color => (this._props.layoutDoc.dataViz_histogram_defaultColor = color), 'Change Default Bar Color')}
-                            setSelectedColor={undoable(color => (this._props.layoutDoc.dataViz_histogram_defaultColor = color), 'Change Default Bar Color')}
+                            setFinalColor={undoable(color => {
+                                this._props.layoutDoc.dataViz_histogram_defaultColor = color;
+                            }, 'Change Default Bar Color')}
+                            setSelectedColor={undoable(color => {
+                                this._props.layoutDoc.dataViz_histogram_defaultColor = color;
+                            }, 'Change Default Bar Color')}
                             size={Size.XSMALL}
                         />
                     </div>
                     <div ref={this._histogramRef} />
-                    {selected != 'none' ? (
-                        <div className={'selected-data'}>
+                    {selected !== 'none' ? (
+                        <div className="selected-data">
                             Selected: {selected}
                             &nbsp; &nbsp;
                             <ColorPicker
-                                tooltip={'Change Bar Color'}
+                                tooltip="Change Bar Color"
                                 type={Type.SEC}
                                 icon={<FaFillDrip />}
-                                selectedColor={selectedBarColor ? selectedBarColor : this.curBarSelected.attr('fill')}
+                                selectedColor={selectedBarColor || this.curBarSelected.attr('fill')}
                                 setFinalColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Bar Color')}
                                 setSelectedColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Bar Color')}
                                 size={Size.XSMALL}
                             />
                             &nbsp;
                             <IconButton
-                                icon={<FontAwesomeIcon icon={'eraser'} />}
+                                icon={<FontAwesomeIcon icon="eraser" />}
                                 size={Size.XSMALL}
-                                color={'black'}
+                                color="black"
                                 type={Type.SEC}
-                                tooltip={'Revert to the default bar color'}
+                                tooltip="Revert to the default bar color"
                                 onClick={undoable(
                                     action(() => this.eraseSelectedColor()),
                                     'Change Selected Bar Color'
@@ -524,7 +514,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
                     ) : null}
                 </div>
             ) : (
-                <span className="chart-container"> {'first use table view to select a column to graph'}</span>
+                <span className="chart-container"> first use table view to select a column to graph</span>
             );
         }
         // when it is a brushed table and the incoming table doesn't have any rows selected
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index c667a15de..8105adf1e 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -3,7 +3,7 @@ import * as d3 from 'd3';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, NumListCast, StrListCast } from '../../../../../fields/Doc';
+import { Doc, DocListCast, NumListCast } from '../../../../../fields/Doc';
 import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
 import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
@@ -63,7 +63,6 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
         return !this.parentViz ? this._props.records : this._tableDataIds.map(rowId => this._props.records[rowId]);
     }
     @computed get _lineChartData() {
-        var guids = StrListCast(this._props.layoutDoc.dataViz_rowIds);
         if (this._props.axes.length <= 1) return [];
         return this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[1]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
     }
@@ -103,7 +102,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
         );
         this._disposers.annos = reaction(
             () => DocListCast(this._props.dataDoc[this._props.fieldKey + '_annotations']),
-            annotations => {
+            (/* annotations */) => {
                 // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way
                 // could be blue colored to make it look like anchor
                 // this.drawAnnotations()
@@ -176,7 +175,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
     getAnchor = (pinProps?: PinProps) => {
         const anchor = Docs.Create.ConfigDocument({
             //
-            title: 'line doc selection' + this._currSelected?.x,
+            title: 'line doc selection' + (this._currSelected?.x ?? ''),
         });
         PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
         anchor.config_dataVizSelection = this._currSelected ? new List<number>([this._currSelected.x, this._currSelected.y]) : undefined;
@@ -192,11 +191,12 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
     }
 
     @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 + ' Line Chart';
-        } else return ax1 + ' by ' + ax0 + ' Line Chart';
+        }
+        return ax1 + ' by ' + ax0 + ' Line Chart';
     }
 
     setupTooltip() {
@@ -216,9 +216,11 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
     @action
     setCurrSelected(x?: number, y?: number) {
         // TODO: nda - get rid of svg element in the list?
-        if (this._currSelected && this._currSelected.x == x && this._currSelected.y == y) this._currSelected = undefined;
+        if (this._currSelected && this._currSelected.x === x && this._currSelected.y === y) this._currSelected = undefined;
         else this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined;
-        this._props.records.forEach(record => record[this._props.axes[0]] === x && record[this._props.axes[1]] === y && (record.selected = true));
+        this._props.records.forEach(record => {
+            record[this._props.axes[0]] === x && record[this._props.axes[1]] === y && (record.selected = true);
+        });
     }
 
     drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
@@ -242,13 +244,13 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
         d3.select(this._lineChartRef.current).select('svg').remove();
         d3.select(this._lineChartRef.current).select('.tooltip').remove();
 
-        var { xMin, xMax, yMin, yMax } = rangeVals;
+        let { xMin, xMax, yMin, yMax } = rangeVals;
         if (xMin === undefined || xMax === undefined || yMin === undefined || yMax === undefined) {
             return;
         }
 
         // adding svg
-        const margin = this._props.margin;
+        const { margin } = this._props;
         const svg = (this._lineChartSvg = d3
             .select(this._lineChartRef.current)
             .append('svg')
@@ -258,15 +260,15 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
             .append('g')
             .attr('transform', `translate(${margin.left}, ${margin.top})`));
 
-        var validSecondData;
+        let validSecondData;
         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));
+            const 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 || isNaN(d.x) || !d.y || isNaN(d.y)) return false;
                 return true;
             });
-            var secondDataRange = minMaxRange([validSecondData]);
+            const secondDataRange = minMaxRange([validSecondData]);
             if (secondDataRange.xMax! > xMax) xMax = secondDataRange.xMax;
             if (secondDataRange.yMax! > yMax) yMax = secondDataRange.yMax;
             if (secondDataRange.xMin! < xMin) xMin = secondDataRange.xMin;
@@ -290,45 +292,31 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
             svg.append('path').attr('stroke', 'red');
 
             // legend
-            var color = d3.scaleOrdinal().range(['black', 'blue']).domain([this._props.axes[1], this._props.axes[2]]);
+            const color: any = 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('cy', (d, i) => -30 + i * 15)
                 .attr('r', 7)
-                .style('fill', function (d) {
-                    return color(d);
-                });
+                .style('fill', d => 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('y', (d, i) => -30 + i * 15)
+                .style('fill', d => color(d))
+                .text(d => 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] || isNaN(d[key])) return false;
-            });
-            return true;
-        });
+        const keys = Object.keys(data[0]);
+        const validData = data.filter(d => !keys.some(key => isNaN(d[key])));
 
         // draw the plot line
         drawLine(svg.append('path'), validData, lineGen, false);
@@ -355,7 +343,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
             const x0 = bisect(data, xScale.invert(xPos - 5)); // shift x by -5 so that you can reach points on the left-side axis
             const d0 = data[x0];
             // find .circle-d1 with data-x = d0.x and data-y = d0.y
-            const selected = svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
+            svg.selectAll('.datapoint').filter((d: any) => d['data-x'] === d0.x && d['data-y'] === d0.y);
             this.setCurrSelected(d0.x, d0.y);
             this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip);
         });
@@ -378,7 +366,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
             .style('text-anchor', 'middle')
             .text(this._props.axes[0]);
         svg.append('text')
-            .attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
+            .attr('transform', 'rotate(-90) translate(0, -10)')
             .attr('x', -(height / 2))
             .attr('y', -30)
             .attr('height', 20)
@@ -404,57 +392,60 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
     }
 
     render() {
-        var titleAccessor: any = 'dataViz_lineChart_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_lineChart_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;
         const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
-        var selectedTitle = '';
+        let 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;
+                let 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) {
+        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]]) ? (
                 <div className="chart-container" style={{ width: this._props.width + this._props.margin.right }}>
                     <div className="graph-title">
                         <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>
                     <div ref={this._lineChartRef} />
-                    {selectedPt != 'none' ? (
-                        <div className={'selected-data'}>
+                    {selectedPt !== 'none' ? (
+                        <div className="selected-data">
                             {`Selected: ${selectedPt}`}
                             {`${selectedTitle}`}
                             <Button
-                                onClick={e => {
+                                onClick={() => {
                                     this._props.vizBox.sidebarBtnDown;
                                     this._props.vizBox.sidebarAddDocument;
-                                }}></Button>
+                                }}
+                            />
                         </div>
                     ) : null}
                 </div>
             ) : (
-                <span className="chart-container"> {'first use table view to select two numerical axes to plot'}</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 two numerical axes to plot</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/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 15959c61d..55c221046 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { Button, Type } from 'browndash-components';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -13,7 +15,9 @@ import { ObservableReactComponent } from '../../../ObservableReactComponent';
 import { DocumentView } from '../../DocumentView';
 import { DataVizView } from '../DataVizBox';
 import './Chart.scss';
+
 const { DATA_VIZ_TABLE_ROW_HEIGHT } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
+
 interface TableBoxProps {
     Document: Doc;
     layoutDoc: Doc;
@@ -71,7 +75,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
     }
 
     @computed get columns() {
-        return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header != '' && header != undefined) : [];
+        return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header !== '' && header !== undefined) : [];
     }
 
     // updates the 'dataViz_selectedRows' and 'dataViz_highlightedRows' fields to no longer include rows that aren't in the table
@@ -108,13 +112,11 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
             if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
             else highlited?.push(rowId);
             if (!selected?.includes(rowId)) selected?.push(rowId);
-        } else {
+        } else if (selected?.includes(rowId)) {
             // selecting a row
-            if (selected?.includes(rowId)) {
-                if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
-                selected.splice(selected.indexOf(rowId), 1);
-            } else selected?.push(rowId);
-        }
+            if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
+            selected.splice(selected.indexOf(rowId), 1);
+        } else selected?.push(rowId);
         e.stopPropagation();
     };
 
@@ -124,7 +126,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
         setupMoveUpEvents(
             {},
             e,
-            e => {
+            moveEv => {
                 // dragging off a column to create a brushed DataVizBox
                 const sourceAnchorCreator = () => this._props.docView?.()!.Document!;
                 const targetCreator = (annotationOn: Doc | undefined) => {
@@ -138,13 +140,13 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                     embedding.pieSliceColors = Field.Copy(this._props.layoutDoc.pieSliceColors);
                     return embedding;
                 };
-                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) {
-                                e.linkDocument.link_displayLine = true;
-                                e.linkDocument.link_matchEmbeddings = true;
-                                e.linkDocument.link_displayArrow = true;
+                if (this._props.docView?.() && !ClientUtils.isClick(moveEv.clientX, moveEv.clientY, downX, downY, Date.now())) {
+                    DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
+                        dragComplete: completeEv => {
+                            if (!completeEv.aborted && completeEv.annoDragData && completeEv.annoDragData.linkSourceDoc && completeEv.annoDragData.dropDocument && completeEv.linkDocument) {
+                                completeEv.linkDocument.link_displayLine = true;
+                                completeEv.linkDocument.link_matchEmbeddings = true;
+                                completeEv.linkDocument.link_displayArrow = true;
                                 // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
                                 // e.annoDragData.linkSourceDoc.followLinkZoom = false;
                             }
@@ -155,9 +157,9 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                 return false;
             },
             emptyFunction,
-            action(e => {
-                if (e.shiftKey) {
-                    if (this._props.titleCol == col) this._props.titleCol = '';
+            action(clickEv => {
+                if (clickEv.shiftKey) {
+                    if (this._props.titleCol === col) this._props.titleCol = '';
                     else this._props.titleCol = col;
                     this._props.selectTitleCol(this._props.titleCol);
                 } else {
@@ -185,8 +187,22 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                         }
                     }}>
                     <div className="selectAll-buttons">
-                        <Button onClick={action(() => (this._props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds)))} text="Select All" type={Type.SEC} color={'black'} />
-                        <Button onClick={action(() => (this._props.layoutDoc.dataViz_selectedRows = new List<number>()))} text="Deselect All" type={Type.SEC} color={'black'} />
+                        <Button
+                            onClick={action(() => {
+                                this._props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds);
+                            })}
+                            text="Select All"
+                            type={Type.SEC}
+                            color="black"
+                        />
+                        <Button
+                            onClick={action(() => {
+                                this._props.layoutDoc.dataViz_selectedRows = new List<number>();
+                            })}
+                            text="Deselect All"
+                            type={Type.SEC}
+                            color="black"
+                        />
                     </div>
                     <div
                         className={`tableBox-container ${this.columns[0]}`}
@@ -225,7 +241,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                                                         ? 'darkgreen'
                                                         : this._props.axes.length > 2 && this._props.axes.lastElement() === col
                                                           ? 'darkred'
-                                                          : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] == col)
+                                                          : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] === col)
                                                             ? 'darkblue'
                                                             : undefined,
                                                 background:
@@ -233,7 +249,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                                                         ? '#E3fbdb'
                                                         : this._props.axes.length > 2 && this._props.axes.lastElement() === col
                                                           ? '#Fbdbdb'
-                                                          : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] == col)
+                                                          : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] === col)
                                                             ? '#c6ebf7'
                                                             : undefined,
                                                 // blue: #ADD8E6
@@ -260,11 +276,11 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                                                 background: NumListCast(this._props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
                                             }}>
                                             {this.columns.map(col => {
-                                                var colSelected = false;
-                                                if (this._props.axes.length > 2) colSelected = this._props.axes[0] == col || this._props.axes[1] == col || this._props.axes[2] == col;
-                                                else if (this._props.axes.length > 1) colSelected = this._props.axes[0] == col || this._props.axes[1] == col;
-                                                else if (this._props.axes.length > 0) colSelected = this._props.axes[0] == col;
-                                                if (this._props.titleCol == col) colSelected = true;
+                                                let colSelected = false;
+                                                if (this._props.axes.length > 2) colSelected = this._props.axes[0] === col || this._props.axes[1] === col || this._props.axes[2] === col;
+                                                else if (this._props.axes.length > 1) colSelected = this._props.axes[0] === col || this._props.axes[1] === col;
+                                                else if (this._props.axes.length > 0) colSelected = this._props.axes[0] === col;
+                                                if (this._props.titleCol === col) colSelected = true;
                                                 return (
                                                     <td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
                                                         <div className="tableBox-cell">{this._props.records[rowId][col]}</div>
@@ -279,10 +295,10 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                     </div>
                 </div>
             );
-        } 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>
-            );
+        }
+        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/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 518158a7f..832e18b68 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -18,7 +19,7 @@ import { CollectionView } from '../collections/CollectionView';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView';
 import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
-import { PresElementBox } from '../nodes/trails/PresElementBox';
+import { PresElementBox } from './trails/PresElementBox';
 import { SearchBox } from '../search/SearchBox';
 import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo';
 import { AudioBox } from './AudioBox';
@@ -71,8 +72,8 @@ interface HTMLtagProps {
     children?: JSX.Element[];
 }
 
-//"<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} overflow='hidden' position='absolute' width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'> <ImageBox {...props} fieldKey={'data'}/> <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px' transform='translate(-40px, 45px) rotate(-45deg)' position='absolute' color='{this.bannerColor===`green`?`light`:`dark`}blue' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'> {this.title}</HTMLspan></HTMLdiv>"
-//"<HTMLdiv borderRadius='100px' overflow='hidden' position='absolute' width='100%' height='100%'
+// "<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} overflow='hidden' position='absolute' width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'> <ImageBox {...props} fieldKey={'data'}/> <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px' transform='translate(-40px, 45px) rotate(-45deg)' position='absolute' color='{this.bannerColor===`green`?`light`:`dark`}blue' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'> {this.title}</HTMLspan></HTMLdiv>"
+// "<HTMLdiv borderRadius='100px' overflow='hidden' position='absolute' width='100%' height='100%'
 //          transform='rotate({2*this.x+this.y}deg)'
 //          onClick = { this.bannerColor = this.bannerColor === 'red' ? 'green' : 'red' } >
 //    <ImageBox {...props} fieldKey={'data'}/>
@@ -85,7 +86,7 @@ interface HTMLtagProps {
 //  </HTMLdiv>"
 @observer
 export class HTMLtag extends React.Component<HTMLtagProps> {
-    click = (e: React.MouseEvent) => {
+    click = () => {
         const clickScript = (this.props as any).onClick as Opt<ScriptField>;
         clickScript?.script.run({ this: this.props.Document, self: this.props.Document, scale: this.props.scaling });
     };
@@ -96,11 +97,10 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
     render() {
         const style: { [key: string]: any } = {};
         const divKeys = OmitKeys(this.props, ['children', 'dragStarting', 'dragEnding', 'htmltag', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit;
-        const replacer = (match: any, expr: string, offset: any, string: any) => {
+        const replacer = (match: any, expr: string) =>
             // bcz: this executes a script to convert a property expression string:  { script }  into a value
-            return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.Document, this: this.props.Document, scale: this.props.scaling }).result as string) || '';
-        };
-        Object.keys(divKeys).map((prop: string) => {
+            (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.Document, this: this.props.Document, scale: this.props.scaling }).result as string) || '';
+        Object.keys(divKeys).forEach((prop: string) => {
             const p = (this.props as any)[prop] as string;
             style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer);
         });
@@ -158,7 +158,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
             'dontCenter',
             'DataTransition',
             'contextMenuItems',
-            //'onClick', // don't need to omit this since it will be set
+            // 'onClick', // don't need to omit this since it will be set
             'onDoubleClickScript',
             'onPointerDownScript',
             'onPointerUpScript',
@@ -187,21 +187,16 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
         let layoutFrame = this.layout;
 
         // replace code content with a script  >{content}<   as in  <HTMLdiv>{this.title}</HTMLdiv>
-        const replacer = (match: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => {
-            return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix;
-        };
+        const replacer = (match: any, prefix: string, expr: string, postfix: string) => prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix;
         layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
 
         // replace HTML<tag> with corresponding HTML tag as in:  <HTMLdiv> becomes  <HTMLtag Document={props.Document} htmltag='div'>
-        const replacer2 = (match: any, p1: string, offset: any, string: any) => {
-            return `<HTMLtag Document={props.Document} scaling='${this._props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
-        };
+        const replacer2 = (match: any, p1: string) => `<HTMLtag Document={props.Document} scaling='${this._props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
         layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2);
 
         // replace /HTML<tag> with </HTMLdiv>  as in:  </HTMLdiv> becomes  </HTMLtag>
-        const replacer3 = (match: any, p1: string, offset: any, string: any) => {
-            return `</HTMLtag`;
-        };
+        const replacer3 = (/* match: any, p1: string, offset: any, string: any */) => `</HTMLtag`;
+
         layoutFrame = layoutFrame.replace(/<\/HTML([a-zA-Z0-9_-]+)/g, replacer3);
 
         // add onClick function to props
@@ -271,7 +266,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
                 }}
                 bindings={bindings}
                 jsx={layoutFrame}
-                showWarnings={true}
+                showWarnings
                 onError={(test: any) => {
                     console.log('DocumentContentsView:' + test, bindings, layoutFrame);
                 }}
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index d378082f8..a029b3761 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.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 { action, computed, makeObservable, observable, runInAction } from 'mobx';
@@ -26,20 +28,22 @@ interface DocumentLinksButtonProps {
     AlwaysOn?: boolean;
     InMenu?: boolean;
     OnHover?: boolean;
-    StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed)
+    StartLink?: boolean; // whether the link HAS been started (i.e. now needs to be completed)
     ShowCount?: boolean;
     scaling?: () => number; // how uch doc is scaled so that link buttons can invert it
     hideCount?: () => boolean;
 }
 
 export class DocButtonState {
-    @observable public StartLink: Doc | undefined = undefined; //origin's Doc, if defined
+    @observable public StartLink: Doc | undefined = undefined; // origin's Doc, if defined
     @observable public StartLinkView: DocumentView | undefined = undefined;
     @observable public AnnotationId: string | undefined = undefined;
     @observable public AnnotationUri: string | undefined = undefined;
     @observable public LinkEditorDocView: DocumentView | undefined = undefined;
+    // eslint-disable-next-line no-use-before-define
     public static _instance: DocButtonState | undefined;
     public static get Instance() {
+        // eslint-disable-next-line no-return-assign
         return DocButtonState._instance ?? (DocButtonState._instance = new DocButtonState());
     }
     constructor() {
@@ -50,7 +54,7 @@ export class DocButtonState {
 export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksButtonProps> {
     private _linkButton = React.createRef<HTMLDivElement>();
     public static get StartLink() { return DocButtonState.Instance.StartLink; } // prettier-ignore
-    public static set StartLink(value) { runInAction(() => (DocButtonState.Instance.StartLink = value)); } // prettier-ignore
+    public static set StartLink(value) { runInAction(() => {DocButtonState.Instance.StartLink = value}); } // prettier-ignore
     @observable public static StartLinkView: DocumentView | undefined = undefined;
     @observable public static AnnotationId: string | undefined = undefined;
     @observable public static AnnotationUri: string | undefined = undefined;
@@ -93,7 +97,9 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
             }),
             undefined,
             undefined,
-            action(() => (DocButtonState.Instance.LinkEditorDocView = this._props.View))
+            action(() => {
+                DocButtonState.Instance.LinkEditorDocView = this._props.View;
+            })
         );
     };
 
@@ -105,7 +111,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
             emptyFunction,
             action((e, doubleTap) => {
                 if (doubleTap && this._props.InMenu && this._props.StartLink) {
-                    //action(() => Doc.BrushDoc(this._props.View.Document));
+                    // action(() => Doc.BrushDoc(this._props.View.Document));
                     if (DocumentLinksButton.StartLink === this._props.View.Document) {
                         DocumentLinksButton.StartLink = undefined;
                         DocumentLinksButton.StartLinkView = undefined;
@@ -119,7 +125,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
     };
 
     @undoBatch
-    onLinkClick = (e: React.MouseEvent): void => {
+    onLinkClick = (): void => {
         if (this._props.InMenu && this._props.StartLink) {
             DocumentLinksButton.AnnotationId = undefined;
             DocumentLinksButton.AnnotationUri = undefined;
@@ -127,7 +133,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
                 DocumentLinksButton.StartLink = undefined;
                 DocumentLinksButton.StartLinkView = undefined;
             } else {
-                //if this LinkButton's Document is undefined
+                // if this LinkButton's Document is undefined
                 DocumentLinksButton.StartLink = this._props.View.Document;
                 DocumentLinksButton.StartLinkView = this._props.View;
             }
@@ -154,7 +160,9 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
                 DocumentLinksButton.AnnotationUri = undefined;
                 // !this._props.StartLink
             } else if (startLink !== endLink) {
+                // eslint-disable-next-line no-param-reassign
                 endLink = endLinkView?.ComponentView?.getAnchor?.(true, pinProps) || endLink;
+                // eslint-disable-next-line no-param-reassign
                 startLink = DocumentLinksButton.StartLinkView?.ComponentView?.getAnchor?.(true) || startLink;
                 const linkDoc = DocUtils.MakeLink(startLink, endLink, { link_relationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined });
 
@@ -193,7 +201,9 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
                     }
 
                     setTimeout(
-                        action(() => (TaskCompletionBox.taskCompleted = false)),
+                        action(() => {
+                            TaskCompletionBox.taskCompleted = false;
+                        }),
                         2500
                     );
                 }
@@ -243,13 +253,13 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
             showLinkCount(this._props.OnHover, this._props.Bottom)
         ) : (
             <div className="documentLinksButton-menu">
-                {this._props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
+                {this._props.StartLink ? ( // if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
                     <div className={`documentLinksButton ${isActive ? `startLink` : ``}`} ref={this._linkButton} onPointerDown={isActive ? StopEvent : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
                         <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
                     </div>
                 ) : null}
-                {!this._props.StartLink && DocumentLinksButton.StartLink !== this._props.View.Document ? ( //if the origin node is not this node
-                    <div className={'documentLinksButton-endLink'} ref={this._linkButton} onPointerDown={DocumentLinksButton.StartLink && this.completeLink}>
+                {!this._props.StartLink && DocumentLinksButton.StartLink !== this._props.View.Document ? ( // if the origin node is not this node
+                    <div className="documentLinksButton-endLink" ref={this._linkButton} onPointerDown={DocumentLinksButton.StartLink && this.completeLink}>
                         <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
                     </div>
                 ) : null}
@@ -263,7 +273,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
         const buttonTitle = 'Tap to view links; double tap to open link collection';
         const title = this._props.ShowCount ? buttonTitle : menuTitle;
 
-        //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
+        // render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
         return !Array.from(this.filteredLinks).length && !this._props.AlwaysOn ? null : (
             <div
                 className="documentLinksButton-wrapper"
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 62a3e2467..5962cd09f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -118,7 +118,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
      * This function is filled in by MainView to allow non-viewBox views to add Docs as tabs without
      * needing to know about/reference MainView
      */
-    public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
+    public static addDocTabFunc: (doc: Doc | Doc[], location: OpenWhere) => boolean = returnFalse;
 
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _doubleClickTimeout: NodeJS.Timeout | undefined;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 14454ff61..5c91d5aca 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable react/no-unused-prop-types */
+/* eslint-disable react/require-default-props */
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -31,6 +33,7 @@ export interface FocusViewOptions {
     easeFunc?: 'linear' | 'ease'; // transition method for scrolling
 }
 export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>;
+// eslint-disable-next-line no-use-before-define
 export type StyleProviderFuncType = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => any;
 //
 // these properties get assigned through the render() method of the DocumentView when it creates this node.
@@ -73,13 +76,14 @@ export interface FieldViewSharedProps {
     onPointerDownScript?: () => ScriptField;
     onPointerUpScript?: () => ScriptField;
     onBrowseClickScript?: () => ScriptField | undefined;
+    // eslint-disable-next-line no-use-before-define
     onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
     layout_fitWidth?: (doc: Doc) => boolean | undefined;
     searchFilterDocs: () => Doc[];
     layout_showTitle?: () => string;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
     rootSelected?: () => boolean; // whether the root of a template has been selected
-    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
+    addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
     filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
     addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
     removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
@@ -120,7 +124,7 @@ export interface FieldViewProps extends FieldViewSharedProps {
 @observer
 export class FieldView extends React.Component<FieldViewProps> {
     public static LayoutString(fieldType: { name: string }, fieldStr: string) {
-        return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={'data'} />"
+        return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; // e.g., "<ImageBox {...props} fieldKey={'data'} />"
     }
     @computed get fieldval() {
         return this.props.Document[this.props.fieldKey];
@@ -135,6 +139,6 @@ export class FieldView extends React.Component<FieldViewProps> {
         if (field instanceof List) return <div> {field.map(f => Field.toString(f)).join(', ')} </div>;
         if (field instanceof WebField) return <p>{Field.toString(field.url.href)}</p>;
         if (!(field instanceof Promise)) return <p>{Field.toString(field)}</p>;
-        return <p> {'Waiting for server...'} </p>;
+        return <p> Waiting for server... </p>;
     }
 }
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 70fc63115..79738c452 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -1,13 +1,14 @@
+/* eslint-disable react/jsx-props-no-spreading */
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
+import { Button, ColorPicker, Dropdown, DropdownType, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { ClientUtils, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
 import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, Utils } from '../../../../Utils';
-import { ClientUtils, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
@@ -34,7 +35,7 @@ export enum ButtonType {
     NumberSliderButton = 'numSliderBtn',
     NumberDropdownButton = 'numDropdownBtn',
     NumberInlineButton = 'numInlineBtn',
-    EditableText = 'editableText',
+    EditText = 'editableText',
 }
 
 export interface ButtonProps extends FieldViewProps {
@@ -83,7 +84,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         if (iconFalse) {
             icon = StrCast(this.dataDoc[this.fieldKey ?? 'iconFalse'] ?? this.dataDoc.icon, 'user') as any;
             if (icon) return <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
-            else return null;
+            return null;
         }
         icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any;
         return !icon ? null : icon === 'pres-trail' ? TrailsIcon(color) : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
@@ -109,7 +110,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
      * - Color button
      * - Dropdown list
      * - Number button
-     **/
+     * */
 
     _batch: UndoManager.Batch | undefined = undefined;
     /**
@@ -118,17 +119,12 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
     @computed get numberDropdown() {
         let type: NumberDropdownType;
         switch (this.type) {
-            case ButtonType.NumberDropdownButton:
-                type = 'dropdown';
-                break;
-            case ButtonType.NumberInlineButton:
-                type = 'input';
-                break;
+            case ButtonType.NumberDropdownButton:  type = 'dropdown'; break;
+            case ButtonType.NumberInlineButton:    type = 'input';  break;
             case ButtonType.NumberSliderButton:
-            default:
-                type = 'slider';
+            default:                               type = 'slider';
                 break;
-        }
+        } // prettier-ignore
         const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, self: this.Document, value, _readOnly_: value === undefined });
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
         // Script for checking the outcome of the toggle
@@ -155,12 +151,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         setupMoveUpEvents(
             this,
             e,
-            (e: PointerEvent) => {
-                return ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, self: this.Document, value: { doc: value, e } }).result;
-            },
+            () => ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, self: this.Document, value: { doc: value, e } }).result,
             emptyFunction,
             emptyFunction
-        );
+        ); // prettier-ignore
         return false;
     };
 
@@ -189,7 +183,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                     }
                     return (
                         <Popup
-                            icon={<FontAwesomeIcon size={'1x'} icon={icon} />}
+                            icon={<FontAwesomeIcon size="1x" icon={icon} />}
                             text={text}
                             type={Type.TERT}
                             color={SettingsManager.userColor}
@@ -275,8 +269,8 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         // Determine the type of toggle button
         const tooltip: string = StrCast(this.Document.toolTip);
 
-        const script = ScriptCast(this.Document.onClick);
-        const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
+        // const script = ScriptCast(this.Document.onClick);
+        // const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
         // Colors
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
         const items = DocListCast(this.dataDoc.data);
@@ -313,7 +307,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         const toggleStatus = script?.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result ?? false;
         // Colors
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
-        const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
+        // const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
 
         return (
             <Toggle
@@ -323,7 +317,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                 toggleStatus={toggleStatus}
                 text={buttonText}
                 color={color}
-                //background={SettingsManager.userBackgroundColor}
+                // background={SettingsManager.userBackgroundColor}
                 icon={this.Icon(color)!}
                 label={this.label}
                 onPointerDown={e =>
@@ -332,10 +326,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                         e,
                         returnTrue,
                         emptyFunction,
-                        action((e, doubleTap) => {
+                        action((clickEv, doubleTap) => {
                             (!doubleTap || !double) && script?.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
                             doubleTap && double?.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
-                            this._hackToRecompute = this._hackToRecompute + 1;
+                            this._hackToRecompute += 1;
                         })
                     )
                 }
@@ -348,27 +342,22 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
      */
     @computed get defaultButton() {
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
-        const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
         const tooltip: string = StrCast(this.Document.toolTip);
 
         return <IconButton tooltip={tooltip} icon={this.Icon(color)!} label={this.label} />;
     }
 
     @computed get editableText() {
-        // Script for running the toggle
         const script = ScriptCast(this.Document.script);
-        // Function to run the script
         const checkResult = script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result;
 
-        const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ this: this.Document, self: this.Document, value, _readOnly_: false }).result;
-
-        return <EditableText editing={false} setEditing={(editing: boolean) => {}} />;
+        const setValue = (value: string): boolean => script?.script.run({ this: this.Document, self: this.Document, value, _readOnly_: false }).result;
 
         return (
             <div className="menuButton editableText">
-                <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} />
+                <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon="lock" />
                 <div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}>
-                    <EditableView GetValue={() => script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
+                    <EditableView GetValue={() => script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine contents={checkResult} />
                 </div>
             </div>
         );
@@ -384,7 +373,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
             case ButtonType.NumberDropdownButton: 
             case ButtonType.NumberInlineButton:
             case ButtonType.NumberSliderButton: return this.numberDropdown;
-            case ButtonType.EditableText:       return this.editableText; 
+            case ButtonType.EditText:           return this.editableText; 
             case ButtonType.DropdownList:       return this.dropdownListButton;
             case ButtonType.ColorButton:        return this.colorButton; 
             case ButtonType.MultiToggleButton:  return this.multiToggleButton;
@@ -395,6 +384,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                         background={SettingsManager.userBackgroundColor} text={StrCast(this.dataDoc.buttonText)}/>;
             case ButtonType.MenuButton: return <IconButton {...btnProps} color={color} 
                         background={SettingsManager.userBackgroundColor} size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />; 
+            default: 
         }
         return this.defaultButton;
     };
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 231300a65..90b4a6740 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -22,7 +22,7 @@ import { Networking } from '../../Network';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
-import { ContextMenu } from '../../views/ContextMenu';
+import { ContextMenu } from '../ContextMenu';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
@@ -428,9 +428,9 @@ export class ImageBox 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,
@@ -476,6 +476,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                 }}>
                 <CollectionFreeFormView
                     ref={this._ffref}
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props}
                     setContentViewBox={emptyFunction}
                     NativeWidth={returnZero}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index c3afc198d..f96dd2b76 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
 import { Tooltip } from '@mui/material';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -63,7 +64,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
     render() {
         // let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
         let protoCount = 0;
-        let doc = this._props.doc;
+        let { doc } = this._props;
         while (doc) {
             if (Object.keys(doc).includes(this._props.keyName)) {
                 break;
@@ -77,10 +78,18 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
         const hover = { transition: '0.3s ease opacity', opacity: this.isPointerOver || this.isChecked ? 1 : 0 };
 
         return (
-            <tr className={this._props.rowStyle} onPointerEnter={action(() => (this.isPointerOver = true))} onPointerLeave={action(() => (this.isPointerOver = false))}>
+            <tr
+                className={this._props.rowStyle}
+                onPointerEnter={action(() => {
+                    this.isPointerOver = true;
+                })}
+                onPointerLeave={action(() => {
+                    this.isPointerOver = false;
+                })}>
                 <td className="keyValuePair-td-key" style={{ width: `${this._props.keyWidth}%` }}>
                     <div className="keyValuePair-td-key-container">
                         <button
+                            type="button"
                             style={hover}
                             className="keyValuePair-td-key-delete"
                             onClick={undoBatch(() => {
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index d1c8c62ed..80ece7cc8 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -13,6 +13,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { PinProps, ViewBoxBaseComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
+// eslint-disable-next-line import/extensions
 import BigText from './LabelBigText';
 import './LabelBox.scss';
 import { PresBox } from './trails';
@@ -23,7 +24,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return FieldView.LayoutString(LabelBox, fieldKey);
     }
     public static LayoutStringWithTitle(fieldStr: string, label?: string) {
-        return !label ? LabelBox.LayoutString(fieldStr) : `<LabelBox fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
+        return !label ? LabelBox.LayoutString(fieldStr) : `<LabelBox fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; // e.g., "<ImageBox {...props} fieldKey={"data} />"
     }
     private dropDisposer?: DragManager.DragDropDisposer;
     private _timeout: any;
@@ -54,14 +55,16 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
     get paramsDoc() {
         return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc;
     }
-    specificContextMenu = (e: React.MouseEvent): void => {
+    specificContextMenu = (): void => {
         const funcs: ContextMenuProps[] = [];
         !Doc.noviceMode &&
             funcs.push({
                 description: 'Clear Script Params',
                 event: () => {
                     const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
-                    params?.map(p => (this.paramsDoc[p] = undefined));
+                    params?.forEach(p => {
+                        this.paramsDoc[p] = undefined;
+                    });
                 },
                 icon: 'trash',
             });
@@ -71,7 +74,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @undoBatch
     drop = (e: Event, de: DragManager.DropEvent) => {
-        const docDragData = de.complete.docDragData;
+        const { docDragData } = de.complete;
         const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
         const missingParams = params?.filter(p => !this.paramsDoc[p]);
         if (docDragData && missingParams?.includes((e.target as any).textContent)) {
@@ -131,7 +134,10 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
         };
         this._timeout = undefined;
         if (!r) return params;
-        if (!r.offsetHeight || !r.offsetWidth) return (this._timeout = setTimeout(() => this.fitTextToBox(r)));
+        if (!r.offsetHeight || !r.offsetWidth) {
+            this._timeout = setTimeout(() => this.fitTextToBox(r));
+            return this._timeout;
+        }
         const parent = r.parentNode;
         const parentStyle = parent.style;
         parentStyle.display = '';
@@ -154,8 +160,13 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return (
             <div
                 className="labelBox-outerDiv"
-                onMouseLeave={action(() => (this._mouseOver = false))}
-                onMouseOver={action(() => (this._mouseOver = true))}
+                onMouseLeave={action(() => {
+                    this._mouseOver = false;
+                })}
+                // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events
+                onMouseOver={action(() => {
+                    this._mouseOver = true;
+                })}
                 ref={this.createDropTarget}
                 onContextMenu={this.specificContextMenu}
                 style={{ boxShadow: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow) }}>
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 0155defb7..bff6d53da 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -15,7 +15,9 @@ import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LinkAnchorBox.scss';
 import { LinkInfo } from './LinkDocPreview';
+
 const { MEDIUM_GRAY } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
+
 @observer
 export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
@@ -48,7 +50,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 else this._props.select(false);
             });
     };
-    onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
+    onPointerMove = action((e: PointerEvent) => {
         const cdiv = this._ref?.current?.parentElement;
         if (!this._isOpen && cdiv) {
             const bounds = cdiv.getBoundingClientRect();
@@ -68,7 +70,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return false;
     });
 
-    specificContextMenu = (e: React.MouseEvent): void => {};
+    specificContextMenu = (): void => {};
 
     render() {
         TraceMobx();
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 7855f8fe8..822485b8d 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -55,7 +55,6 @@ import { MarkerIcons } from './MarkerIcons';
 
 const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3MXIydm92ODdianp6cGYifQ.WFAqbhwxtMHOWSPtu0l2uQ';
 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/';
 
 type PopupInfo = {
@@ -112,13 +111,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     };
 
     // this list contains pushpins and configs
-    @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } //prettier-ignore
-    @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } //prettier-ignore
-    @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } //prettier-ignore
-    @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } //prettier-ignore
-    @computed get SidebarShown() { return this.layoutDoc._layout_showSidebar ? true : false; } //prettier-ignore
-    @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } //prettier-ignore
-    @computed get SidebarKey() {  return this.fieldKey + '_sidebar'; } //prettier-ignore
+    @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } // prettier-ignore
+    @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } // prettier-ignore
+    @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } // prettier-ignore
+    @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } // prettier-ignore
+    @computed get SidebarShown() { return !!this.layoutDoc._layout_showSidebar; } // prettier-ignore
+    @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } // prettier-ignore
+    @computed get SidebarKey() {  return this.fieldKey + '_sidebar'; } // prettier-ignore
     @computed get sidebarColor() {
         return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
     }
@@ -259,7 +258,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     }
                 });
             }
-        }); //add to annotation list
+        }); // add to annotation list
 
         return this.addDocument(doc, sidebarKey); // add to sidebar list
     };
@@ -326,7 +325,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                 }}
                 onPointerDown={this.sidebarBtnDown}>
-                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
             </div>
         );
     }
@@ -390,7 +389,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     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%';
@@ -654,7 +653,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             console.error(features);
             if (features && features.length > 0 && features[0].properties && features[0].geometry) {
                 const geometry = features[0].geometry as LineString;
-                const routeTitle: string = features[0].properties['routeTitle'];
+                const { routeTitle } = features[0].properties;
                 const routeDoc: Doc | undefined = this.allRoutes.find(routeDoc => routeDoc.title === routeTitle);
                 this.deselectPinOrRoute(); // TODO: Also deselect route if selected
                 if (routeDoc) {
@@ -699,7 +698,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
      */
     handleMapDblClick = async (e: MapLayerMouseEvent) => {
         e.preventDefault();
-        const lngLat: LngLat = e.lngLat;
+        const { lngLat }: LngLat = e;
         const longitude: number = lngLat.lng;
         const latitude: number = lngLat.lat;
 
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index e857ef722..189e2105d 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -6,7 +6,7 @@ import * as React from 'react';
 import { MapProvider, Map as MapboxMap } from 'react-map-gl';
 import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
 import { emptyFunction } from '../../../../Utils';
-import { Doc, DocListCast, LinkedTo, Opt } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
 import { DocCss, Highlight } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
@@ -15,7 +15,6 @@ import { DocUtils, Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { LinkManager } from '../../../util/LinkManager';
-import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { UndoManager, undoable } from '../../../util/UndoManager';
 import { PinProps, ViewBoxAnnotatableComponent } from '../../DocComponent';
@@ -25,9 +24,9 @@ import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
 import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
+import '../MapBox/MapBox.scss';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PresBox } from '../trails';
-import './MapBox.scss';
 
 /**
  * MapBox architecture:
@@ -43,7 +42,6 @@ import './MapBox.scss';
  */
 
 const mapboxApiKey = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNsbnc2eHJpbTA1ZTUyam85aGx4Z2FhbGwifQ.2Kqw9mk-9wAAg9kmHmKzcg';
-const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/  and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS=<your apikey>
 
 /**
  * Consider integrating later: allows for drawing, circling, making shapes on map
@@ -89,7 +87,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN);
     }
     @computed get SidebarShown() {
-        return this.layoutDoc._layout_showSidebar ? true : false;
+        return !!this.layoutDoc._layout_showSidebar;
     }
     @computed get sidebarWidthPercent() {
         return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
@@ -139,14 +137,18 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                     }
                 });
             }
-        }); //add to annotation list
+        }); // add to annotation list
 
         return this.addDocument(doc, sidebarKey); // add to sidebar list
     };
 
     removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => {
         const docs = doc instanceof Doc ? [doc] : doc;
-        this.allAnnotations.filter(anno => docs.includes(DocCast(anno.mapPin))).forEach(anno => (anno.mapPin = undefined));
+        this.allAnnotations
+            .filter(anno => docs.includes(DocCast(anno.mapPin)))
+            .forEach(anno => {
+                anno.mapPin = undefined;
+            });
         return this.removeDocument(doc, annotationKey, undefined);
     };
 
@@ -206,7 +208,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                     backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                 }}
                 onPointerDown={this.sidebarBtnDown}>
-                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
             </div>
         );
     }
@@ -605,7 +607,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
         this.map_docToPinMap.delete(pin);
         const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), color ? { color } : {});
-        this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(pin));
+        this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(pin));
         this._bingMap.current.entities.push(newpin);
         this.map_docToPinMap.set(pin, newpin);
     };
@@ -637,7 +639,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
             () => this.allAnnotations.map(doc => doc[Highlight]),
             () => {
                 const allConfigPins = this.allAnnotations.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin);
-                allConfigPins.forEach(({ doc, pushpin }) => {
+                allConfigPins.forEach(({ pushpin }) => {
                     if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) {
                         this.recolorPin(pushpin);
                         this.map_pinHighlighted.delete(pushpin);
@@ -736,7 +738,6 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
             return null;
         }
 
-        const renderAnnotations = (childFilters?: () => string[]) => null;
         return (
             <div className="mapBox" ref={this._ref}>
                 <div
@@ -746,15 +747,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                         e.button === 0 && !e.ctrlKey && e.stopPropagation();
                     }}
                     style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
-                    <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
-                    {renderAnnotations(this.opaqueFilter)}
-                    {SnappingManager.IsDragging ? null : renderAnnotations()}
-
                     <div className="mapBox-searchbar">
                         <EditableText
                             // editing
                             setVal={(newText: string | number) => typeof newText === 'string' && this.searchbarOnEdit(newText)}
-                            onEnter={e => this.bingSearch()}
+                            onEnter={() => this.bingSearch()}
                             placeholder={this.bingSearchBarContents || 'enter city/zip/...'}
                             textAlign="center"
                         />
@@ -792,7 +789,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                             ? null
                             : this.allAnnotations
                                   .filter(anno => !anno.layout_unrendered)
-                                  .map((pushpin, i) => (
+                                  .map(pushpin => (
                                       <DocumentView
                                           key={pushpin[Id]}
                                           // eslint-disable-next-line react/jsx-props-no-spreading
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 8140c0ca7..cc897aaef 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/control-has-associated-label */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -26,7 +28,7 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { Colors } from '../global/globalEnums';
-import { CreateImage } from '../nodes/WebBoxRenderer';
+import { CreateImage } from './WebBoxRenderer';
 import { PDFViewer } from '../pdf/PDFViewer';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { DocumentView, OpenWhere } from './DocumentView';
@@ -67,8 +69,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200;
         !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
         if (this.pdfUrl) {
-            if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => (this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)));
-            else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf)));
+            if (PDFBox.pdfcache.get(this.pdfUrl.url.href))
+                runInAction(() => {
+                    this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href);
+                });
+            else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href))
+                PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(
+                    action((pdf: any) => {
+                        this._pdf = pdf;
+                    })
+                );
         }
     }
 
@@ -86,7 +96,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         if (oldDiv instanceof HTMLCanvasElement) {
             const canvas = oldDiv;
             const img = document.createElement('img'); // create a Image Element
-            img.src = canvas.toDataURL(); //image sourcez
+            img.src = canvas.toDataURL(); // image sourcez
             img.style.width = canvas.style.width;
             img.style.height = canvas.style.height;
             const newCan = newDiv as HTMLCanvasElement;
@@ -97,7 +107,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     };
 
     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;
@@ -112,11 +122,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         this.replaceCanvases(docViewContent, newDiv);
         const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
 
-        const anchx = NumCast(cropping.x);
-        const anchy = NumCast(cropping.y);
+        // const anchx = NumCast(cropping.x);
+        // const anchy = NumCast(cropping.y);
         const anchw = NumCast(cropping._width) * (this._props.NativeDimScaling?.() || 1);
         const anchh = NumCast(cropping._height) * (this._props.NativeDimScaling?.() || 1);
-        const viewScale = 1;
+        // const viewScale = 1;
         cropping.title = 'crop: ' + this.Document.title;
         cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
         cropping.y = NumCast(this.Document.y);
@@ -157,7 +167,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     )
                 );
             })
-            .catch(function (error: any) {
+            .catch((error: any) => {
                 console.error('oops, something went wrong!', error);
             });
 
@@ -245,7 +255,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         }
         const docAnchor = () =>
             Docs.Create.ConfigDocument({
-                title: StrCast(this.Document.title + '@' + NumCast(this.layoutDoc._layout_scrollTop)?.toFixed(0)),
+                title: StrCast(this.Document.title + '@' + (NumCast(this.layoutDoc._layout_scrollTop) ?? 0).toFixed(0)),
                 annotationOn: this.Document,
             });
         const visibleAnchor = this._pdfViewer?._getAnchor?.(this._pdfViewer.savedAnnotations(), true);
@@ -288,7 +298,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         this.Document._layout_curPage = Math.min(NumCast(this.dataDoc[this._props.fieldKey + '_numPages']), (NumCast(this.Document._layout_curPage) || 1) + 1);
         return true;
     };
-    public gotoPage = (p: number) => (this.Document._layout_curPage = p);
+    public gotoPage = (p: number) => {
+        this.Document._layout_curPage = p;
+    };
 
     @undoBatch
     onKeyDown = action((e: KeyboardEvent) => {
@@ -300,6 +312,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             case 'PageUp':
                 processed = this.backPage();
                 break;
+            default:
         }
         if (processed) {
             e.stopImmediatePropagation();
@@ -315,7 +328,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             this._initialScrollTarget = undefined;
         }
     };
-    searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
+    searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+        this._searchString = e.currentTarget.value;
+    };
 
     // adding external documents; to sidebar key
     // if (doc.Geolocation) this.addDocument(doc, this.fieldkey+"_annotation")
@@ -371,11 +386,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     settingsPanel() {
         const pageBtns = (
             <>
-                <button className="pdfBox-backBtn" key="back" title="Page Back" onPointerDown={e => e.stopPropagation()} onClick={this.backPage}>
-                    <FontAwesomeIcon style={{ color: 'white' }} icon={'arrow-left'} size="sm" />
+                <button type="button" className="pdfBox-backBtn" key="back" title="Page Back" onPointerDown={e => e.stopPropagation()} onClick={this.backPage}>
+                    <FontAwesomeIcon style={{ color: 'white' }} icon="arrow-left" size="sm" />
                 </button>
-                <button className="pdfBox-fwdBtn" key="fwd" title="Page Forward" onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage}>
-                    <FontAwesomeIcon style={{ color: 'white' }} icon={'arrow-right'} size="sm" />
+                <button type="button" className="pdfBox-fwdBtn" key="fwd" title="Page Forward" onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage}>
+                    <FontAwesomeIcon style={{ color: 'white' }} icon="arrow-right" size="sm" />
                 </button>
             </>
         );
@@ -388,7 +403,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 onPointerDown={e => e.stopPropagation()}
                 style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}>
                 <div className="pdfBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
-                    <button className="pdfBox-overlayButton" title={searchTitle} />
+                    <button type="button" className="pdfBox-overlayButton" title={searchTitle} />
                     <input
                         className="pdfBox-searchBar"
                         placeholder="Search"
@@ -399,17 +414,18 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                             e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey);
                         }}
                     />
-                    <button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
+                    <button type="button" className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
                         <FontAwesomeIcon icon="search" size="sm" />
                     </button>
-                    <button className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation}>
-                        <FontAwesomeIcon icon={'arrow-up'} size="lg" />
+                    <button type="button" className="pdfBox-prevIcon" title="Previous Annotation" onClick={this.prevAnnotation}>
+                        <FontAwesomeIcon icon="arrow-up" size="lg" />
                     </button>
-                    <button className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation}>
-                        <FontAwesomeIcon icon={'arrow-down'} size="lg" />
+                    <button type="button" className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation}>
+                        <FontAwesomeIcon icon="arrow-down" size="lg" />
                     </button>
                 </div>
                 <button
+                    type="button"
                     className="pdfBox-overlayButton"
                     title={searchTitle}
                     onClick={action(() => {
@@ -426,9 +442,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     <input
                         value={curPage}
                         style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: 'all' }}
-                        onChange={e => (this.Document._layout_curPage = Number(e.currentTarget.value))}
+                        onChange={e => {
+                            this.Document._layout_curPage = Number(e.currentTarget.value);
+                        }}
                         onKeyDown={e => e.stopPropagation()}
-                        onClick={action(() => (this._pageControls = !this._pageControls))}
+                        onClick={action(() => {
+                            this._pageControls = !this._pageControls;
+                        })}
                     />
                     {this._pageControls ? pageBtns : null}
                 </div>
@@ -443,14 +463,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         return PDFBox.sidebarResizerWidth + nativeDiff * (this._props.NativeDimScaling?.() || 1);
     };
     @undoBatch
-    toggleSidebarType = () => (this.dataDoc[this.SidebarKey + '_type_collection'] = this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
-    specificContextMenu = (e: React.MouseEvent): void => {
+    toggleSidebarType = () => {
+        this.dataDoc[this.SidebarKey + '_type_collection'] = this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform;
+    };
+    specificContextMenu = (): void => {
         const cm = ContextMenu.Instance;
         const options = cm.findByDescription('Options...');
         const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
         !Doc.noviceMode && optionItems.push({ description: 'Toggle Sidebar Type', event: this.toggleSidebarType, icon: 'expand-arrows-alt' });
         !Doc.noviceMode && optionItems.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' });
-        //optionItems.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
+        // optionItems.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
         !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'asterisk' });
         const help = cm.findByDescription('Help...');
         const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
@@ -472,7 +494,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
     @observable _showSidebar = false;
     @computed get SidebarShown() {
-        return this._showSidebar || this.layoutDoc._show_sidebar ? true : false;
+        return !!(this._showSidebar || this.layoutDoc._show_sidebar);
     }
     @computed get sidebarHandle() {
         return (
@@ -486,7 +508,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                 }}
                 onPointerDown={e => this.sidebarBtnDown(e, true)}>
-                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
             </div>
         );
     }
@@ -517,6 +539,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             return ComponentTag === CollectionStackingView ? (
                 <SidebarAnnos
                     ref={this._sidebarRef}
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props}
                     Document={this.Document}
                     layoutDoc={this.layoutDoc}
@@ -532,6 +555,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             ) : (
                 <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
                     <ComponentTag
+                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
                         setContentViewBox={emptyFunction} // override setContentView to do nothing
                         NativeWidth={this.sidebarNativeWidthFunc}
@@ -542,7 +566,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                         yPadding={0}
                         viewField={this.SidebarKey}
                         isAnnotationOverlay={false}
-                        originTopLeft={true}
+                        originTopLeft
                         isAnyChildContentActive={this.isAnyChildContentActive}
                         select={emptyFunction}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -551,7 +575,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                         addDocument={this.sidebarAddDocument}
                         ScreenToLocalTransform={this.sidebarScreenToLocal}
                         renderDepth={this._props.renderDepth + 1}
-                        noSidebar={true}
+                        noSidebar
                         fieldKey={this.SidebarKey}
                     />
                 </div>
@@ -585,6 +609,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                         top: 0,
                     }}>
                     <PDFViewer
+                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
                         pdfBox={this}
                         sidebarAddDoc={this.sidebarAddDocument}
@@ -617,10 +642,19 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         const pdfView = !this._pdf ? null : this.renderPdfView;
         const href = this.pdfUrl?.url.href;
         if (!pdfView && href) {
-            if (PDFBox.pdfcache.get(href)) setTimeout(action(() => (this._pdf = PDFBox.pdfcache.get(href))));
+            if (PDFBox.pdfcache.get(href))
+                setTimeout(
+                    action(() => {
+                        this._pdf = PDFBox.pdfcache.get(href);
+                    })
+                );
             else {
                 if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise);
-                PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, (this._pdf = pdf))));
+                PDFBox.pdfpromise.get(href)?.then(
+                    action((pdf: any) => {
+                        PDFBox.pdfcache.set(href, (this._pdf = pdf));
+                    })
+                );
             }
         }
         return pdfView ?? this.renderTitleBox;
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 40199cce1..7d123d90c 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -47,7 +47,9 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable videoDuration: number | undefined = undefined;
 
     @action
-    setVideoDuration = (duration: number) => (this.videoDuration = duration);
+    setVideoDuration = (duration: number) => {
+        this.videoDuration = duration;
+    };
 
     @action
     setResult = (info: Upload.AccessPathInfo, presentation?: Presentation) => {
@@ -69,15 +71,15 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static WorkspaceStopRecording() {
         const remDoc = RecordingBox.screengrabber?.Document;
         if (remDoc) {
-            //if recordingbox is true; when we press the stop button. changed vals temporarily to see if changes happening
+            // if recordingbox is true; when we press the stop button. changed vals temporarily to see if changes happening
             RecordingBox.screengrabber?.Pause?.();
             setTimeout(() => {
                 RecordingBox.screengrabber?.Finish?.();
-                remDoc.overlayX = 70; //was 100
+                remDoc.overlayX = 70; // was 100
                 remDoc.overlayY = 590;
                 RecordingBox.screengrabber = undefined;
             }, 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
+            // 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 = mediaState.Paused;
             Doc.AddDocToList(Doc.UserDoc(), 'workspaceRecordings', remDoc);
@@ -103,10 +105,10 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
                       _width: 205,
                       _height: 115,
                   });
-        screengrabber.overlayX = 70; //was -400
-        screengrabber.overlayY = 590; //was 0
+        screengrabber.overlayX = 70; // was -400
+        screengrabber.overlayY = 590; // was 0
         screengrabber[DocData][Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
-        Doc.AddToMyOverlay(screengrabber); //just adds doc to overlay
+        Doc.AddToMyOverlay(screengrabber); // just adds doc to overlay
         DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
             RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
             RecordingBox.screengrabber.Record?.();
@@ -137,7 +139,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     @undoBatch
     public static addRecToWorkspace(value: RecordingBox) {
-        let ffView = Array.from(DocumentManager.Instance.DocumentViews).find(view => view.ComponentView instanceof CollectionFreeFormView);
+        const ffView = Array.from(DocumentManager.Instance.DocumentViews).find(view => view.ComponentView instanceof CollectionFreeFormView);
         (ffView?.ComponentView as CollectionFreeFormView)._props.addDocument?.(value.Document);
         Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value.Document);
         Doc.RemFromMyOverlay(value.Document);
@@ -204,51 +206,66 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
             </div>
         );
     }
+    // eslint-disable-next-line no-use-before-define
     static screengrabber: RecordingBox | undefined;
 }
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function stopWorkspaceRecording() {
     RecordingBox.WorkspaceStopRecording();
 });
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function stopWorkspaceReplaying(value: Doc) {
     RecordingBox.stopWorkspaceReplaying(value);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function removeWorkspaceReplaying(value: Doc) {
     RecordingBox.removeWorkspaceReplaying(value);
 });
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function getCurrentRecording() {
     return Doc.UserDoc().currentRecording;
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function getWorkspaceRecordings() {
     return new List<any>(['Record Workspace', `Record Webcam`, ...DocListCast(Doc.UserDoc().workspaceRecordings)]);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function isWorkspaceRecording() {
     return Doc.UserDoc().workspaceRecordingState === mediaState.Recording;
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function isWorkspaceReplaying() {
     return Doc.UserDoc().workspaceReplayingState;
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function replayWorkspace(value: Doc | string, _readOnly_: boolean) {
     if (_readOnly_) return DocCast(Doc.UserDoc().currentRecording) ?? 'Record Workspace';
     if (typeof value === 'string') RecordingBox.WorkspaceStartRecording(value);
     else RecordingBox.replayWorkspace(value);
+    return undefined;
 });
-ScriptingGlobals.add(function pauseWorkspaceReplaying(value: Doc, _readOnly_: boolean) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function pauseWorkspaceReplaying(value: Doc) {
     RecordingBox.pauseWorkspaceReplaying(value);
 });
-ScriptingGlobals.add(function resumeWorkspaceReplaying(value: Doc, _readOnly_: boolean) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function resumeWorkspaceReplaying(value: Doc) {
     RecordingBox.resumeWorkspaceReplaying(value);
 });
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function startRecordingDrag(value: { doc: Doc | string; e: React.PointerEvent }) {
     if (DocCast(value.doc)) {
         DragManager.StartDocumentDrag([value.e.target as HTMLElement], new DragManager.DocumentDragData([DocCast(value.doc)], dropActionType.embed), value.e.clientX, value.e.clientY);
         value.e.preventDefault();
         return true;
     }
+    return undefined;
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function renderDropdown() {
     if (!Doc.UserDoc().workspaceRecordings || DocListCast(Doc.UserDoc().workspaceRecordings).length === 0) {
         return true;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index e29e47514..882f6ba9e 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/media-has-caption */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import * as React from 'react';
 // import { Canvas } from '@react-three/fiber';
@@ -163,7 +164,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
     }
 
-    specificContextMenu = (e: React.MouseEvent): void => {
+    specificContextMenu = (): void => {
         const subitems = [{ description: 'Screen Capture', event: this.toggleRecording, icon: 'expand-arrows-alt' as any }];
         ContextMenu.Instance.addItem({ description: 'Options...', subitems, icon: 'video' });
     };
@@ -171,7 +172,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     @computed get content() {
         return (
             <video
-                className={'videoBox-content'}
+                className="videoBox-content"
                 key="video"
                 ref={r => {
                     this._videoRef = r;
@@ -184,7 +185,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 autoPlay={this._screenCapture}
                 style={{ width: this._screenCapture ? '100%' : undefined, height: this._screenCapture ? '100%' : undefined }}
                 onCanPlay={this.videoLoad}
-                controls={true}
+                controls
                 onClick={e => e.preventDefault()}>
                 <source type="video/mp4" />
                 Not supported.
@@ -221,23 +222,23 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     toggleRecording = async () => {
         if (!this._screenCapture) {
             this._audioRec = new MediaRecorder(await navigator.mediaDevices.getUserMedia({ audio: true }));
-            const aud_chunks: any = [];
-            this._audioRec.ondataavailable = (e: any) => aud_chunks.push(e.data);
-            this._audioRec.onstop = async (e: any) => {
-                const [{ result }] = await Networking.UploadFilesToServer(aud_chunks.map((file: any) => ({ file })));
+            const audChunks: any = [];
+            this._audioRec.ondataavailable = (e: any) => audChunks.push(e.data);
+            this._audioRec.onstop = async () => {
+                const [{ result }] = await Networking.UploadFilesToServer(audChunks.map((file: any) => ({ file })));
                 if (!(result instanceof Error)) {
                     this.dataDoc[this._props.fieldKey + '_audio'] = new AudioField(result.accessPaths.agnostic.client);
                 }
             };
             this._videoRef!.srcObject = await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
             this._videoRec = new MediaRecorder(this._videoRef!.srcObject);
-            const vid_chunks: any = [];
+            const vidChunks: any = [];
             this._videoRec.onstart = () => {
                 if (this.dataDoc[this._props.fieldKey + '_trackScreen']) TrackMovements.Instance.start();
                 this.dataDoc[this._props.fieldKey + '_recordingStart'] = new DateField(new Date());
             };
-            this._videoRec.ondataavailable = (e: any) => vid_chunks.push(e.data);
-            this._videoRec.onstop = async (e: any) => {
+            this._videoRec.ondataavailable = (e: any) => vidChunks.push(e.data);
+            this._videoRec.onstop = async () => {
                 const presentation = TrackMovements.Instance.yieldPresentation();
                 if (presentation?.movements) {
                     const presCopy = { ...presentation };
@@ -245,7 +246,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                     this.dataDoc[this.fieldKey + '_presentation'] = JSON.stringify(presCopy);
                 }
                 TrackMovements.Instance.finish();
-                const file = new File(vid_chunks, `${this.Document[Id]}.mkv`, { type: vid_chunks[0].type, lastModified: Date.now() });
+                const file = new File(vidChunks, `${this.Document[Id]}.mkv`, { type: vidChunks[0].type, lastModified: Date.now() });
                 const [{ result }] = await Networking.UploadFilesToServer({ file });
                 this.dataDoc[this.fieldKey + '_duration'] = (new Date().getTime() - this.recordingStart!) / 1000;
                 if (!(result instanceof Error)) {
@@ -298,6 +299,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 <div className="videoBox-viewer">
                     <div style={{ position: 'relative', height: this.videoPanelHeight() }}>
                         <CollectionFreeFormView
+                            // eslint-disable-next-line react/jsx-props-no-spreading
                             {...this._props}
                             setContentViewBox={emptyFunction}
                             NativeWidth={returnZero}
@@ -306,7 +308,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             PanelWidth={this._props.PanelWidth}
                             focus={this._props.focus}
                             isSelected={this._props.isSelected}
-                            isAnnotationOverlay={true}
+                            isAnnotationOverlay
                             select={emptyFunction}
                             isContentActive={returnFalse}
                             NativeDimScaling={returnOne}
@@ -325,9 +327,10 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                     <div style={{ background: SettingsManager.userColor, position: 'relative', height: this.formattedPanelHeight() }}>
                         {!(this.dataDoc[this.fieldKey + '_dictation'] instanceof Doc) ? null : (
                             <FormattedTextBox
+                                // eslint-disable-next-line react/jsx-props-no-spreading
                                 {...this._props}
                                 Document={DocCast(this.dataDoc[this.fieldKey + '_dictation'])}
-                                fieldKey={'text'}
+                                fieldKey="text"
                                 PanelHeight={this.formattedPanelHeight}
                                 select={emptyFunction}
                                 isContentActive={emptyFunction}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 60141b2a6..5b3f37993 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/media-has-caption */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -153,11 +154,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                     clearTimeout(this._controlsFadeTimer);
                     this._scrubbing = true;
                     this._controlsFadeTimer = setTimeout(
-                        action(() => (this._scrubbing = false)),
+                        action(() => {
+                            this._scrubbing = false;
+                        }),
                         500
                     );
                     e.stopPropagation();
                     break;
+                default:
             }
         }
     };
@@ -204,7 +208,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         else {
             this._keepCurrentlyPlaying = true;
             this.pause();
-            setTimeout(() => (this._keepCurrentlyPlaying = false));
+            setTimeout(() => {
+                this._keepCurrentlyPlaying = false;
+            });
         }
     };
 
@@ -247,7 +253,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             clearTimeout(this._controlsFadeTimer);
             this._controlsVisible = true;
             this._controlsFadeTimer = setTimeout(
-                action(() => (this._controlsVisible = false)),
+                action(() => {
+                    this._controlsVisible = false;
+                }),
                 3000
             );
         }
@@ -281,7 +289,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         const canvas = document.createElement('canvas');
         canvas.width = 640;
         canvas.height = (640 * Doc.NativeHeight(this.layoutDoc)) / (Doc.NativeWidth(this.layoutDoc) || 1);
-        const ctx = canvas.getContext('2d'); //draw image to canvas. scale to target dimensions
+        const ctx = canvas.getContext('2d'); // draw image to canvas. scale to target dimensions
         if (ctx) {
             this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
         }
@@ -298,7 +306,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             this._props.addDocument?.(b);
             DocUtils.MakeLink(b, this.Document, { link_relationship: 'video snapshot' });
         } else {
-            //convert to desired file format
+            // 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, '');
@@ -335,7 +343,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         Doc.SetNativeWidth(imageSnapshot[DocData], Doc.NativeWidth(this.layoutDoc));
         Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
         this._props.addDocument?.(imageSnapshot);
-        const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
+        DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
         // link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);  // do we need to set an end time? should default to +0.1
         setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
     };
@@ -346,7 +354,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent';
         const anchor =
             addAsAnnotation && marquee
-                ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.Document
+                ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode || undefined, undefined, marquee, addAsAnnotation) || this.Document
                 : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.Document });
         PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true, pannable: true } }, this.Document);
         return anchor;
@@ -376,12 +384,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
             if (this.heightPercent === 100) {
                 // do we want to always open up the timeline when followin a link? kind of clunky visually
-                //this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
+                // this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
                 options.didMove = true;
             }
             return this._stackedTimeline.getView(doc, options);
         }
-        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));
+        });
     };
 
     // extracts video thumbnails and saves them as field of doc
@@ -391,7 +401,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         const thumbnailPromises: Promise<any>[] = [];
         const video = document.createElement('video');
 
-        video.onloadedmetadata = () => (video.currentTime = 0);
+        video.onloadedmetadata = () => {
+            video.currentTime = 0;
+        };
 
         video.onseeked = () => {
             const canvas = document.createElement('canvas');
@@ -405,7 +417,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             if (newTime < video.duration) {
                 video.currentTime = newTime;
             } else {
-                Promise.all(thumbnailPromises).then(thumbnails => (this.dataDoc[this.fieldKey + '_thumbnails'] = new List<string>(thumbnails)));
+                Promise.all(thumbnailPromises).then(thumbnails => {
+                    this.dataDoc[this.fieldKey + '_thumbnails'] = new List<string>(thumbnails);
+                });
             }
         };
 
@@ -424,11 +438,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             this._disposers.reactionDisposer?.();
             this._disposers.reactionDisposer = reaction(
                 () => NumCast(this.layoutDoc._layout_currentTimecode),
-                time => !this._playing && (vref.currentTime = time),
+                time => {
+                    !this._playing && (vref.currentTime = time);
+                },
                 { fireImmediately: true }
             );
 
-            (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length != VideoBox.numThumbnails) && this.getVideoThumbnails();
+            (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length !== VideoBox.numThumbnails) && this.getVideoThumbnails();
         }
     };
 
@@ -437,7 +453,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     setContentRef = (cref: HTMLDivElement | null) => {
         this._contentRef = cref;
         if (cref) {
-            cref.onfullscreenchange = action(e => {
+            cref.onfullscreenchange = action(() => {
                 this._fullScreen = document.fullscreenElement === cref;
                 this._controlsVisible = true;
                 this._scrubbing = false;
@@ -452,7 +468,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     };
 
     // context menu
-    specificContextMenu = (e: React.MouseEvent): void => {
+    specificContextMenu = (): void => {
         const field = Cast(this.dataDoc[this._props.fieldKey], VideoField);
         if (field) {
             const url = field.url.href;
@@ -463,7 +479,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                 subitems.push({
                     description: 'Screen Capture',
                     event: async () => {
-                        runInAction(() => (this._screenCapture = !this._screenCapture));
+                        runInAction(() => {
+                            this._screenCapture = !this._screenCapture;
+                        });
                         this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
                     },
                     icon: 'expand-arrows-alt',
@@ -471,7 +489,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             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: () => (this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks),
+                event: () => {
+                    this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks;
+                },
                 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' });
@@ -505,7 +525,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     };
 
     // ref for updating time
-    setAudioRef = (e: HTMLAudioElement | null) => (this._audioPlayer = e);
+    setAudioRef = (e: HTMLAudioElement | null) => {
+        this._audioPlayer = e;
+    };
 
     // renders the video and audio
     @computed get content() {
@@ -587,7 +609,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         setupMoveUpEvents(
             this,
             e,
-            action(encodeURIComponent => {
+            action(() => {
                 this._clicking = false;
                 if (this._props.isContentActive()) {
                     // const local = this.ScreenToLocalTransform().scale(this._props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
@@ -601,7 +623,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             () => {
                 this.layoutDoc._layout_timelineHeightPercent = this.heightPercent !== 100 ? 100 : VideoBox.heightPercent;
                 setTimeout(
-                    action(() => (this._clicking = false)),
+                    action(() => {
+                        this._clicking = false;
+                    }),
                     500
                 );
             },
@@ -636,7 +660,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     addDocWithTimecode(doc: Doc | Doc[]): boolean {
         const docs = doc instanceof Doc ? [doc] : doc;
         const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
-        docs.forEach(doc => (doc._timecodeToHide = (doc._timecodeToShow = curTime) + 1));
+        docs.forEach(doc => {
+            doc._timecodeToHide = (doc._timecodeToShow = curTime) + 1;
+        });
         return this.addDocument(doc);
     }
 
@@ -732,11 +758,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     // stretches vertically or horizontally depending on video orientation so video fits full screen
     fullScreenSize() {
         if (this._videoRef && this._videoRef.videoHeight / this._videoRef.videoWidth > 1) {
-            //prettier-ignore
-            return ({ height: '100%' });
+            return { height: '100%' };
         }
-        //prettier-ignore
-        return ({ width: '100%' });
+        return ({ width: '100%' }); // prettier-ignore
     }
 
     // for zoom slider, sets timeline waveform zoom
@@ -778,7 +802,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         this._props.select(true);
     };
 
-    timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+    timelineWhenChildContentsActiveChanged = action((isActive: boolean) => {
+        this._isAnyChildContentActive = isActive;
+        this._props.whenChildContentsActiveChanged(isActive);
+    });
 
     timelineScreenToLocal = () =>
         this._props
@@ -786,7 +813,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             .scale(this.scaling())
             .translate(0, (-this.heightPercent / 100) * this._props.PanelHeight());
 
-    setPlayheadTime = (time: number) => (this.player!.currentTime = this.layoutDoc._layout_currentTimecode = time);
+    setPlayheadTime = (time: number) => {
+        this.player!.currentTime = this.layoutDoc._layout_currentTimecode = time;
+    };
 
     timelineHeight = () => (this._props.PanelHeight() * (100 - this.heightPercent)) / 100;
 
@@ -849,7 +878,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         return (
             <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%`, display: this.heightPercent === 100 ? 'none' : '' }}>
                 <CollectionStackedTimeline
-                    ref={action((r: any) => (this._stackedTimeline = r))}
+                    ref={action((r: any) => {
+                        this._stackedTimeline = r;
+                    })}
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props}
                     dataFieldKey={this.fieldKey}
                     fieldKey={this.annotationKey}
@@ -887,7 +919,7 @@ export class VideoBox 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.backgroundColor = 'transparent';
@@ -916,8 +948,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         croppingProto.type = DocumentType.VID;
         croppingProto.layout = VideoBox.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.videoCrop = true;
         croppingProto.layout_currentTimecode = this.layoutDoc._layout_currentTimecode;
         croppingProto.freeform_scale = viewScale;
@@ -959,14 +991,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                             left: (this._props.PanelWidth() - this.panelWidth()) / 2,
                         }}>
                         <CollectionFreeFormView
+                            // eslint-disable-next-line react/jsx-props-no-spreading
                             {...this._props}
                             setContentViewBox={emptyFunction}
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
                             renderDepth={this._props.renderDepth + 1}
                             fieldKey={this.annotationKey}
-                            isAnnotationOverlay={true}
-                            annotationLayerHostsContent={true}
+                            isAnnotationOverlay
+                            annotationLayerHostsContent
                             PanelWidth={this._props.PanelWidth}
                             PanelHeight={this._props.PanelHeight}
                             isAnyChildContentActive={returnFalse}
@@ -1050,12 +1083,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                     </div>
                 )}
 
-                <div className="videobox-button" title={'full screen'} onPointerDown={this.onFullDown}>
+                <div className="videobox-button" title="full screen" onPointerDown={this.onFullDown}>
                     <FontAwesomeIcon icon="expand" />
                 </div>
 
                 {!this._fullScreen && width > 300 && (
-                    <div className="videobox-button" title={'show timeline'} onPointerDown={this.onTimelineHdlDown}>
+                    <div className="videobox-button" title="show timeline" onPointerDown={this.onTimelineHdlDown}>
                         <FontAwesomeIcon icon="eye" />
                     </div>
                 )}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 446e83dd3..fc2e4bf61 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { htmlToText } from 'html-to-text';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
@@ -15,7 +17,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 { emptyFunction, stringHash, Utils } from '../../../Utils';
+import { emptyFunction, stringHash } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -39,8 +41,9 @@ import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
 import { LinkInfo } from './LinkDocPreview';
 import { PresBox } from './trails';
 import './WebBox.scss';
+
 const { CreateImage } = require('./WebBoxRenderer');
-const _global = (window /* browser */ || global) /* node */ as any;
+
 @observer
 export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
@@ -142,19 +145,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         const scrollTop = NumCast(this.layoutDoc._layout_scrollTop);
         const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
         const nativeHeight = (nativeWidth * this._props.PanelHeight()) / this._props.PanelWidth();
-        var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
+        let htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
         if (!htmlString) {
             htmlString = await (await fetch(ClientUtils.CorsProxy(this.webField!.href))).text();
         }
         this.layoutDoc.thumb = undefined;
         this.Document.thumbLockout = true; // lock to prevent multiple thumb updates.
         CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop)
-            .then((data_url: any) => {
-                if (data_url.includes('<!DOCTYPE')) {
+            .then((dataUrl: any) => {
+                if (dataUrl.includes('<!DOCTYPE')) {
                     console.log('BAD DATA IN THUMB CREATION');
                     return;
                 }
-                Utils.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename =>
+                ClientUtils.convertDataUri(dataUrl, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename =>
                     setTimeout(
                         action(() => {
                             this.Document.thumbLockout = false;
@@ -167,7 +170,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     )
                 );
             })
-            .catch(function (error: any) {
+            .catch((error: any) => {
                 console.error('oops, something went wrong!', error);
             });
     };
@@ -188,7 +191,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         });
         this._disposers.urlchange = reaction(
             () => WebCast(this.dataDoc.data),
-            url => this.submitURL(false, false)
+            () => this.submitURL(false, false)
         );
         this._disposers.titling = reaction(
             () => StrCast(this.Document.title),
@@ -200,8 +203,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
         this._disposers.layout_autoHeight = reaction(
             () => this.layoutDoc._layout_autoHeight,
-            layout_autoHeight => {
-                if (layout_autoHeight) {
+            layoutAutoHeight => {
+                if (layoutAutoHeight) {
                     this.layoutDoc._nativeHeight = NumCast(this.Document[this._props.fieldKey + '_nativeHeight']);
                     this._props.setHeight?.(NumCast(this.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
                 }
@@ -221,7 +224,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             } // else it's an HTMLfield
         } else if (this.webField && !this.dataDoc.text) {
             WebRequest.get(ClientUtils.CorsProxy(this.webField.href)) //
-                .then(result => result && (this.dataDoc.text = htmlToText(result.content)));
+                .then(result => {
+                    result && (this.dataDoc.text = htmlToText(result.content));
+                });
         }
 
         this._disposers.scrollReaction = reaction(
@@ -297,21 +302,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     const focusTime = options.zoomTime ?? 500;
                     this.goTo(scrollTo, focusTime, options.easeFunc);
                     return focusTime;
-                } else {
-                    this._initialScroll = scrollTo;
                 }
+                this._initialScroll = scrollTo;
             }
         }
+        return undefined;
     };
 
     @action
-    getView = (doc: Doc, options: FocusViewOptions) => {
-        if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this.DocumentView?.()));
+    getView = (doc: Doc /* , options: FocusViewOptions */) => {
+        if (Doc.AreProtosEqual(doc, this.Document))
+            return new Promise<Opt<DocumentView>>(res => {
+                res(this.DocumentView?.());
+            });
         if (this.Document.layout_fieldKey === 'layout_icon') this.DocumentView?.().iconify();
         const webUrl = WebCast(doc.config_data)?.url;
         if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href);
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false);
-        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));
+        });
     };
 
     sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
@@ -322,14 +332,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         return this._props.addDocTab(doc, where);
     };
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
-        let ele: Opt<HTMLDivElement> = undefined;
+        let ele: Opt<HTMLDivElement>;
         try {
             const contents = this._iframe?.contentWindow?.getSelection()?.getRangeAt(0).cloneContents();
             if (contents) {
                 ele = document.createElement('div');
                 ele.append(contents);
             }
-        } catch (e) {}
+        } catch (e) {
+            /* empty */
+        }
         const visibleAnchor = this._getAnchor(this._savedAnnotations, true);
         const anchor =
             visibleAnchor ??
@@ -338,7 +350,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 y: NumCast(this.layoutDoc._layout_scrollTop),
                 annotationOn: this.Document,
             });
-        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: pinProps?.pinData ? true : false, pannable: true } }, this.Document);
+        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: !!pinProps?.pinData, pannable: true } }, this.Document);
         anchor.text = ele?.textContent ?? '';
         anchor.text_html = ele?.innerHTML ?? this._selectionText;
         addAsAnnotation && this.addDocumentWrapper(anchor);
@@ -395,7 +407,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 e?.stopPropagation();
                 setTimeout(() => {
                     // if menu comes up right away, the down event can still be active causing a menu item to be selected
-                    this.specificContextMenu(undefined as any);
+                    this.specificContextMenu();
                     this.DocumentView?.().onContextMenu(undefined, theclick[0], theclick[1]);
                 });
             }
@@ -470,6 +482,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             const sheets = document.head.appendChild(style);
             return (sheets as any).sheet;
         }
+        return undefined;
     }
     addWebStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') {
         const propText =
@@ -484,7 +497,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     _iframetimeout: any = undefined;
     @observable _warning = 0;
     @action
-    iframeLoaded = (e: any) => {
+    iframeLoaded = () => {
         const iframe = this._iframe;
         if (this._initialScroll !== undefined) {
             this.setScrollPos(this._initialScroll);
@@ -640,12 +653,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 this._scrollHeight = 0;
                 if (this._webUrl === this._url) {
                     this._webUrl = curUrl;
-                    setTimeout(action(() => (this._webUrl = this._url)));
+                    setTimeout(
+                        action(() => {
+                            this._webUrl = this._url;
+                        })
+                    );
                 } else {
                     this._webUrl = this._url;
                 }
                 return true;
             }
+            return undefined;
         });
         return false;
     };
@@ -663,12 +681,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 this._scrollHeight = 0;
                 if (this._webUrl === this._url) {
                     this._webUrl = curUrl;
-                    setTimeout(action(() => (this._webUrl = this._url)));
+                    setTimeout(
+                        action(() => {
+                            this._webUrl = this._url;
+                        })
+                    );
                 } else {
                     this._webUrl = this._url;
                 }
                 return true;
             }
+            return undefined;
         });
         return false;
     };
@@ -700,7 +723,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         const html = dataTransfer.getData('text/html');
         const uri = dataTransfer.getData('text/uri-list');
         const url = uri || html || this._url || '';
-        const newurl = url.startsWith(window.location.origin) ? url.replace(window.location.origin, this._url?.match(/http[s]?:\/\/[^\/]*/)?.[0] || '') : url;
+        const newurl = url.startsWith(window.location.origin) ? url.replace(window.location.origin, this._url?.match(/http[s]?:\/\/[^/]*/)?.[0] || '') : url;
         this.setData(newurl);
         e.stopPropagation();
     };
@@ -723,19 +746,31 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         e.stopPropagation();
     };
 
-    specificContextMenu = (e: React.MouseEvent | PointerEvent): void => {
+    specificContextMenu = (): void => {
         const cm = ContextMenu.Instance;
         const funcs: ContextMenuProps[] = [];
         if (!cm.findByDescription('Options...')) {
             !Doc.noviceMode &&
-                funcs.push({ description: (this.layoutDoc[this.fieldKey + '_useCors'] ? "Don't Use" : 'Use') + ' Cors', event: () => (this.layoutDoc[this.fieldKey + '_useCors'] = !this.layoutDoc[this.fieldKey + '_useCors']), icon: 'snowflake' });
+                funcs.push({
+                    description: (this.layoutDoc[this.fieldKey + '_useCors'] ? "Don't Use" : 'Use') + ' Cors',
+                    event: () => {
+                        this.layoutDoc[this.fieldKey + '_useCors'] = !this.layoutDoc[this.fieldKey + '_useCors'];
+                    },
+                    icon: 'snowflake',
+                });
             funcs.push({
                 description: (this.dataDoc[this.fieldKey + '_allowScripts'] ? 'Prevent' : 'Allow') + ' Scripts',
                 event: () => {
                     this.dataDoc[this.fieldKey + '_allowScripts'] = !this.dataDoc[this.fieldKey + '_allowScripts'];
                     if (this._iframe) {
-                        runInAction(() => (this._hackHide = true));
-                        setTimeout(action(() => (this._hackHide = false)));
+                        runInAction(() => {
+                            this._hackHide = true;
+                        });
+                        setTimeout(
+                            action(() => {
+                                this._hackHide = false;
+                            })
+                        );
                     }
                 },
                 icon: 'snowflake',
@@ -773,7 +808,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             setupMoveUpEvents(
                 this,
                 e,
-                action(e => {
+                action(() => {
                     MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                     return true;
                 }),
@@ -797,7 +832,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     @observable lighttext = false;
 
     @computed get urlContent() {
-        if (this.ScreenToLocalBoxXf().Scale > 25) return <div></div>;
+        if (this.ScreenToLocalBoxXf().Scale > 25) return <div />;
         setTimeout(
             action(() => {
                 if (this._initialScroll === undefined && !this._webPageHasBeenRendered) {
@@ -826,12 +861,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         if (field instanceof WebField) {
             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);
+            // if (!scripts) console.log('No scripts for: ' + url);
             return (
                 <iframe
+                    title="web iframe"
                     key={this._warning}
                     className="webBox-iframe"
-                    ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))}
+                    ref={action((r: HTMLIFrameElement | null) => {
+                        this._iframe = r;
+                    })}
                     style={{ pointerEvents: SnappingManager.IsResizing ? 'none' : undefined }}
                     src={url}
                     onLoad={this.iframeLoaded}
@@ -842,11 +880,23 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 />
             );
         }
-        return <iframe className="webBox-iframe" ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))} src={'https://crossorigin.me/https://cs.brown.edu'} />;
+        return (
+            <iframe
+                title="web frame"
+                className="webBox-iframe"
+                ref={action((r: HTMLIFrameElement | null) => {
+                    this._iframe = r;
+                })}
+                src="https://crossorigin.me/https://cs.brown.edu"
+            />
+        );
     }
 
     addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
-        this._url && (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.config_data = new WebField(this._url)));
+        this._url &&
+            (doc instanceof Doc ? [doc] : doc).forEach(doc => {
+                doc.config_data = new WebField(this._url);
+            });
         return this.addDocument(doc, annotationKey);
     };
 
@@ -900,14 +950,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                 }}
                 onPointerDown={e => this.sidebarBtnDown(e, true)}>
-                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
+                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
             </div>
         );
     }
     @observable _previewNativeWidth: Opt<number> = undefined;
     @observable _previewWidth: Opt<number> = undefined;
     toggleSidebar = action((preview: boolean = false) => {
-        var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
+        let nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
         if (!nativeWidth) {
             const defaultNativeWidth = NumCast(this.Document.nativeWidth, this.dataDoc[this.fieldKey] instanceof WebField ? 850 : NumCast(this.Document._width));
             Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth);
@@ -946,7 +996,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     };
     _innerCollectionView: CollectionFreeFormView | undefined;
     zoomScaling = () => this._innerCollectionView?.zoomScaling() ?? 1;
-    setInnerContent = (component: ViewBoxInterface) => (this._innerCollectionView = component as CollectionFreeFormView);
+    setInnerContent = (component: ViewBoxInterface) => {
+        this._innerCollectionView = component as CollectionFreeFormView;
+    };
 
     @computed get content() {
         const interactive = this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
@@ -977,24 +1029,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 {this.inlineTextAnnotations
                     .sort((a, b) => NumCast(a.y) - NumCast(b.y))
                     .map(anno => (
+                        // eslint-disable-next-line react/jsx-props-no-spreading
                         <Annotation {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
                     ))}
             </div>
         );
     }
     @computed get SidebarShown() {
-        return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false;
+        return !!(this._showSidebar || this.layoutDoc._layout_showSidebar);
     }
     renderAnnotations = (childFilters: () => string[]) => (
         <CollectionFreeFormView
+            // eslint-disable-next-line react/jsx-props-no-spreading
             {...this._props}
             setContentViewBox={this.setInnerContent}
             NativeWidth={returnZero}
             NativeHeight={returnZero}
             originTopLeft={false}
-            isAnnotationOverlayScrollable={true}
+            isAnnotationOverlayScrollable
             renderDepth={this._props.renderDepth + 1}
-            isAnnotationOverlay={true}
+            isAnnotationOverlay
             fieldKey={this.annotationKey}
             setPreviewCursor={this.setPreviewCursor}
             PanelWidth={this.panelWidth}
@@ -1037,7 +1091,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 }}
                 // when active, block wheel events from propagating since they're handled by the iframe
                 onWheel={this.onZoomWheel}
-                onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
+                onScroll={() => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
                 onPointerDown={this.onMarqueeDown}>
                 <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this._props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}>
                     {this.content}
@@ -1053,7 +1107,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         return (
             <div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}>
                 <div className="webBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
-                    <button className="webBox-overlayButton" title={'search'} />
+                    <button type="button" className="webBox-overlayButton" title="search" />
                     <input
                         className="webBox-searchBar"
                         placeholder="Search"
@@ -1064,13 +1118,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                             e.stopPropagation();
                         }}
                     />
-                    <button className="webBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
+                    <button type="button" className="webBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
                         <FontAwesomeIcon icon="search" size="sm" />
                     </button>
                 </div>
                 <button
+                    type="button"
                     className="webBox-overlayButton"
-                    title={'search'}
+                    title="search"
                     onClick={action(() => {
                         this._searching = !this._searching;
                         this.search('', false, true);
@@ -1083,8 +1138,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             </div>
         );
     }
-    searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
-    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
+    searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+        this._searchString = e.currentTarget.value;
+    };
+    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => {
+        this._setPreviewCursor = func;
+    };
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth;
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
     scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
@@ -1157,6 +1216,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this._props.PanelWidth()}%` }}>
                     <SidebarAnnos
                         ref={this._sidebarRef}
+                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                         fieldKey={this.fieldKey + '_' + this._urlHash}
@@ -1177,6 +1237,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         );
     }
 }
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function urlHash(url: string) {
     return stringHash(url);
 });
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index eaa8fffaa..dc388b22a 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/control-has-associated-label */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
@@ -24,90 +27,69 @@ import { OpenWhere } from '../DocumentView';
 import './DashFieldView.scss';
 import { FormattedTextBox } from './FormattedTextBox';
 
-export class DashFieldView {
-    dom: HTMLDivElement; // container for label and value
-    root: any;
-    node: any;
-    tbox: FormattedTextBox;
-    getpos: any;
-    @observable _nodeSelected = false;
-    NodeSelected = () => this._nodeSelected;
+@observer
+export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
+    // eslint-disable-next-line no-use-before-define
+    static Instance: DashFieldViewMenu;
+    static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
+    static toggleFieldHide: () => void = emptyFunction;
+    static toggleValueHide: () => void = emptyFunction;
+    constructor(props: any) {
+        super(props);
+        DashFieldViewMenu.Instance = this;
+    }
 
-    unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
-    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-        makeObservable(this);
-        const self = this;
-        this.node = node;
-        this.tbox = tbox;
-        this.getpos = getPos;
-        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';
-        const tBox = this.tbox;
-        this.dom.onkeypress = function (e: KeyboardEvent) {
-            e.stopPropagation();
-        };
-        this.dom.onkeydown = function (e: KeyboardEvent) {
-            e.stopPropagation();
-            if (e.key === 'Tab') {
-                e.preventDefault();
-                const editor = tbox.EditorView;
-                if (editor) {
-                    const state = editor.state;
-                    for (var i = self.getpos() + 1; i < state.doc.content.size; i++) {
-                        if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
-                            editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
-                            return;
-                        }
-                    }
-                    // tBox.setFocus(state.selection.to);
-                }
-            }
-        };
-        this.dom.onkeyup = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onmousedown = function (e: any) {
-            e.stopPropagation();
-        };
+    showFields = (e: React.MouseEvent) => {
+        DashFieldViewMenu.createFieldView(e);
+        DashFieldViewMenu.Instance.fadeOut(true);
+    };
+    toggleFieldHide = () => {
+        DashFieldViewMenu.toggleFieldHide();
+        DashFieldViewMenu.Instance.fadeOut(true);
+    };
+    toggleValueHide = () => {
+        DashFieldViewMenu.toggleValueHide();
+        DashFieldViewMenu.Instance.fadeOut(true);
+    };
 
-        this.root = ReactDOM.createRoot(this.dom);
-        this.root.render(
-            <DashFieldViewInternal
-                node={node}
-                unclickable={this.unclickable}
-                getPos={getPos}
-                fieldKey={node.attrs.fieldKey}
-                docId={node.attrs.docId}
-                width={node.attrs.width}
-                height={node.attrs.height}
-                hideKey={node.attrs.hideKey}
-                hideValue={node.attrs.hideValue}
-                editable={node.attrs.editable}
-                nodeSelected={this.NodeSelected}
-                tbox={tbox}
-            />
+    @observable _fieldKey = '';
+
+    @action
+    public show = (x: number, y: number, fieldKey: string) => {
+        this._fieldKey = fieldKey;
+        this.jumpTo(x, y, true);
+        const hideMenu = () => {
+            this.fadeOut(true);
+            document.removeEventListener('pointerdown', hideMenu, true);
+        };
+        document.addEventListener('pointerdown', hideMenu, true);
+    };
+    render() {
+        return this.getElement(
+            <>
+                <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
+                    <button type="button" className="antimodeMenu-button" onPointerDown={this.showFields}>
+                        <FontAwesomeIcon icon="eye" size="sm" />
+                    </button>
+                </Tooltip>
+                {this._fieldKey.startsWith('#') ? null : (
+                    <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}>
+                        <button type="button" className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
+                            <FontAwesomeIcon icon="bullseye" size="sm" />
+                        </button>
+                    </Tooltip>
+                )}
+                {this._fieldKey.startsWith('#') ? null : (
+                    <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}>
+                        <button type="button" className="antimodeMenu-button" onPointerDown={this.toggleValueHide}>
+                            <FontAwesomeIcon icon="hashtag" size="sm" />
+                        </button>
+                    </Tooltip>
+                )}
+            </>
         );
     }
-    destroy() {
-        setTimeout(() => {
-            try {
-                this.root.unmount();
-            } catch {}
-        });
-    }
-    deselectNode() {
-        runInAction(() => (this._nodeSelected = false));
-        this.dom.classList.remove('ProseMirror-selectednode');
-    }
-    selectNode() {
-        setTimeout(() => runInAction(() => (this._nodeSelected = true)), 100);
-        this.dom.classList.add('ProseMirror-selectednode');
-    }
 }
-
 interface IDashFieldViewInternal {
     fieldKey: string;
     docId: string;
@@ -137,7 +119,9 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
         makeObservable(this);
         this._fieldKey = this._props.fieldKey;
         this._textBoxDoc = this._props.tbox.Document;
-        const setDoc = action((doc: Doc) => (this._dashDoc = doc));
+        const setDoc = action((doc: Doc) => {
+            this._dashDoc = doc;
+        });
 
         if (this._props.docId) {
             DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc));
@@ -172,7 +156,11 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     // set the display of the field's value (checkbox for booleans, span of text for strings)
     @computed get fieldValueContent() {
         return !this._dashDoc ? null : (
-            <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
+            <div
+                onClick={action(() => {
+                    this._expanded = !this._props.editable ? !this._expanded : true;
+                })}
+                style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
                 <SchemaTableCell
                     Document={this._dashDoc}
                     col={0}
@@ -187,20 +175,20 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     padding={0}
                     getFinfo={emptyFunction}
                     setColumnValues={returnFalse}
-                    allowCRs={true}
+                    allowCRs
                     oneLine={!this._expanded && !this._props.nodeSelected()}
                     finishEdit={this.finishEdit}
                     transform={Transform.Identity}
                     menuTarget={null}
-                    autoFocus={true}
+                    autoFocus
                     rootSelected={this._props.tbox._props.rootSelected}
                 />
             </div>
         );
     }
 
-    createPivotForField = (e: React.MouseEvent) => {
-        let container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement();
+    createPivotForField = () => {
+        const container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement();
         if (container) {
             const embedding = Doc.MakeEmbedding(container.Document);
             embedding._type_collection = CollectionViewType.Time;
@@ -219,7 +207,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     toggleFieldHide = undoable(
         action(() => {
             const editor = this._props.tbox.EditorView!;
-            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey ? true : false }));
+            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey }));
         }),
         'hideKey'
     );
@@ -227,7 +215,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     toggleValueHide = undoable(
         action(() => {
             const editor = this._props.tbox.EditorView!;
-            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue ? true : false }));
+            editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue }));
         }),
         'hideValue'
     );
@@ -243,11 +231,11 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     // clicking on the label creates a pivot view collection of all documents
     // in the same collection.  The pivot field is the fieldKey of this label
     onPointerDownLabelSpan = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, returnFalse, e => {
+        setupMoveUpEvents(this, e, returnFalse, returnFalse, moveEv => {
             DashFieldViewMenu.createFieldView = this.createPivotForField;
             DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide;
             DashFieldViewMenu.toggleValueHide = this.toggleValueHide;
-            DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey);
+            DashFieldViewMenu.Instance.show(moveEv.clientX, moveEv.clientY + 16, this._fieldKey);
             const editor = this._props.tbox.EditorView!;
             setTimeout(() => editor.dispatch(editor.state.tr.setSelection(new NodeSelection(editor.state.doc.resolve(this._props.getPos())))), 100);
         });
@@ -277,7 +265,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                 }}>
                 {this._hideKey ? null : (
                     <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
-                        {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey}
+                        {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : (this._dashDoc?.title ?? '') + ':') + this._fieldKey}
                     </span>
                 )}
                 {this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent}
@@ -293,65 +281,93 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
         );
     }
 }
-@observer
-export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
-    static Instance: DashFieldViewMenu;
-    static createFieldView: (e: React.MouseEvent) => void = emptyFunction;
-    static toggleFieldHide: () => void = emptyFunction;
-    static toggleValueHide: () => void = emptyFunction;
-    constructor(props: any) {
-        super(props);
-        DashFieldViewMenu.Instance = this;
-    }
-
-    showFields = (e: React.MouseEvent) => {
-        DashFieldViewMenu.createFieldView(e);
-        DashFieldViewMenu.Instance.fadeOut(true);
-    };
-    toggleFieldHide = (e: React.MouseEvent) => {
-        DashFieldViewMenu.toggleFieldHide();
-        DashFieldViewMenu.Instance.fadeOut(true);
-    };
-    toggleValueHide = (e: React.MouseEvent) => {
-        DashFieldViewMenu.toggleValueHide();
-        DashFieldViewMenu.Instance.fadeOut(true);
-    };
-
-    @observable _fieldKey = '';
+export class DashFieldView {
+    dom: HTMLDivElement; // container for label and value
+    root: any;
+    node: any;
+    tbox: FormattedTextBox;
+    getpos: any;
+    @observable _nodeSelected = false;
+    NodeSelected = () => this._nodeSelected;
 
-    @action
-    public show = (x: number, y: number, fieldKey: string) => {
-        this._fieldKey = fieldKey;
-        this.jumpTo(x, y, true);
-        const hideMenu = () => {
-            this.fadeOut(true);
-            document.removeEventListener('pointerdown', hideMenu, true);
+    unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        makeObservable(this);
+        const self = this;
+        this.node = node;
+        this.tbox = tbox;
+        this.getpos = getPos;
+        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.onkeypress = function (e: KeyboardEvent) {
+            e.stopPropagation();
         };
-        document.addEventListener('pointerdown', hideMenu, true);
-    };
-    render() {
-        return this.getElement(
-            <>
-                <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}>
-                    <button className="antimodeMenu-button" onPointerDown={this.showFields}>
-                        <FontAwesomeIcon icon="eye" size="sm" />
-                    </button>
-                </Tooltip>
-                {this._fieldKey.startsWith('#') ? null : (
-                    <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}>
-                        <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}>
-                            <FontAwesomeIcon icon="bullseye" size="sm" />
-                        </button>
-                    </Tooltip>
-                )}
-                {this._fieldKey.startsWith('#') ? null : (
-                    <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}>
-                        <button className="antimodeMenu-button" onPointerDown={this.toggleValueHide}>
-                            <FontAwesomeIcon icon="hashtag" size="sm" />
-                        </button>
-                    </Tooltip>
-                )}
-            </>
+        this.dom.onkeydown = function (e: KeyboardEvent) {
+            e.stopPropagation();
+            if (e.key === 'Tab') {
+                e.preventDefault();
+                const editor = tbox.EditorView;
+                if (editor) {
+                    const { state } = editor;
+                    for (let i = self.getpos() + 1; i < state.doc.content.size; i++) {
+                        if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
+                            editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
+                            return;
+                        }
+                    }
+                }
+            }
+        };
+        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(
+            <DashFieldViewInternal
+                node={node}
+                unclickable={this.unclickable}
+                getPos={getPos}
+                fieldKey={node.attrs.fieldKey}
+                docId={node.attrs.docId}
+                width={node.attrs.width}
+                height={node.attrs.height}
+                hideKey={node.attrs.hideKey}
+                hideValue={node.attrs.hideValue}
+                editable={node.attrs.editable}
+                nodeSelected={this.NodeSelected}
+                tbox={tbox}
+            />
         );
     }
+    destroy() {
+        setTimeout(() => {
+            try {
+                this.root.unmount();
+            } catch {
+                /* empty */
+            }
+        });
+    }
+    deselectNode() {
+        runInAction(() => {
+            this._nodeSelected = false;
+        });
+        this.dom.classList.remove('ProseMirror-selectednode');
+    }
+    selectNode() {
+        setTimeout(
+            action(() => {
+                this._nodeSelected = true;
+            }),
+            100
+        );
+        this.dom.classList.add('ProseMirror-selectednode');
+    }
 }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 5b2b558fc..3a1a72910 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -350,7 +350,8 @@ footnote::before {
     span {
         font-family: inherit;
         background-color: inherit;
-        display: inline; // needs to be inline for search highlighting to appear // contents; // fixes problem where extra space is added around <ol> lists when inside a prosemirror span
+        display: inline; // needs to be inline for search highlighting to appear
+        // display: contents; // BUT needs to be 'contents' to avoid Chrome bug where extra space is added above and <ol> lists when inside a prosemirror span
     }
 
     blockquote {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a82f025f9..99a2f4ab9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1531,7 +1531,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
             ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
             ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
-            ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) })] : []),
+            ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { fontFamily: 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: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })],
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index c3a5a2c86..073ed91c3 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -23,7 +23,7 @@ export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle
     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;
-            let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty('type') && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
+            let depth = Array.from(path).reduce((p: number, c: any) => p + (c.type === schema.nodes.ordered_list ? 1 : 0), 0);
             if (node.type === schema.nodes.ordered_list) {
                 if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle;
                 depth++;
@@ -34,18 +34,19 @@ export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle
     return tx2;
 };
 
-export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
+export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMap {
     const keys: { [key: string]: any } = {};
 
     function bind(key: string, cmd: any) {
-        if (mapKeys) {
-            const mapped = mapKeys[key];
-            if (mapped === false) return;
-            if (mapped) key = mapped;
-        }
         keys[key] = cmd;
     }
 
+    function onKey(): boolean | undefined {
+        // bcz: this is pretty hacky -- prosemirror doesn't send us the keyboard event, but the 'event' variable is in scope.. so we access it anyway
+        // eslint-disable-next-line no-restricted-globals
+        return props.onKey?.(event, props);
+    }
+
     const canEdit = (state: any) => {
         switch (GetEffectiveAcl(props.TemplateDataDocument)) {
             case AclAugment:
@@ -84,12 +85,12 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     // 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));
-    bind('Alt-Tab', () => (props.onKey?.(event, props) ? true : true));
-    bind('Meta-Tab', () => (props.onKey?.(event, props) ? true : true));
-    bind('Meta-Enter', () => (props.onKey?.(event, props) ? true : true));
+    bind('Ctrl-Tab', () => onKey() || true);
+    bind('Alt-Tab', () => onKey() || true);
+    bind('Meta-Tab', () => onKey() || true);
+    bind('Meta-Enter', () => onKey() || true);
     bind('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
-        if (props.onKey?.(event, props)) return true;
+        if (onKey()) return true;
         if (!canEdit(state)) return true;
         const ref = state.selection;
         const range = ref.$from.blockRange(ref.$to);
@@ -119,10 +120,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
                 console.log('bullet promote fail');
             }
         }
+        return undefined;
     });
 
     bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
-        if (props.onKey?.(event, props)) return true;
+        if (onKey()) return true;
         if (!canEdit(state)) return true;
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
 
@@ -136,10 +138,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
         ) {
             console.log('bullet demote fail');
         }
+        return undefined;
     });
 
     // Command to create a new Tab with a PDF of all the command shortcuts
-    bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+    bind('Mod-/', () => {
         const newDoc = Docs.Create.PdfDocument(ClientUtils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
         props.addDocTab(newDoc, OpenWhere.addRight);
     });
@@ -171,13 +174,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
         SelectionManager.DeselectAll();
     });
 
-    bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true));
-    bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true));
+    bind('Alt-Enter', () => onKey() || true);
+    bind('Ctrl-Enter', () => onKey() || true);
     bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => {
         dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
         return true;
     });
-    bind('Cmd-?', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+    bind('Cmd-?', () => {
         RTFMarkup.Instance.open();
         return true;
     });
@@ -200,7 +203,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
             const node = resolved.nodeAfter;
             const sm = state.storedMarks || undefined;
             if (node) {
-                tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+                tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]);
             }
         }
         dispatch(tr);
@@ -215,7 +218,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
             const node = resolved.nodeAfter;
             const sm = state.storedMarks || undefined;
             if (node) {
-                tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+                tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]);
             }
         }
         dispatch(tr);
@@ -230,7 +233,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
             const node = resolved.nodeAfter;
             const sm = state.storedMarks || undefined;
             if (node) {
-                tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+                tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]);
             }
         }
         dispatch(tr);
@@ -262,7 +265,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
 
     // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
     const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
-        if (props.onKey?.(event, props)) return true;
+        if (onKey()) return true;
         if (!canEdit(state)) return true;
 
         if (
@@ -296,7 +299,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     // command to break line
 
     const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
-        if (props.onKey?.(event, props)) return true;
+        if (onKey()) return true;
         if (!canEdit(state)) return true;
 
         const trange = state.selection.$from.blockRange(state.selection.$to);
@@ -361,8 +364,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     bind('Enter', enter);
 
     // 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;
+    bind('Space', () => {
+        if (props.TemplateDataDocument && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(props.TemplateDataDocument))) return true;
         return false;
     });
 
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index ec9c1a15d..6108383c2 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -23,10 +23,12 @@ import { FormattedTextBox } from './FormattedTextBox';
 import { updateBullets } from './ProsemirrorExampleTransfer';
 import './RichTextMenu.scss';
 import { schema } from './schema_rts';
+
 const { toggleMark } = require('prosemirror-commands');
 
 @observer
 export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
+    // eslint-disable-next-line no-use-before-define
     static _instance: { menu: RichTextMenu | undefined } = observable({ menu: undefined });
     static get Instance() {
         return RichTextMenu._instance?.menu;
@@ -116,7 +118,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     componentDidMount() {
         this._disposer = reaction(
             () => SelectionManager.Views.slice(),
-            views => this.updateMenu(undefined, undefined, undefined, undefined)
+            () => this.updateMenu(undefined, undefined, undefined, undefined)
         );
     }
     componentWillUnmount() {
@@ -139,10 +141,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
         this.setActiveMarkButtons(this.getActiveMarksOnSelection());
         const active = this.getActiveFontStylesOnSelection();
-        const activeFamilies = active.activeFamilies;
-        const activeSizes = active.activeSizes;
-        const activeColors = active.activeColors;
-        const activeHighlights = active.activeHighlights;
+        const { activeFamilies } = active;
+        const { activeSizes } = active;
+        const { activeColors } = active;
+        const { activeHighlights } = active;
         const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc();
         const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey);
 
@@ -174,8 +176,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
                 dispatch(updateBullets(markup, state.schema));
             } else {
                 const state = this.view?.state;
-                const tr = this.view?.state.tr;
-                if (tr && state) {
+                if (state) {
+                    const { tr } = state;
                     if (dontToggle) {
                         tr.addMark(state.selection.from, state.selection.to, mark);
                         dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
@@ -190,7 +192,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     // finds font sizes and families in selection
     getActiveAlignment() {
         if (this.view && this.TextView?._props.rootSelected?.()) {
-            const path = (this.view.state.selection.$from as any).path;
+            const { path } = this.view.state.selection.$from as any;
             for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
                 if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
                     return path[i].attrs.align || 'left';
@@ -222,24 +224,25 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         const activeColors = new Set<string>();
         const activeHighlights = new Set<string>();
         if (this.view && this.TextView?._props.rootSelected?.()) {
-            const state = this.view.state;
+            const { state } = this.view;
             const pos = this.view.state.selection.$from;
-            var marks: Mark[] = [...(state.storedMarks ?? [])];
+            let marks: Mark[] = [...(state.storedMarks ?? [])];
             if (state.storedMarks !== null) {
+                /* empty */
             } else if (state.selection.empty) {
                 for (let i = 0; i <= pos.depth; i++) {
                     marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
                 }
             } else {
-                state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+                state.doc.nodesBetween(state.selection.from, state.selection.to, (node /* , pos, parent, index */) => {
                     node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
                 });
             }
             marks.forEach(m => {
-                m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.family);
-                m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.color);
+                m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.fontFamily);
+                m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.fontColor);
                 m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize);
-                m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight));
+                m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHigh));
             });
         } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) {
             SelectionManager.Views.forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
@@ -254,26 +257,27 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         return found;
     }
 
-    //finds all active marks on selection in given group
+    // finds all active marks on selection in given group
     getActiveMarksOnSelection() {
         if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[];
 
-        const state = this.view.state;
-        var marks: Mark[] = [...(state.storedMarks ?? [])];
+        const { state } = this.view;
+        let marks: Mark[] = [...(state.storedMarks ?? [])];
         const pos = this.view.state.selection.$from;
         if (state.storedMarks !== null) {
+            /* empty */
         } else if (state.selection.empty) {
             for (let i = 0; i <= pos.depth; i++) {
                 marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks];
             }
         } else {
-            state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node /* , pos, parent, index */) => {
                 node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
             });
         }
         const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
-        return markGroup.filter(mark_type => {
-            const mark = state.schema.mark(mark_type);
+        return markGroup.filter(markType => {
+            const mark = state.schema.mark(markType);
             return mark.isInSet(marks);
         });
     }
@@ -291,7 +295,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         this._superscriptActive = false;
 
         activeMarks.forEach(mark => {
-            // prettier-ignore
             switch (mark.name) {
                 case 'noAutoLinkAnchor': this._noLinkActive = true; break;
                 case 'strong':           this._boldActive = true; break;
@@ -300,7 +303,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
                 case 'strikethrough':    this._strikethroughActive = true; break;
                 case 'subscript':        this._subscriptActive = true; break;
                 case 'superscript':      this._superscriptActive = true; break;
-            }
+                default:
+            } // prettier-ignore
         });
     }
 
@@ -353,53 +357,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         }
     };
 
-    setFontSize = (fontSize: string) => {
+    setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => {
         if (this.view) {
-            if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) {
-                this.TextView.dataDoc[this.TextView.fieldKey + '_fontSize'] = fontSize;
+            if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === value))) {
+                this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value;
                 this.view.focus();
             } else {
-                const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize });
+                const attrs: { [key: string]: string } = {};
+                attrs[fontField] = value;
+                const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
                 this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
                 this.view.focus();
             }
-        } else if (SelectionManager.Views.length) {
-            SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontSize'] = fontSize));
-        } else Doc.UserDoc().fontSize = fontSize;
-        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
-    };
-
-    setFontFamily = (family: string) => {
-        if (this.view) {
-            const fmark = this.view.state.schema.marks.pFontFamily.create({ family });
-            this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
-            this.view.focus();
-        } else if (SelectionManager.Views.length) {
-            SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontFamily'] = family));
-        } else Doc.UserDoc().fontFamily = family;
+        } else Doc.UserDoc()[fontField] = value;
         this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
     };
 
-    setHighlight(color: string) {
-        if (this.view) {
-            const highlightMark = this.view.state.schema.mark(this.view.state.schema.marks.marker, { highlight: color });
-            this.setMark(highlightMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(highlightMark)), true);
-            this.view.focus();
-        } else Doc.UserDoc()._fontHighlight = color;
-        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
-    }
-
-    setColor(color: string) {
-        if (this.view) {
-            const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color });
-            this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true);
-            this.view.focus();
-        } else if (SelectionManager.Views.length) {
-            SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontColor'] = color));
-        } else Doc.UserDoc().fontColor = color;
-        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
-    }
-
     // TODO: remove doesn't work
     // remove all node type and apply the passed-in one to the selected text
     changeListType = (mapStyle: string) => {
@@ -407,7 +380,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         const newMapStyle = active === mapStyle ? '' : mapStyle;
         if (!this.view || newMapStyle === '') return;
 
-        let inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
+        const inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list;
         const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
         if (inList) {
             const tx2 = updateBullets(this.view.state.tr, schema, newMapStyle, this.view.state.doc.resolve(this.view.state.selection.$anchor.before(1) + 1).pos, this.view.state.doc.resolve(this.view.state.selection.$anchor.after(1)).pos);
@@ -428,7 +401,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     insertSummarizer(state: EditorState, dispatch: any) {
         if (state.selection.empty) return false;
         const mark = state.schema.marks.summarize.create();
-        const tr = state.tr;
+        const { tr } = state;
         tr.addMark(state.selection.from, state.selection.to, mark);
         const content = tr.selection.content();
         const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
@@ -436,13 +409,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         return true;
     }
 
-    vcenterToggle = (view: EditorView, dispatch: any) => {
+    vcenterToggle = () => {
         this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered);
     };
     align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
         if (this.TextView?._props.rootSelected?.()) {
-            var tr = view.state.tr;
-            view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
+            let { tr } = view.state;
+            view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos) => {
                 if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) {
                     tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align: alignment }, node.marks);
                     return false;
@@ -455,56 +428,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         }
     };
 
-    insetParagraph(state: EditorState, dispatch: any) {
-        var tr = state.tr;
-        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
+    paragraphSetup(state: EditorState, dispatch: any, field: 'inset' | 'indent', value?: 0 | 10 | -10) {
+        let { tr } = state;
+        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos) => {
             if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
-                const inset = (node.attrs.inset ? Number(node.attrs.inset) : 0) + 10;
-                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
-                return false;
-            }
-            return true;
-        });
-        dispatch?.(tr);
-        return true;
-    }
-    outsetParagraph(state: EditorState, dispatch: any) {
-        var tr = state.tr;
-        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
-            if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
-                const inset = Math.max(0, (node.attrs.inset ? Number(node.attrs.inset) : 0) - 10);
-                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks);
-                return false;
-            }
-            return true;
-        });
-        dispatch?.(tr);
-        return true;
-    }
-
-    indentParagraph(state: EditorState, dispatch: any) {
-        var tr = state.tr;
-        const heading = false;
-        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
-            if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
-                const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
-                const indent = !nodeval ? 25 : nodeval < 0 ? 0 : nodeval + 25;
-                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
-                return false;
-            }
-            return true;
-        });
-        !heading && dispatch?.(tr);
-        return true;
-    }
-
-    hangingIndentParagraph(state: EditorState, dispatch: any) {
-        var tr = state.tr;
-        state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => {
-            if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
-                const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined;
-                const indent = !nodeval ? -25 : nodeval > 0 ? 0 : nodeval - 10;
-                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks);
+                const newValue = !value ?  
+                    (node.attrs[field] ? 0 : node.attrs[field] + 10) : 
+                    Math.max(0, value); // prettier-ignore
+                tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, ...(field === 'inset' ? { inset: newValue } : { indent: newValue }) }, node.marks);
                 return false;
             }
             return true;
@@ -514,7 +445,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     }
 
     insertBlockquote(state: EditorState, dispatch: any) {
-        const path = (state.selection.$from as any).path;
+        const { path } = state.selection.$from as any;
         if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
             lift(state, dispatch);
         } else {
@@ -547,13 +478,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     }
 
     @action
-    fillBrush(state: EditorState, dispatch: any) {
+    fillBrush() {
         if (!this.view) return;
 
         if (!Array.from(this.brushMarks.keys()).length) {
-            const selected_marks = this.getMarksInSelection(this.view.state);
-            if (selected_marks.size >= 0) {
-                this.brushMarks = selected_marks;
+            const selectedMarks = this.getMarksInSelection(this.view.state);
+            if (selectedMarks.size >= 0) {
+                this.brushMarks = selectedMarks;
             }
         } else {
             const { from, to, $from } = this.view.state.selection;
@@ -597,9 +528,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
         const button = (
             <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
-                <button className="antimodeMenu-button color-preview-button">
-                    <FontAwesomeIcon icon="link" size="lg" />
-                </button>
+                {
+                    // eslint-disable-next-line jsx-a11y/control-has-associated-label
+                    <button type="button" className="antimodeMenu-button color-preview-button">
+                        <FontAwesomeIcon icon="link" size="lg" />
+                    </button>
+                }
             </Tooltip>
         );
 
@@ -607,21 +541,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             <div className="dropdown link-menu">
                 <p>Linked to:</p>
                 <input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} />
-                <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, 'add:right')}>
+                <button type="button" className="make-button" onPointerDown={() => this.makeLinkToURL(link)}>
                     Apply hyperlink
                 </button>
                 <div className="divider" />
-                <button className="remove-button" onPointerDown={e => this.deleteLink()}>
+                <button type="button" className="remove-button" onPointerDown={() => this.deleteLink()}>
                     Remove link
                 </button>
             </div>
         );
 
-        return <ButtonDropdown view={this.view} key={'link button'} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />;
+        // eslint-disable-next-line no-use-before-define
+        return <ButtonDropdown view={this.view} key="link button" button={button} dropdownContent={dropdownContent} openDropdownOnButton link />;
     }
 
     async getTextLinkTargetTitle() {
-        if (!this.view) return;
+        if (!this.view) return undefined;
 
         const node = this.view.state.selection.$from.nodeAfter;
         const link = node && node.marks.find(m => m.type.name === 'link');
@@ -633,15 +568,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
                     if (linkclicked) {
                         const linkDoc = await DocServer.GetRefField(linkclicked);
                         if (linkDoc instanceof Doc) {
-                            const link_anchor_1 = await Cast(linkDoc.link_anchor_1, Doc);
-                            const link_anchor_2 = await Cast(linkDoc.link_anchor_2, Doc);
+                            const linkAnchor1 = await Cast(linkDoc.link_anchor_1, Doc);
+                            const linkAnchor2 = await Cast(linkDoc.link_anchor_2, Doc);
                             const currentDoc = SelectionManager.Docs.lastElement();
-                            if (currentDoc && link_anchor_1 && link_anchor_2) {
-                                if (Doc.AreProtosEqual(currentDoc, link_anchor_1)) {
-                                    return StrCast(link_anchor_2.title);
+                            if (currentDoc && linkAnchor1 && linkAnchor2) {
+                                if (Doc.AreProtosEqual(currentDoc, linkAnchor1)) {
+                                    return StrCast(linkAnchor2.title);
                                 }
-                                if (Doc.AreProtosEqual(currentDoc, link_anchor_2)) {
-                                    return StrCast(link_anchor_1.title);
+                                if (Doc.AreProtosEqual(currentDoc, linkAnchor2)) {
+                                    return StrCast(linkAnchor1.title);
                                 }
                             }
                         }
@@ -653,11 +588,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
                 return link.attrs.title;
             }
         }
+        return undefined;
     }
 
     // TODO: should check for valid URL
     @undoBatch
-    makeLinkToURL = (target: string, lcoation: string) => {
+    makeLinkToURL = (target: string) => {
         ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target);
     };
 
@@ -736,7 +672,11 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
 
     render() {
         return (
-            <div className="button-dropdown-wrapper" ref={node => (this.ref = node)}>
+            <div
+                className="button-dropdown-wrapper"
+                ref={node => {
+                    this.ref = node;
+                }}>
                 {!this._props.pdf ? (
                     <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
                         {this._props.button}
@@ -747,9 +687,12 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps
                 ) : (
                     <>
                         {this._props.button}
-                        <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
-                            <FontAwesomeIcon icon="caret-down" size="sm" />
-                        </button>
+                        {
+                            // eslint-disable-next-line jsx-a11y/control-has-associated-label
+                            <button type="button" className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
+                                <FontAwesomeIcon icon="caret-down" size="sm" />
+                            </button>
+                        }
                     </>
                 )}
                 {this.showDropdown ? this._props.dropdownContent : null}
@@ -762,10 +705,11 @@ interface RichTextMenuPluginProps {
     editorProps: any;
 }
 export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
-    render() {
-        return null;
-    }
+    // eslint-disable-next-line react/no-unused-class-component-methods
     update(view: EditorView, lastState: EditorState | undefined) {
         RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, (view as any).TextView?.layoutDoc);
     }
+    render() {
+        return null;
+    }
 }
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 5b5617484..88adab66e 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -263,7 +263,7 @@ export class RichTextRules {
                 };
 
                 if (isValidColor(color)) {
-                    return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
+                    return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ fontColor: color }));
                 }
 
                 return null;
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index d1c7b72a5..6e1f325cf 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -127,29 +127,29 @@ export const marks: { [index: string]: MarkSpec } = {
 
     /* FONTS */
     pFontFamily: {
-        attrs: { family: { default: '' } },
+        attrs: { fontFamily: { default: '' } },
         parseDOM: [
             {
                 tag: 'span',
                 getAttrs(dom: any) {
                     const cstyle = getComputedStyle(dom);
                     if (cstyle.font) {
-                        if (cstyle.font.indexOf('Times New Roman') !== -1) return { family: 'Times New Roman' };
-                        if (cstyle.font.indexOf('Arial') !== -1) return { family: 'Arial' };
-                        if (cstyle.font.indexOf('Georgia') !== -1) return { family: 'Georgia' };
-                        if (cstyle.font.indexOf('Comic Sans') !== -1) return { family: 'Comic Sans MS' };
-                        if (cstyle.font.indexOf('Tahoma') !== -1) return { family: 'Tahoma' };
-                        if (cstyle.font.indexOf('Crimson') !== -1) return { family: 'Crimson Text' };
+                        if (cstyle.font.indexOf('Times New Roman') !== -1) return { fontFamily: 'Times New Roman' };
+                        if (cstyle.font.indexOf('Arial') !== -1) return { fontFamily: 'Arial' };
+                        if (cstyle.font.indexOf('Georgia') !== -1) return { fontFamily: 'Georgia' };
+                        if (cstyle.font.indexOf('Comic Sans') !== -1) return { fontFamily: 'Comic Sans MS' };
+                        if (cstyle.font.indexOf('Tahoma') !== -1) return { fontFamily: 'Tahoma' };
+                        if (cstyle.font.indexOf('Crimson') !== -1) return { fontFamily: 'Crimson Text' };
                     }
-                    return { family: '' };
+                    return { fontFamily: '' };
                 },
             },
         ],
-        toDOM: node => (node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]),
+        toDOM: node => (node.attrs.fontFamily ? ['span', { style: `font-family: "${node.attrs.fontFamily}";` }] : ['span', 0]),
     },
     // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
     pFontColor: {
-        attrs: { color: { default: '' } },
+        attrs: { fontColor: { default: '' } },
         inclusive: true,
         parseDOM: [
             {
@@ -159,24 +159,24 @@ export const marks: { [index: string]: MarkSpec } = {
                 },
             },
         ],
-        toDOM: node => (node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]),
+        toDOM: node => (node.attrs.fontColor ? ['span', { style: 'color:' + node.attrs.fontColor }] : ['span', 0]),
     },
 
-    marker: {
+    pFontHighlight: {
         attrs: {
-            highlight: { default: 'transparent' },
+            fontHighlight: { default: 'transparent' },
         },
         inclusive: true,
         parseDOM: [
             {
                 tag: 'span',
                 getAttrs(dom: any) {
-                    return { highlight: dom.getAttribute('backgroundColor') };
+                    return { fontHighlight: dom.getAttribute('background-color') };
                 },
             },
         ],
         toDOM(node: any) {
-            return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }];
+            return node.attrs.fontHighlight ? ['span', { style: 'background-color:' + node.attrs.fontHighlight }] : ['span', { style: 'background-color: transparent' }];
         },
     },
 
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 70b6604ab..184487b7d 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -4,14 +4,14 @@ import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './Para
 import { DocServer } from '../../../DocServer';
 import { Doc, Field, FieldType } from '../../../../fields/Doc';
 
-const blockquoteDOM: DOMOutputSpec = ['blockquote', 0],
-    hrDOM: DOMOutputSpec = ['hr'],
-    preDOM: DOMOutputSpec = ['pre', ['code', 0]],
-    brDOM: DOMOutputSpec = ['br'],
-    ulDOM: DOMOutputSpec = ['ul', 0];
+const blockquoteDOM: DOMOutputSpec = ['blockquote', 0];
+const hrDOM: DOMOutputSpec = ['hr'];
+const preDOM: DOMOutputSpec = ['pre', ['code', 0]];
+const brDOM: DOMOutputSpec = ['br'];
+// const ulDOM: DOMOutputSpec = ['ul', 0];
 
-function formatAudioTime(time: number) {
-    time = Math.round(time);
+function formatAudioTime(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;
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 518bf66cd..a3b1a419b 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.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 { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
@@ -13,7 +15,6 @@ import { ObjectField } from '../../../../fields/ObjectField';
 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, stringHash } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs } from '../../../documents/Documents';
@@ -30,7 +31,7 @@ import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
 import { CollectionView } from '../../collections/CollectionView';
 import { TreeView } from '../../collections/TreeView';
-import { pinDataTypes, PinProps, ViewBoxBaseComponent } from '../../DocComponent';
+import { pinDataTypes as dataTypes, PinProps, ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
 import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
@@ -38,6 +39,7 @@ import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
+import { SettingsManager } from '../../../util/SettingsManager';
 
 @observer
 export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -61,6 +63,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     _unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things
     _presTimer: NodeJS.Timeout | undefined;
 
+    // eslint-disable-next-line no-use-before-define
     @observable public static Instance: PresBox;
 
     @observable _isChildActive = false;
@@ -118,6 +121,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get selectedDocumentView() {
         if (SelectionManager.Views.length) return SelectionManager.Views[0];
         if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.Document);
+        return undefined;
     }
     @computed get isPres() {
         return this.selectedDoc === this.Document;
@@ -172,7 +176,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             () => this.layoutDoc.presentation_status === PresStatus.Edit,
             editing => editing && 
                 this.childDocs.filter(doc => doc.presentation_indexed !== undefined).forEach(doc => {
-                    this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined));
+                    this.progressivizedItems(doc)?.forEach(indexedDoc => { indexedDoc.opacity = undefined; });
                     doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1);
                 }) // prettier-ignore
         );
@@ -202,13 +206,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     };
 
-    //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
+    // TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
     // TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions
     // No more frames in current doc and next slide is defined, therefore move to next slide
     nextSlide = (slideNum?: number) => {
         const nextSlideInd = slideNum ?? this.itemIndex + 1;
         let curSlideInd = nextSlideInd;
-        //CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.());
+        // CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.());
         this.clearSelectedArray();
         const doGroupWithUp =
             (nextSelected: number, force = false) =>
@@ -220,7 +224,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         if (serial) {
                             this.gotoDocument(nextSelected, this.activeItem, true, async () => {
                                 const waitTime = NumCast(this.activeItem.presentation_duration);
-                                await new Promise<void>(res => setTimeout(() => res(), Math.max(0, waitTime)));
+                                await new Promise<void>(res => {
+                                    setTimeout(res, Math.max(0, waitTime));
+                                });
                                 doGroupWithUp(nextSelected + 1)();
                             });
                         } else {
@@ -239,8 +245,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const targetList = PresBox.targetRenderedDoc(doc);
         if (doc.presentation_indexed !== undefined && targetList) {
             const listItems = (Cast(targetList[Doc.LayoutFieldKey(targetList)], listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[]) ?? DocListCast(targetList[Doc.LayoutFieldKey(targetList) + '_annotations']);
-            return listItems.filter(doc => !doc.layout_unrendered);
+            return listItems.filter(ldoc => !ldoc.layout_unrendered);
         }
+        return undefined;
     };
 
     // go to documents chain
@@ -259,7 +266,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 const targetRenderedDoc = PresBox.targetRenderedDoc(this.activeItem);
                 targetRenderedDoc._dataTransition = 'all 1s';
                 targetRenderedDoc.opacity = 1;
-                setTimeout(() => (targetRenderedDoc._dataTransition = 'inherit'), 1000);
+                setTimeout(() => {
+                    targetRenderedDoc._dataTransition = 'inherit';
+                }, 1000);
                 const listItems = this.progressivizedItems(this.activeItem);
                 if (listItems && presIndexed < listItems.length) {
                     if (!first) {
@@ -280,6 +289,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     return true;
                 }
             }
+            return undefined;
         };
         if (progressiveReveal(false)) return true;
         if (this.childDocs[this.itemIndex + 1] !== undefined) {
@@ -289,7 +299,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
             // before moving onto next slide, run the subroutines :)
             const currentDoc = this.childDocs[this.itemIndex];
-            //could i do this.childDocs[this.itemIndex] for first arg?
+            // could i do this.childDocs[this.itemIndex] for first arg?
             this.runSubroutines(TreeView.GetRunningChildren.get(currentDoc)?.(), this.childDocs[this.itemIndex + 1]);
 
             this.nextSlide(curLast + 1 === this.childDocs.length ? (this.layoutDoc.presLoop ? 0 : curLast) : curLast + 1);
@@ -309,7 +319,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // Called when the user activates 'back' - to move to the previous part of the pres. trail
     @action
     back = () => {
-        const activeItem: Doc = this.activeItem;
+        const { activeItem } = this;
         let prevSelected = this.itemIndex;
         // Functionality for group with up
         let didZoom = activeItem.presentation_movement;
@@ -328,8 +338,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return this.itemIndex;
     };
 
-    //The function that is called when a document is clicked or reached through next or back.
-    //it'll also execute the necessary actions if presentation is playing.
+    // The function that is called when a document is clicked or reached through next or back.
+    // it'll also execute the necessary actions if presentation is playing.
     @undoBatch
     public gotoDocument = action((index: number, from?: Doc, group?: boolean, finished?: () => void) => {
         Doc.UnBrushAllDocs();
@@ -346,13 +356,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 this.startTempMedia(this.targetDoc, this.activeItem);
             }
             if (!group) this.clearSelectedArray();
-            this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array
+            this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); // Update selected array
             this.turnOffEdit();
-            this.navigateToActiveItem(finished); //Handles movement to element only when presentationTrail is list
-            this.doHideBeforeAfter(); //Handles hide after/before
+            this.navigateToActiveItem(finished); // Handles movement to element only when presentationTrail is list
+            this.doHideBeforeAfter(); // Handles hide after/before
         }
     });
-    static pinDataTypes(target?: Doc): pinDataTypes {
+    static pinDataTypes(target?: Doc): dataTypes {
         const targetType = target?.type as any;
         const inkable = [DocumentType.INK].includes(targetType);
         const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._type_collection === CollectionViewType.Stacking;
@@ -363,19 +373,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const datarange = [DocumentType.FUNCPLOT].includes(targetType);
         const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG, DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined;
         const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined;
-        const type_collection = targetType === DocumentType.COL;
+        const typeCollection = targetType === DocumentType.COL;
         const filters = true;
         const pivot = true;
         const dataannos = false;
-        return { scrollable, pannable, inkable, type_collection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos };
+        return { scrollable, pannable, inkable, type_collection: typeCollection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos };
     }
 
     @action
-    playAnnotation = (anno: AudioField) => {};
+    playAnnotation = (/* anno: AudioField */) => {
+        /* empty */
+    };
     @action
-    static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) {
+    // eslint-disable-next-line default-param-last
+    static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: dataTypes, targetDoc?: Doc) {
         const bestTarget = bestTargetView?.Document ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc);
-        if (!bestTarget) return;
+        if (!bestTarget) return undefined;
         let changed = false;
         if (pinDocLayout) {
             if (
@@ -392,20 +405,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 bestTarget.width = NumCast(activeItem.config_width, NumCast(bestTarget.width));
                 bestTarget.height = NumCast(activeItem.config_height, NumCast(bestTarget.height));
                 bestTarget[TransitionTimer] && clearTimeout(bestTarget[TransitionTimer]);
-                bestTarget[TransitionTimer] = setTimeout(() => (bestTarget[TransitionTimer] = bestTarget._dataTransition = undefined), transTime + 10);
+                bestTarget[TransitionTimer] = setTimeout(() => {
+                    bestTarget[TransitionTimer] = bestTarget._dataTransition = undefined;
+                }, transTime + 10);
                 changed = true;
             }
         }
 
         const activeFrame = activeItem.config_activeFrame ?? activeItem.config_currentFrame;
         if (activeFrame !== undefined) {
-            const transTime = NumCast(activeItem.presentation_transition, 500);
+            const frameTime = NumCast(activeItem.presentation_transition, 500);
             const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc);
             const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext;
             if (context) {
                 const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.CollectionFreeFormView;
                 if (ffview?.childDocs) {
-                    PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, transTime);
+                    PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, frameTime);
                     ffview.layoutDoc._currentFrame = NumCast(activeFrame);
                 }
             }
@@ -423,7 +438,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
             }
             bestTarget[fkey + '_usePath'] = activeItem.config_usePath;
-            setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
+            setTimeout(() => {
+                bestTarget._dataTransition = undefined;
+            }, transTime + 10);
         }
         if (pinDataTypes?.datarange || (!pinDataTypes && activeItem.config_xRange !== undefined)) {
             if (bestTarget.xRange !== activeItem.config_xRange) {
@@ -565,7 +582,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         Doc.AddDocToList(bestTarget[DocData], layoutField, doc);
                     }
                 });
-            setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10);
+            setTimeout(
+                () =>
+                    Array.from(transitioned).forEach(
+                        action(doc => {
+                            doc._dataTransition = undefined;
+                        })
+                    ),
+                transTime + 10
+            );
         }
         if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.config_viewBounds !== undefined || activeItem.config_panX !== undefined || activeItem.config_viewScale !== undefined))) && !bestTarget.isGroup) {
             const contentBounds = Cast(activeItem.config_viewBounds, listSpec('number'));
@@ -580,18 +605,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     activeItem.presentation_movement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale);
                     dv.ComponentView?.brushView?.(viewport, transTime, 2500);
                 }
-            } else {
-                if (bestTarget._freeform_panX !== activeItem.config_panX || bestTarget._freeform_panY !== activeItem.config_panY || bestTarget._freeform_scale !== activeItem.config_viewScale) {
-                    bestTarget._freeform_panX = activeItem.config_panX ?? bestTarget._freeform_panX;
-                    bestTarget._freeform_panY = activeItem.config_panY ?? bestTarget._freeform_panY;
-                    bestTarget._freeform_scale = activeItem.config_viewScale ?? bestTarget._freeform_scale;
-                    changed = true;
-                }
+            } else if (bestTarget._freeform_panX !== activeItem.config_panX || bestTarget._freeform_panY !== activeItem.config_panY || bestTarget._freeform_scale !== activeItem.config_viewScale) {
+                bestTarget._freeform_panX = activeItem.config_panX ?? bestTarget._freeform_panX;
+                bestTarget._freeform_panY = activeItem.config_panY ?? bestTarget._freeform_panY;
+                bestTarget._freeform_scale = activeItem.config_viewScale ?? bestTarget._freeform_scale;
+                changed = true;
             }
         }
         if (changed) {
             return bestTargetView?.setViewTransition('all', transTime);
         }
+        return undefined;
     }
 
     /// copies values from the targetDoc (which is the prototype of the pinDoc) to
@@ -628,8 +652,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 pinDoc.config_data = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data;
             }
             if (pinProps.pinData.dataannos) {
-                const fkey = Doc.LayoutFieldKey(targetDoc);
-                pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[DocData][fkey + '_annotations']).filter(doc => !doc.layout_unrendered));
+                const fieldKey = Doc.LayoutFieldKey(targetDoc);
+                pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[DocData][fieldKey + '_annotations']).filter(doc => !doc.layout_unrendered));
             }
             if (pinProps.pinData.inkable) {
                 pinDoc.config_fillColor = targetDoc.fillColor;
@@ -639,19 +663,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             }
             if (pinProps.pinData.scrollable) pinDoc.config_scrollTop = targetDoc._layout_scrollTop;
             if (pinProps.pinData.clippable) {
-                const fkey = Doc.LayoutFieldKey(targetDoc);
-                pinDoc.config_clipWidth = targetDoc[fkey + '_clipWidth'];
+                const fieldKey = Doc.LayoutFieldKey(targetDoc);
+                pinDoc.config_clipWidth = targetDoc[fieldKey + '_clipWidth'];
             }
             if (pinProps.pinData.datarange) {
-                pinDoc.config_xRange = undefined; //targetDoc?.xrange;
-                pinDoc.config_yRange = undefined; //targetDoc?.yrange;
+                pinDoc.config_xRange = undefined; // targetDoc?.xrange;
+                pinDoc.config_yRange = undefined; // targetDoc?.yrange;
             }
             if (pinProps.pinData.map) {
                 // pinDoc.config_latitude = targetDoc?.latitude;
                 // pinDoc.config_longitude = targetDoc?.longitude;
                 pinDoc.config_map_zoom = targetDoc?.map_zoom;
                 pinDoc.config_map_type = targetDoc?.map_type;
-                //...
+                // ...
             }
             if (pinProps.pinData.poslayoutview)
                 pinDoc.config_pinLayoutData = new List<string>(
@@ -711,8 +735,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      * on the right.
      */
     navigateToActiveItem = (afterNav?: () => void) => {
-        const activeItem: Doc = this.activeItem;
-        const targetDoc: Doc = this.targetDoc;
+        const { activeItem, targetDoc } = this;
         const finished = () => {
             afterNav?.();
             targetDoc[Animation] = undefined;
@@ -766,7 +789,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (targetDoc) {
             if (activeItem.presentation_targetDoc instanceof Doc) activeItem.presentation_targetDoc[Animation] = undefined;
 
-            DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, dv => {
+            DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, () => {
                 // if target or the doc it annotates is not in the lightbox, then close the lightbox
                 if (!DocumentManager.Instance.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) {
                     LightboxView.Instance.SetLightboxDoc(undefined);
@@ -798,7 +821,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     opacity = 0;
                 } else if (index === this.itemIndex || !curDoc.presentation_hideAfter) {
                     opacity = 1;
-                    setTimeout(() => (tagDoc._dataTransition = undefined), 1000);
+                    setTimeout(() => {
+                        tagDoc._dataTransition = undefined;
+                    }, 1000);
                 }
             }
             const hidingIndAft =
@@ -828,12 +853,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const savedStates = docs.map(doc => {
             switch (doc.type) {
                 case DocumentType.COL:
-                    if (doc._type_collection === CollectionViewType.Freeform) return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.freeform_panX), y: NumCast(doc.freeform_panY), s: NumCast(doc.freeform_scale) };
+                    if (doc._type_collection === CollectionViewType.Freeform) {
+                        return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.freeform_panX), y: NumCast(doc.freeform_panY), s: NumCast(doc.freeform_scale) };
+                    }
                     break;
                 case DocumentType.INK:
                     if (doc.data instanceof InkField) {
                         return { type: doc.type, doc, data: doc.data?.[Copy](), fillColor: doc.fillColor, color: doc.color, x: NumCast(doc.x), y: NumCast(doc.y) };
                     }
+                    break;
+                default:
             }
             return undefined;
         });
@@ -841,7 +870,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this._exitTrail = () => {
             savedStates
                 .filter(savedState => savedState)
-                .map(savedState => {
+                .forEach(savedState => {
                     switch (savedState?.type) {
                         case CollectionViewType.Freeform:
                             {
@@ -861,6 +890,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 doc.color = color;
                             }
                             break;
+                        default:
                     }
                 });
             LightboxView.Instance.SetLightboxDoc(undefined);
@@ -879,8 +909,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     };
 
-    //The function that resets the presentation by removing every action done by it. It also
-    //stops the presentaton.
+    // The function that resets the presentation by removing every action done by it. It also
+    // stops the presentaton.
     resetPresentation = () => {
         this.childDocs
             .map(doc => PresBox.targetRenderedDoc(doc))
@@ -897,12 +927,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // The function allows for viewing the pres path on toggle
     @action togglePath = (off?: boolean) => {
         this._pathBoolean = off ? false : !this._pathBoolean;
-        CollectionFreeFormView.ShowPresPaths = this._pathBoolean;
+        SnappingManager.SetShowPresPaths(this._pathBoolean);
     };
 
     // The function allows for expanding the view of pres on toggle
     @action toggleExpandMode = () => {
-        runInAction(() => (this._expandBoolean = !this._expandBoolean));
+        runInAction(() => {
+            this._expandBoolean = !this._expandBoolean;
+        });
         this.Document.expandBoolean = this._expandBoolean;
         this.childDocs.forEach(doc => {
             doc.presentation_expandInlineButton = this._expandBoolean;
@@ -918,7 +950,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 const startInd = NumCast(doc.presentation_indexedStart);
                 this.progressivizedItems(doc)
                     ?.slice(startInd)
-                    .forEach(indexedDoc => (indexedDoc.opacity = 0));
+                    .forEach(indexedDoc => {
+                        indexedDoc.opacity = 0;
+                    });
                 doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, startInd);
             }
             // if (doc.presentation_hide && this.childDocs.indexOf(doc) === startIndex)  tagDoc.opacity = 0;
@@ -969,13 +1003,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     public static minimizedWidth = 198;
     public static OpenPresMinimized(doc: Doc, pt: number[]) {
-        doc.overlayX = pt[0];
-        doc.overlayY = pt[1];
+        [doc.overlayX, doc.overlayY] = pt;
         doc._height = 30;
         doc._width = PresBox.minimizedWidth;
         Doc.AddToMyOverlay(doc);
         PresBox.Instance?.initializePresState(PresBox.Instance.itemIndex);
-        return (doc.presentation_status = PresStatus.Manual);
+        doc.presentation_status = PresStatus.Manual;
+        return doc.presentation_status;
     }
 
     /**
@@ -984,12 +1018,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     @undoBatch
     viewChanged = action((e: React.ChangeEvent) => {
-        //@ts-ignore
-        const type_collection = e.target.selectedOptions[0].value as CollectionViewType;
-        this.layoutDoc.presFieldKey = this.fieldKey + (type_collection === CollectionViewType.Tree ? '-linearized' : '');
+        const typeCollection = (e.target as any).selectedOptions[0].value as CollectionViewType;
+        this.layoutDoc.presFieldKey = this.fieldKey + (typeCollection === CollectionViewType.Tree ? '-linearized' : '');
         // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
-        [CollectionViewType.Tree || CollectionViewType.Stacking].includes(type_collection) && (this.Document._pivotField = undefined);
-        this.Document._type_collection = type_collection;
+        [CollectionViewType.Tree || CollectionViewType.Stacking].includes(typeCollection) && (this.Document._pivotField = undefined);
+        this.Document._type_collection = typeCollection;
         if (this.isTreeOrStack) {
             this.layoutDoc._gridGap = 0;
         }
@@ -1001,10 +1034,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     // @undoBatch
     mediaStopChanged = action((e: React.ChangeEvent) => {
-        const activeItem: Doc = this.activeItem;
-        //@ts-ignore
-        const stopDoc = e.target.selectedOptions[0].value as string;
-        const stopDocIndex: number = Number(stopDoc[0]);
+        const { activeItem } = this;
+        const stopDoc = (e.target as any).selectedOptions[0].value as string;
+        const stopDocIndex = Number(stopDoc[0]);
         activeItem.mediaStopDoc = stopDocIndex;
         if (this.childDocs[stopDocIndex - 1].mediaStopTriggerList) {
             const list = DocListCast(this.childDocs[stopDocIndex - 1].mediaStopTriggerList);
@@ -1025,10 +1057,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return StrCast(activeItem.presentation_movement);
     });
 
-    whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isChildActive = isActive)));
+    whenChildContentsActiveChanged = action((isActive: boolean) => {
+        this._props.whenChildContentsActiveChanged((this._isChildActive = isActive));
+    });
     // For dragging documents into the presentation trail
     addDocumentFilter = (docs: Doc[]) => {
-        docs.forEach((doc, i) => {
+        const results = docs.map(doc => {
             if (doc.presentation_targetDoc) return true;
             if (doc.type === DocumentType.LABEL) {
                 const audio = Cast(doc.annotationOn, Doc, null);
@@ -1041,13 +1075,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     return false;
                 }
             } else if (doc.type !== DocumentType.PRES) {
+                // eslint-disable-next-line operator-assignment
                 if (!doc.presentation_targetDoc) doc.title = doc.title + ' - Slide';
                 doc.presentation_targetDoc = doc.createdFrom ?? doc; // dropped document will be a new embedding of an embedded document somewhere else.
                 doc.presentation_movement = PresMovement.Zoom;
                 if (this._expandBoolean) doc.presentation_expandInlineButton = true;
             }
+            return false;
         });
-        return true;
+        return !results.some(r => !r);
     };
 
     childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
@@ -1068,24 +1104,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const tagDoc = Cast(curDoc.presentation_targetDoc, Doc, null);
             if (curDoc && curDoc === this.activeItem)
                 return (
+                    // eslint-disable-next-line react/no-array-index-key
                     <div key={index} className="selectedList-items">
                         <b>
                             {index + 1}. {curDoc.title}
                         </b>
                     </div>
                 );
-            else if (tagDoc)
+            if (tagDoc)
                 return (
+                    // eslint-disable-next-line react/no-array-index-key
                     <div key={index} className="selectedList-items">
                         {index + 1}. {curDoc.title}
                     </div>
                 );
-            else if (curDoc)
+            if (curDoc)
                 return (
+                    // eslint-disable-next-line react/no-array-index-key
                     <div key={index} className="selectedList-items">
                         {index + 1}. {curDoc.title}
                     </div>
                 );
+            return null;
         });
     }
 
@@ -1095,15 +1135,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         presDocView && SelectionManager.SelectView(presDocView, false);
     };
 
-    focusElement = (doc: Doc, options: FocusViewOptions) => {
+    focusElement = (doc: Doc) => {
         this.selectElement(doc);
         return undefined;
     };
 
-    //Regular click
+    // Regular click
     @action
     selectElement = (doc: Doc, noNav = false) => {
-        CollectionStackedTimeline.CurrentlyPlaying?.map((clip, i) => clip?.ComponentView?.Pause?.());
+        CollectionStackedTimeline.CurrentlyPlaying?.map(clip => clip?.ComponentView?.Pause?.());
         if (noNav) {
             const index = this.childDocs.indexOf(doc);
             if (index >= 0 && index < this.childDocs.length) {
@@ -1115,7 +1155,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.updateCurrentPresentation(DocCast(doc.embedContainer));
     };
 
-    //Command click
+    // Command click
     @action
     multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
         if (!this.selectedArray.has(doc)) {
@@ -1130,7 +1170,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.selectPres();
     };
 
-    //Shift click
+    // Shift click
     @action
     shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
         this.clearSelectedArray();
@@ -1145,7 +1185,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.selectPres();
     };
 
-    //regular click
+    // regular click
     @action
     regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, noNav: boolean, selectPres = true) => {
         this.clearSelectedArray();
@@ -1176,9 +1216,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 if (this.layoutDoc.presentation_status === 'edit') {
                     undoBatch(
                         action(() => {
-                            for (const doc of this.selectedArray) {
-                                this.removeDocument(doc);
-                            }
+                            Array.from(this.selectedArray).forEach(doc => this.removeDocument(doc));
                             this.clearSelectedArray();
                             this._eleArray.length = 0;
                             this._dragArray.length = 0;
@@ -1245,8 +1283,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     this.childDocs.forEach(doc => this.addToSelectedArray(doc));
                     handled = true;
                 }
-            default:
                 break;
+            default:
         }
         if (handled) {
             e.stopPropagation();
@@ -1266,6 +1304,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const tagDoc = PresBox.targetRenderedDoc(doc);
             const srcContext = Cast(tagDoc.embedContainer, Doc, null);
             const labelCreator = (top: number, left: number, edge: number, fontSize: number) => (
+                // eslint-disable-next-line react/no-array-index-key
                 <div className="pathOrder" key={tagDoc.id + 'pres' + index} style={{ top, left, width: edge, height: edge, fontSize }} onClick={() => this.selectElement(doc)}>
                     <div className="pathOrder-frame">{index + 1}</div>
                 </div>
@@ -1298,7 +1337,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 order.push(
                     <>
                         {labelCreator(top - indEdge / 2, left - indEdge / 2, indEdge, indFontSize)}
-                        <div className="pathOrder-presPinView" style={{ top, left, width, height, borderWidth: indEdge / 10 }}></div>
+                        <div className="pathOrder-presPinView" style={{ top, left, width, height, borderWidth: indEdge / 10 }} />
                     </>
                 );
             }
@@ -1321,17 +1360,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             .filter(doc => PresBox.targetRenderedDoc(doc)?.embedContainer === collection)
             .forEach((doc, index) => {
                 const tagDoc = PresBox.targetRenderedDoc(doc);
-                if (tagDoc) {
-                    const n1x = NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2;
-                    const n1y = NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2;
-                    if ((index = 0)) pathPoints = n1x + ',' + n1y;
-                    else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
-                } else if (doc.config_pinView) {
-                    const n1x = NumCast(doc.config_panX);
-                    const n1y = NumCast(doc.config_panY);
-                    if ((index = 0)) pathPoints = n1x + ',' + n1y;
-                    else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
-                }
+                const [n1x, n1y] = tagDoc //
+                    ? [NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2, NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2]
+                    : [NumCast(doc.config_panX), NumCast(doc.config_panY)];
+
+                if (index === 0) pathPoints = n1x + ',' + n1y;
+                else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
             });
         return (
             <>
@@ -1377,7 +1411,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @undoBatch
     updateTransitionTime = (number: String, change?: number) => {
-        PresBox.SetTransitionTime(number, (timeInMS: number) => this.selectedArray.forEach(doc => (doc.presentation_transition = timeInMS)), change);
+        PresBox.SetTransitionTime(
+            number,
+            (timeInMS: number) =>
+                this.selectedArray.forEach(doc => {
+                    doc.presentation_transition = timeInMS;
+                }),
+            change
+        );
     };
 
     // Converts seconds to ms and updates presentation_transition
@@ -1387,7 +1428,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (change) scale += change;
         if (scale < 0.01) scale = 0.01;
         if (scale > 1) scale = 1;
-        this.selectedArray.forEach(doc => (doc.config_zoom = scale));
+        this.selectedArray.forEach(doc => {
+            doc.config_zoom = scale;
+        });
     };
 
     /*
@@ -1399,76 +1442,96 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (change) timeInMS += change;
         if (timeInMS < 100) timeInMS = 100;
         if (timeInMS > 20000) timeInMS = 20000;
-        this.selectedArray.forEach(doc => (doc.presentation_duration = timeInMS));
+        this.selectedArray.forEach(doc => {
+            doc.presentation_duration = timeInMS;
+        });
     };
 
     @undoBatch
-    updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_movement = movement)));
+    updateMovement = action((movement: PresMovement, all?: boolean) =>
+        (all ? this.childDocs : this.selectedArray).forEach(doc => {
+            doc.presentation_movement = movement;
+        })
+    );
 
     @undoBatch
     updateHideBefore = (activeItem: Doc) => {
         activeItem.presentation_hideBefore = !activeItem.presentation_hideBefore;
-        this.selectedArray.forEach(doc => (doc.presentation_hideBefore = activeItem.presentation_hideBefore));
+        this.selectedArray.forEach(doc => {
+            doc.presentation_hideBefore = activeItem.presentation_hideBefore;
+        });
     };
 
     @undoBatch
     updateHide = (activeItem: Doc) => {
         activeItem.presentation_hide = !activeItem.presentation_hide;
-        this.selectedArray.forEach(doc => (doc.presentation_hide = activeItem.presentation_hide));
+        this.selectedArray.forEach(doc => {
+            doc.presentation_hide = activeItem.presentation_hide;
+        });
     };
 
     @undoBatch
     updateHideAfter = (activeItem: Doc) => {
         activeItem.presentation_hideAfter = !activeItem.presentation_hideAfter;
-        this.selectedArray.forEach(doc => (doc.presentation_hideAfter = activeItem.presentation_hideAfter));
+        this.selectedArray.forEach(doc => {
+            doc.presentation_hideAfter = activeItem.presentation_hideAfter;
+        });
     };
 
     @undoBatch
     updateOpenDoc = (activeItem: Doc) => {
         activeItem.presentation_openInLightbox = !activeItem.presentation_openInLightbox;
-        this.selectedArray.forEach(doc => (doc.presentation_openInLightbox = activeItem.presentation_openInLightbox));
+        this.selectedArray.forEach(doc => {
+            doc.presentation_openInLightbox = activeItem.presentation_openInLightbox;
+        });
     };
 
     @undoBatch
     updateEaseFunc = (activeItem: Doc) => {
         activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear';
-        this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc));
+        this.selectedArray.forEach(doc => {
+            doc.presEaseFunc = activeItem.presEaseFunc;
+        });
     };
 
     @undoBatch
-    updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_effectDirection = effect));
+    updateEffectDirection = (effect: PresEffectDirection, all?: boolean) =>
+        (all ? this.childDocs : this.selectedArray).forEach(doc => {
+            doc.presentation_effectDirection = effect;
+        });
 
     @undoBatch
-    updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect)));
+    updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) =>
+        (all ? this.childDocs : this.selectedArray).forEach(doc => {
+            bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect);
+        });
 
     static _sliderBatch: any;
     static endBatch = () => {
         PresBox._sliderBatch.end();
         document.removeEventListener('pointerup', PresBox.endBatch, true);
     };
-    public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => {
-        return (
-            <input
-                type="range"
-                step={step}
-                min={min}
-                max={max}
-                value={value}
-                readOnly={true}
-                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');
-                    document.addEventListener('pointerup', PresBox.endBatch, true);
-                    e.stopPropagation();
-                }}
-                onChange={e => {
-                    e.stopPropagation();
-                    change(e.target.value);
-                }}
-            />
-        );
-    };
+    public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => (
+        <input
+            type="range"
+            step={step}
+            min={min}
+            max={max}
+            value={value}
+            readOnly
+            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');
+                document.addEventListener('pointerup', PresBox.endBatch, true);
+                e.stopPropagation();
+            }}
+            onChange={e => {
+                e.stopPropagation();
+                change(e.target.value);
+            }}
+        />
+    );
 
     @undoBatch
     applyTo = (array: Doc[]) => {
@@ -1476,17 +1539,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
         this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true);
         this.updateEffectDirection(this.activeItem.presentation_effectDirection as PresEffectDirection, true);
-        const { presentation_transition, presentation_duration, presentation_hideBefore, presentation_hideAfter } = this.activeItem;
+        // eslint-disable-next-line camelcase
+        const { presentation_transition: pt, presentation_duration: pd, presentation_hideBefore: ph, presentation_hideAfter: pa } = this.activeItem;
         array.forEach(curDoc => {
-            curDoc.presentation_transition = presentation_transition;
-            curDoc.presentation_duration = presentation_duration;
-            curDoc.presentation_hideBefore = presentation_hideBefore;
-            curDoc.presentation_hideAfter = presentation_hideAfter;
+            curDoc.presentation_transition = pt;
+            curDoc.presentation_duration = pd;
+            curDoc.presentation_hideBefore = ph;
+            curDoc.presentation_hideAfter = pa;
         });
     };
 
     @computed get visibilityDurationDropdown() {
-        const activeItem = this.activeItem;
+        const { activeItem } = this;
         if (activeItem && this.targetDoc) {
             const targetType = this.targetDoc.type;
             let duration = activeItem.presentation_duration ? NumCast(activeItem.presentation_duration) / 1000 : 0;
@@ -1502,7 +1566,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 Hide before
                             </div>
                         </Tooltip>
-                        <Tooltip title={<div className="dash-tooltip">{'Hide while presented'}</div>}>
+                        <Tooltip title={<div className="dash-tooltip">Hide while presented</div>}>
                             <div
                                 className={`ribbon-toggle ${activeItem.presentation_hide ? 'active' : ''}`}
                                 style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hide ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
@@ -1511,7 +1575,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             </div>
                         </Tooltip>
 
-                        <Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}>
+                        <Tooltip title={<div className="dash-tooltip">Hide after presented</div>}>
                             <div
                                 className={`ribbon-toggle ${activeItem.presentation_hideAfter ? 'active' : ''}`}
                                 style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hideAfter ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }}
@@ -1520,7 +1584,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             </div>
                         </Tooltip>
 
-                        <Tooltip title={<div className="dash-tooltip">{'Open in lightbox view'}</div>}>
+                        <Tooltip title={<div className="dash-tooltip">Open in lightbox view</div>}>
                             <div
                                 className="ribbon-toggle"
                                 style={{
@@ -1550,15 +1614,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 </div>
                                 <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'} />
+                                        <FontAwesomeIcon icon="caret-up" />
                                     </div>
                                     <div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), -1000)}>
-                                        <FontAwesomeIcon icon={'caret-down'} />
+                                        <FontAwesomeIcon icon="caret-down" />
                                     </div>
                                 </div>
                             </div>
                             {PresBox.inputter('0.1', '0.1', '20', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)}
-                            <div className={'slider-headers'} style={{ display: targetType === DocumentType.AUDIO ? 'none' : 'grid' }}>
+                            <div className="slider-headers" style={{ display: targetType === DocumentType.AUDIO ? 'none' : 'grid' }}>
                                 <div className="slider-text">Short</div>
                                 <div className="slider-text">Medium</div>
                                 <div className="slider-text">Long</div>
@@ -1568,17 +1632,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 </div>
             );
         }
+        return undefined;
     }
     @computed get progressivizeDropdown() {
-        const activeItem = this.activeItem;
+        const { activeItem } = this;
         if (activeItem && this.targetDoc) {
             const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None;
-            const bulletEffect = (effect: PresEffect) => (
+            const bulletEffect = (presEffect: PresEffect) => (
                 <div
-                    className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
+                    className={`presBox-dropdownOption ${activeItem.presentation_effect === presEffect || (presEffect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
                     onPointerDown={StopEvent}
-                    onClick={() => this.updateEffect(effect, true)}>
-                    {effect}
+                    onClick={() => this.updateEffect(presEffect, true)}>
+                    {presEffect}
                 </div>
             );
             return (
@@ -1598,12 +1663,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 // a progressivized slide doesn't have sub-slides, but rather iterates over the data list of the target being progressivized.
                                 // to avoid creating a new slide to correspond to each of the target's data list, we create a computedField to refernce the target's data list.
                                 let dataField = Doc.LayoutFieldKey(tagDoc);
-                                if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField = dataField + '_annotations';
+                                if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField += '_annotations';
 
                                 if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`);
                                 else activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc?.["${dataField}"]`);
                             }}
-                            checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? true : false}
+                            checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined}
                         />
                     </div>
                     <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
@@ -1612,7 +1677,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             className="presBox-checkbox"
                             style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
                             type="checkbox"
-                            onChange={() => (activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1)}
+                            onChange={() => {
+                                activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1;
+                            }}
                             checked={!NumCast(activeItem.presentation_indexedStart)}
                         />
                     </div>
@@ -1622,7 +1689,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             className="presBox-checkbox"
                             style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
                             type="checkbox"
-                            onChange={() => (activeItem.presBulletExpand = !activeItem.presBulletExpand)}
+                            onChange={() => {
+                                activeItem.presBulletExpand = !activeItem.presBulletExpand;
+                            }}
                             checked={BoolCast(activeItem.presBulletExpand)}
                         />
                     </div>
@@ -1642,14 +1711,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 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'} />
+                            <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon="angle-down" />
                             <div
-                                className={'presBox-dropdownOptions'}
+                                className="presBox-dropdownOptions"
                                 style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
                                 onPointerDown={e => e.stopPropagation()}>
                                 {Object.values(PresEffect)
                                     .filter(v => isNaN(Number(v)))
-                                    .map(effect => bulletEffect(effect))}
+                                    .map(peffect => bulletEffect(peffect))}
                             </div>
                         </div>
                     </div>
@@ -1659,7 +1728,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return null;
     }
     @computed get transitionDropdown() {
-        const activeItem = this.activeItem;
+        const { activeItem } = this;
         const preseEffect = (effect: PresEffect) => (
             <div
                 className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
@@ -1715,10 +1784,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 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'} />
+                            <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon="angle-down" />
                             <div
                                 className="presBox-dropdownOptions"
-                                id={'presBoxMovementDropdown'}
+                                id="presBoxMovementDropdown"
                                 onPointerDown={StopEvent}
                                 style={{
                                     color: SnappingManager.userColor,
@@ -1739,10 +1808,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             </div>
                             <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'} />
+                                    <FontAwesomeIcon icon="caret-up" />
                                 </div>
                                 <div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), -0.1)}>
-                                    <FontAwesomeIcon icon={'caret-down'} />
+                                    <FontAwesomeIcon icon="caret-down" />
                                 </div>
                             </div>
                         </div>
@@ -1750,19 +1819,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
                             <div className="presBox-subheading">Transition Time</div>
                             <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
+                                <input className="presBox-input" type="number" readOnly value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
                             </div>
                             <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'} />
+                                    <FontAwesomeIcon icon="caret-up" />
                                 </div>
                                 <div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), -1000)}>
-                                    <FontAwesomeIcon icon={'caret-down'} />
+                                    <FontAwesomeIcon icon="caret-down" />
                                 </div>
                             </div>
                         </div>
                         {PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.updateTransitionTime)}
-                        <div className={'slider-headers'}>
+                        <div className="slider-headers">
                             <div className="slider-text">Fast</div>
                             <div className="slider-text">Medium</div>
                             <div className="slider-text">Slow</div>
@@ -1776,7 +1845,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 className="presBox-checkbox"
                                 style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
                                 type="checkbox"
-                                onChange={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))}
+                                onChange={() => {
+                                    activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio);
+                                }}
                                 checked={BoolCast(activeItem.presPlayAudio)}
                             />
                         </div>
@@ -1786,7 +1857,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 className="presBox-checkbox"
                                 style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
                                 type="checkbox"
-                                onChange={() => (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))}
+                                onChange={() => {
+                                    activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText);
+                                }}
                                 checked={BoolCast(activeItem.presentation_zoomText)}
                             />
                         </div>
@@ -1800,13 +1873,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 color: SnappingManager.userColor,
                                 background: SnappingManager.userVariantColor,
                                 borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5,
-                                border: this._openEffectDropdown ? `solid 2px ${SettingsSnappingManagerManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
+                                border: this._openEffectDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
                             }}>
                             {effect?.toString()}
-                            <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+                            <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon="angle-down" />
                             <div
                                 className="presBox-dropdownOptions"
-                                id={'presBoxMovementDropdown'}
+                                id="presBoxMovementDropdown"
                                 style={{
                                     color: SnappingManager.userColor,
                                     background: SnappingManager.userBackgroundColor,
@@ -1815,7 +1888,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 onPointerDown={e => e.stopPropagation()}>
                                 {Object.values(PresEffect)
                                     .filter(v => isNaN(Number(v)))
-                                    .map(effect => preseEffect(effect))}
+                                    .map(presEffect => preseEffect(presEffect))}
                             </div>
                         </div>
                         <div className="ribbon-doubleButton" style={{ display: effect === PresEffectDirection.None ? 'none' : 'inline-flex' }}>
@@ -1840,20 +1913,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 </div>
             );
         }
+        return undefined;
     }
     @computed get mediaOptionsDropdown() {
-        const activeItem = this.activeItem;
+        const { activeItem } = this;
         if (activeItem && this.targetDoc) {
             const renderTarget = PresBox.targetRenderedDoc(this.activeItem);
             const clipStart = NumCast(renderTarget.clipStart);
             const clipEnd = NumCast(renderTarget.clipEnd, clipStart + NumCast(renderTarget[Doc.LayoutFieldKey(renderTarget) + '_duration']));
-            const config_clipEnd = NumCast(activeItem.config_clipEnd) < NumCast(activeItem.config_clipStart) ? clipEnd - clipStart : NumCast(activeItem.config_clipEnd);
+            const configClipEnd = NumCast(activeItem.config_clipEnd) < NumCast(activeItem.config_clipStart) ? clipEnd - clipStart : NumCast(activeItem.config_clipEnd);
             return (
-                <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+                <div className="presBox-ribbon" onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
                     <div>
                         <div className="ribbon-box">
-                            Start {'&'} End Time
-                            <div className={'slider-headers'}>
+                            Start & End Time
+                            <div className="slider-headers">
                                 <div className="slider-block">
                                     <div className="slider-text" style={{ fontWeight: 500 }}>
                                         Start time (s)
@@ -1863,10 +1937,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                             className="presBox-input"
                                             style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
                                             type="number"
-                                            readOnly={true}
+                                            readOnly
                                             value={NumCast(activeItem.config_clipStart).toFixed(2)}
                                             onKeyDown={e => e.stopPropagation()}
-                                            onChange={action(e => (activeItem.config_clipStart = Number(e.target.value)))}
+                                            onChange={action(e => {
+                                                activeItem.config_clipStart = Number(e.target.value);
+                                            })}
                                         />
                                     </div>
                                 </div>
@@ -1875,7 +1951,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         Duration (s)
                                     </div>
                                     <div className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}>
-                                        {Math.round((config_clipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10}
+                                        {Math.round((configClipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10}
                                     </div>
                                 </div>
                                 <div className="slider-block">
@@ -1888,9 +1964,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                             onKeyDown={e => e.stopPropagation()}
                                             style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
                                             type="number"
-                                            readOnly={true}
-                                            value={config_clipEnd.toFixed(2)}
-                                            onChange={action(e => (activeItem.config_clipEnd = Number(e.target.value)))}
+                                            readOnly
+                                            value={configClipEnd.toFixed(2)}
+                                            onChange={action(e => {
+                                                activeItem.config_clipEnd = Number(e.target.value);
+                                            })}
                                         />
                                     </div>
                                 </div>
@@ -1901,7 +1979,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     step="0.1"
                                     min={clipStart}
                                     max={clipEnd}
-                                    value={config_clipEnd}
+                                    value={configClipEnd}
                                     style={{ gridColumn: 1, gridRow: 1, background: SnappingManager.userColor, color: SnappingManager.userVariantColor }}
                                     className={`toolbar-slider ${'end'}`}
                                     id="toolbar-slider"
@@ -1909,7 +1987,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         this._batch = UndoManager.StartBatch('config_clipEnd');
                                         const endBlock = document.getElementById('endTime');
                                         if (endBlock) {
-                                            endBlock.style.backgroundColor = SnappingManager.userVariantColor;
+                                            endBlock.style.backgroundColor = SnappingManager.userVariantColor ?? '';
                                         }
                                         e.stopPropagation();
                                     }}
@@ -1917,7 +1995,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         this._batch?.end();
                                         const endBlock = document.getElementById('endTime');
                                         if (endBlock) {
-                                            endBlock.style.backgroundColor = SnappingManager.userBackgroundColor;
+                                            endBlock.style.backgroundColor = SnappingManager.userBackgroundColor ?? '';
                                         }
                                     }}
                                     onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1938,7 +2016,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         this._batch = UndoManager.StartBatch('config_clipStart');
                                         const startBlock = document.getElementById('startTime');
                                         if (startBlock) {
-                                            startBlock.style.backgroundColor = SnappingManager.userVariantColor;
+                                            startBlock.style.backgroundColor = SnappingManager.userVariantColor ?? '';
                                         }
                                         e.stopPropagation();
                                     }}
@@ -1946,7 +2024,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         this._batch?.end();
                                         const startBlock = document.getElementById('startTime');
                                         if (startBlock) {
-                                            startBlock.style.backgroundColor = SnappingManager.userBackgroundColor;
+                                            startBlock.style.backgroundColor = SnappingManager.userBackgroundColor ?? '';
                                         }
                                     }}
                                     onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -1957,7 +2035,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             </div>
                             <div className="slider-headers">
                                 <div className="slider-text">{clipStart.toFixed(2)} s</div>
-                                <div className="slider-text"></div>
+                                <div className="slider-text" />
                                 <div className="slider-text">{clipEnd.toFixed(2)} s</div>
                             </div>
                         </div>
@@ -1970,7 +2048,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         className="presBox-checkbox"
                                         type="checkbox"
                                         style={{ border: `solid 1px ${SnappingManager.userColor}` }}
-                                        onChange={() => (activeItem.presentation_mediaStart = 'manual')}
+                                        onChange={() => {
+                                            activeItem.presentation_mediaStart = 'manual';
+                                        }}
                                         checked={activeItem.presentation_mediaStart === 'manual'}
                                     />
                                     <div>On click</div>
@@ -1980,7 +2060,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         className="presBox-checkbox"
                                         style={{ border: `solid 1px ${SnappingManager.userColor}` }}
                                         type="checkbox"
-                                        onChange={() => (activeItem.presentation_mediaStart = 'auto')}
+                                        onChange={() => {
+                                            activeItem.presentation_mediaStart = 'auto';
+                                        }}
                                         checked={activeItem.presentation_mediaStart === 'auto'}
                                     />
                                     <div>Automatically</div>
@@ -1993,7 +2075,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         className="presBox-checkbox"
                                         type="checkbox"
                                         style={{ border: `solid 1px ${SnappingManager.userColor}` }}
-                                        onChange={() => (activeItem.presentation_mediaStop = 'manual')}
+                                        onChange={() => {
+                                            activeItem.presentation_mediaStop = 'manual';
+                                        }}
                                         checked={activeItem.presentation_mediaStop === 'manual'}
                                     />
                                     <div>At media end time</div>
@@ -2003,7 +2087,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         className="presBox-checkbox"
                                         type="checkbox"
                                         style={{ border: `solid 1px ${SnappingManager.userColor}` }}
-                                        onChange={() => (activeItem.presentation_mediaStop = 'auto')}
+                                        onChange={() => {
+                                            activeItem.presentation_mediaStop = 'auto';
+                                        }}
                                         checked={activeItem.presentation_mediaStop === 'auto'}
                                     />
                                     <div>On slide change</div>
@@ -2031,6 +2117,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 </div>
             );
         }
+        return undefined;
     }
     @computed get newDocumentToolbarDropdown() {
         return (
@@ -2094,9 +2181,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @computed get newDocumentDropdown() {
         return (
-            <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
+            <div className="presBox-ribbon" onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
                 <div className="ribbon-box">
-                    Slide Title: <br></br>
+                    Slide Title: <br />
                     <input
                         className="ribbon-textInput"
                         placeholder="..."
@@ -2105,16 +2192,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         onChange={e => {
                             e.stopPropagation();
                             e.preventDefault();
-                            runInAction(() => (this.title = e.target.value));
-                        }}></input>
+                            runInAction(() => {
+                                this.title = e.target.value;
+                            });
+                        }}
+                    />
                 </div>
                 <div className="ribbon-box">
                     Choose type:
                     <div className="ribbon-doubleButton">
-                        <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
+                        <div
+                            title="Text"
+                            className="ribbon-toggle"
+                            style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }}
+                            onClick={action(() => {
+                                this.addFreeform = !this.addFreeform;
+                            })}>
                             Text
                         </div>
-                        <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
+                        <div
+                            title="Freeform"
+                            className="ribbon-toggle"
+                            style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }}
+                            onClick={action(() => {
+                                this.addFreeform = !this.addFreeform;
+                            })}>
                             Freeform
                         </div>
                     </div>
@@ -2122,23 +2224,49 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 <div className="ribbon-box" style={{ display: this.addFreeform ? 'grid' : 'none' }}>
                     Preset layouts:
                     <div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}>
-                        <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'blank'))} />
-                        <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'title'))}>
+                        <div
+                            className="layout"
+                            style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+                            onClick={action(() => {
+                                this.layout = 'blank';
+                            })}
+                        />
+                        <div
+                            className="layout"
+                            style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+                            onClick={action(() => {
+                                this.layout = 'title';
+                            })}>
                             <div className="title">Title</div>
                             <div className="subtitle">Subtitle</div>
                         </div>
-                        <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'header'))}>
+                        <div
+                            className="layout"
+                            style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+                            onClick={action(() => {
+                                this.layout = 'header';
+                            })}>
                             <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>
                                 Section header
                             </div>
                         </div>
-                        <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'content'))}>
+                        <div
+                            className="layout"
+                            style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+                            onClick={action(() => {
+                                this.layout = 'content';
+                            })}>
                             <div className="title" style={{ alignSelf: 'center' }}>
                                 Title
                             </div>
                             <div className="content">Text goes here</div>
                         </div>
-                        <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'twoColumns'))}>
+                        <div
+                            className="layout"
+                            style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
+                            onClick={action(() => {
+                                this.layout = 'twoColumns';
+                            })}>
                             <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>
                                 Title
                             </div>
@@ -2150,8 +2278,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             </div>
                         </div>
                     </div>
-                    <div className="open-layout" onClick={action(() => (this.openLayouts = !this.openLayouts))}>
-                        <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'caret-down'} size={'lg'} />
+                    <div
+                        className="open-layout"
+                        onClick={action(() => {
+                            this.openLayouts = !this.openLayouts;
+                        })}>
+                        <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon="caret-down" size="lg" />
                     </div>
                 </div>
                 <div className="ribbon-final-box">
@@ -2166,17 +2298,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     createNewSlide = (layout?: string, title?: string, freeform?: boolean) => {
-        let doc = undefined;
+        let doc;
         if (layout) doc = this.createTemplate(layout);
         if (freeform && layout) doc = this.createTemplate(layout, title);
         if (!freeform && !layout) doc = Docs.Create.TextDocument('', { _nativeWidth: 400, _width: 225, title: title });
         if (doc) {
             const tabMap = CollectionDockingView.Instance?.tabMap;
-            const tab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc.type === DocumentType.COL)?.DashDoc;
-            const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? tab;
+            const docTab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc.type === DocumentType.COL)?.DashDoc;
+            const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? docTab;
             const data = Cast(presCollection?.data, listSpec(Doc));
-            const config_data = Cast(this.Document.data, listSpec(Doc));
-            if (data && config_data) {
+            const configData = Cast(this.Document.data, listSpec(Doc));
+            if (data && configData) {
                 data.push(doc);
                 this._props.pinToPres(doc, {});
                 this.gotoDocument(this.childDocs.length, this.activeItem);
@@ -2198,12 +2330,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _text_fontSize: '14pt' });
         // prettier-ignore
         switch (layout) {
-            case 'blank': return Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x, y });
-            case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
-            case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
-            case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
-            case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y })
+            case 'blank': return Docs.Create.FreeformDocument([], { title: input || 'Blank slide', _width: 400, _height: 225, x, y });
+            case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input || 'Title slide', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
+            case 'header': return Docs.Create.FreeformDocument([header()], { title: input || 'Section header', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
+            case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input || 'Title and content', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
+            case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input || 'Title and two columns', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y })
+            default:
         }
+        return undefined;
     };
 
     // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view)
@@ -2246,17 +2380,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @action
-    toggleProperties = () => (SnappingManager.Instance.propertiesWidth = SnappingManager.Instance.propertiesWidth > 0 ? 0 : 250);
+    toggleProperties = () => {
+        SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250;
+    };
 
     @computed get toolbar() {
-        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 propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+        const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
         const mode = StrCast(this.Document._type_collection) as CollectionViewType;
         const isMini: boolean = this.toolbarWidth <= 100;
         const activeColor = 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'}>
+            <div id="toolbarContainer" className="presBox-toolbar">
                 {/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
                     <FontAwesomeIcon icon={"plus"} />
                     <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} />
@@ -2266,7 +2402,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }}
                         className="toolbar-button"
                         onClick={this.childDocs.length > 1 ? () => this.togglePath() : undefined}>
-                        <FontAwesomeIcon icon={'exchange-alt'} />
+                        <FontAwesomeIcon icon="exchange-alt" />
                     </div>
                 </Tooltip>
                 {isMini ? null : (
@@ -2274,12 +2410,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <div className="toolbar-divider" />
                         <Tooltip title={<div className="dash-tooltip">{this._presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div>}>
                             <div className="toolbar-button" style={{ cursor: this._presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}>
-                                <FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: this._presKeyEvents ? activeColor : inactiveColor }} />
+                                <FontAwesomeIcon className="toolbar-thumbtack" icon="keyboard" style={{ color: this._presKeyEvents ? activeColor : inactiveColor }} />
                             </div>
                         </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: SnappingManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} />
+                                <FontAwesomeIcon className="toolbar-thumbtack" icon={propIcon} style={{ color: SettingsManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} />
                             </div>
                         </Tooltip>
                     </>
@@ -2324,7 +2460,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     this.gotoDocument(this.itemIndex, this.activeItem);
                                 }
                             })}>
-                            <FontAwesomeIcon icon={'play-circle'} />
+                            <FontAwesomeIcon icon="play-circle" />
                             <div style={{ display: this._props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}>&nbsp; Present</div>
                         </div>
                         {mode === CollectionViewType.Carousel3D || isMini ? null : (
@@ -2333,7 +2469,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 onClick={action(() => {
                                     if (this.childDocs.length) this._presentTools = !this._presentTools;
                                 })}>
-                                <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this._presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'angle-down'} />
+                                <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this._presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon="angle-down" />
                                 {this.presentDropdown}
                             </div>
                         )}
@@ -2396,7 +2532,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             false
                         )
                     }>
-                    <FontAwesomeIcon icon={'arrow-left'} />
+                    <FontAwesomeIcon icon="arrow-left" />
                 </div>
                 <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}>
                     <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.startOrPause(true), false, false)}>
@@ -2424,10 +2560,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             false
                         )
                     }>
-                    <FontAwesomeIcon icon={'arrow-right'} />
+                    <FontAwesomeIcon icon="arrow-right" />
                 </div>
-                <div className="presPanel-divider"></div>
-                <Tooltip title={<div className="dash-tooltip">{'Click to return to 1st slide'}</div>}>
+                <div className="presPanel-divider" />
+                <Tooltip title={<div className="dash-tooltip">Click to return to 1st slide</div>}>
                     <div
                         className="presPanel-button"
                         style={{ border: 'solid 1px white' }}
@@ -2451,7 +2587,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     {inOverlay ? '' : 'Slide'} {this.itemIndex + 1}
                     {this.activeItem?.presentation_indexed !== undefined ? `(${this.activeItem.presentation_indexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
                 </div>
-                <div className="presPanel-divider"></div>
+                <div className="presPanel-divider" />
                 {this._props.PanelWidth() > 250 ? (
                     <div
                         className="presPanel-button-text"
@@ -2465,7 +2601,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     </div>
                 ) : (
                     <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.exitClicked, false, false)}>
-                        <FontAwesomeIcon icon={'times'} />
+                        <FontAwesomeIcon icon="times" />
                     </div>
                 )}
             </div>
@@ -2480,7 +2616,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     @action
-    prevClicked = (e: PointerEvent) => {
+    prevClicked = () => {
         this.back();
         if (this._presTimer) {
             clearTimeout(this._presTimer);
@@ -2489,7 +2625,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     @action
-    nextClicked = (e: PointerEvent) => {
+    nextClicked = () => {
         this.next();
         if (this._presTimer) {
             clearTimeout(this._presTimer);
@@ -2505,7 +2641,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     AddToMap = (treeViewDoc: Doc, index: number[]) => {
         if (!treeViewDoc.presentation_targetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
-        var indexNum = 0;
+        let indexNum = 0;
         for (let i = 0; i < index.length; i++) {
             indexNum += index[i] * 10 ** -i;
         }
@@ -2517,19 +2653,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs
             }
         }
+        return undefined;
     };
 
     SlideIndex = (slideDoc: Doc) => DocListCast(this.dataDoc[this.presFieldKey]).indexOf(slideDoc);
 
-    RemFromMap = (treeViewDoc: Doc, index: number[]) => {
+    RemFromMap = (treeViewDoc: Doc) => {
         if (!treeViewDoc.presentation_targetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
         if (!this._unmounting && this.isTree) {
             this._treeViewMap.delete(treeViewDoc);
             this.dataDoc[this.presFieldKey] = new List<Doc>(this.sort(this._treeViewMap));
         }
+        return undefined;
     };
 
-    sort = (treeView_Map: Map<Doc, number>) => [...treeView_Map.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);
+    sort = (treeViewMap: Map<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);
 
     render() {
         // needed to ensure that the childDocs are loaded for looking up fields
@@ -2541,21 +2679,38 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             (this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
         const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0;
         return this._props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player
-            <div className="miniPres" onClick={e => e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}>
+            <div
+                className="miniPres"
+                onClick={e => e.stopPropagation()}
+                onPointerEnter={action(() => {
+                    this._forceKeyEvents = true;
+                })}>
                 <div
                     className="presPanelOverlay"
                     style={{ display: 'inline-flex', height: 30, background: Doc.ActivePresentation === this.Document ? 'green' : '#323232', top: 0, zIndex: 3000000, boxShadow: this._presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
-                    <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
+                    <Tooltip title={<div className="dash-tooltip">Loop</div>}>
                         <div
                             className="presPanel-button"
                             style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }}
-                            onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}>
-                            <FontAwesomeIcon icon={'redo-alt'} />
+                            onPointerDown={e =>
+                                setupMoveUpEvents(
+                                    this,
+                                    e,
+                                    returnFalse,
+                                    returnFalse,
+                                    () => {
+                                        this.layoutDoc.presLoop = !this.layoutDoc.presLoop;
+                                    },
+                                    false,
+                                    false
+                                )
+                            }>
+                            <FontAwesomeIcon icon="redo-alt" />
                         </div>
                     </Tooltip>
-                    <div className="presPanel-divider"></div>
+                    <div className="presPanel-divider" />
                     <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}>
-                        <FontAwesomeIcon icon={'arrow-left'} />
+                        <FontAwesomeIcon icon="arrow-left" />
                     </div>
                     <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}>
                         <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.startOrPause(true), false, false)}>
@@ -2563,10 +2718,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         </div>
                     </Tooltip>
                     <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}>
-                        <FontAwesomeIcon icon={'arrow-right'} />
+                        <FontAwesomeIcon icon="arrow-right" />
                     </div>
-                    <div className="presPanel-divider"></div>
-                    <Tooltip title={<div className="dash-tooltip">{'Click to return to 1st slide'}</div>}>
+                    <div className="presPanel-divider" />
+                    <Tooltip title={<div className="dash-tooltip">Click to return to 1st slide</div>}>
                         <div className="presPanel-button" style={{ border: 'solid 1px white' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}>
                             <b>1</b>
                         </div>
@@ -2590,15 +2745,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     <div className="Slide">
                         {mode !== CollectionViewType.Invalid ? (
                             <CollectionView
+                                // eslint-disable-next-line react/jsx-props-no-spreading
                                 {...this._props}
                                 PanelWidth={this._props.PanelWidth}
                                 PanelHeight={this.panelHeight}
-                                childIgnoreNativeSize={true}
+                                childIgnoreNativeSize
                                 moveDocument={returnFalse}
-                                ignoreUnrendered={true}
+                                ignoreUnrendered
                                 childDragAction={dropActionType.move}
                                 setContentViewBox={emptyFunction}
-                                //childLayoutFitWidth={returnTrue}
+                                // childLayoutFitWidth={returnTrue}
                                 childOpacity={returnOne}
                                 childClickScript={PresBox.navigateToDocScript}
                                 childLayoutTemplate={this.childLayoutTemplate}
@@ -2629,6 +2785,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 }
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) {
     PresBox.NavigateToTarget(bestTarget, activeItem);
 });
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index fca5a2770..5fa32ad12 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -1,14 +1,16 @@
+/* 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, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
 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 } from '../../../../Utils';
-import { returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
@@ -20,9 +22,9 @@ import { TreeView } from '../../collections/TreeView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
-import { DocumentView } from '../../nodes/DocumentView';
-import { FieldView, FieldViewProps } from '../../nodes/FieldView';
 import { StyleProp } from '../../StyleProvider';
+import { DocumentView } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
 import { PresBox } from './PresBox';
 import './PresElementBox.scss';
 import { PresMovement } from './PresEnums';
@@ -72,7 +74,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     // computes index of this presentation slide in the presBox list
     @computed get indexInPres() {
-        return this.presBoxView?.SlideIndex(this.slideDoc);
+        return this.presBoxView?.SlideIndex(this.slideDoc) ?? 0;
     }
 
     @computed get selectedArray() {
@@ -87,7 +89,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.layoutDoc.layout_hideLinkButton = true;
         this._heightDisposer = reaction(
             () => ({ expand: this.slideDoc.presentation_expandInlineButton, height: this.collapsedHeight }),
-            ({ expand, height }) => (this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0)),
+            ({ expand, height }) => {
+                this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0);
+            },
             { fireImmediately: true }
         );
     }
@@ -95,12 +99,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this._heightDisposer?.();
     }
 
-    presExpandDocumentClick = () => (this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton);
+    presExpandDocumentClick = () => {
+        this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton;
+    };
     embedHeight = () => this.collapsedHeight + this.expandViewHeight;
     embedWidth = () => this._props.PanelWidth() / 2;
-    styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
-        return property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property);
-    };
+    // prettier-ignore
+    styleProvider = ( doc: Doc | undefined, props: Opt<FieldViewProps>, property: string ): any => 
+        (property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property));
     /**
      * The function that is responsible for rendering a preview or not for this
      * presentation element.
@@ -114,7 +120,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     PanelHeight={this.embedHeight}
                     isContentActive={this._props.isContentActive}
                     styleProvider={this.styleProvider}
-                    hideLinkButton={true}
+                    hideLinkButton
                     ScreenToLocalTransform={Transform.Identity}
                     renderDepth={this._props.renderDepth + 1}
                     containerViewPath={returnEmptyDoclist}
@@ -151,7 +157,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         ref={this._titleRef}
                         editing={undefined}
                         contents={doc.title}
-                        overflow={'ellipsis'}
+                        overflow="ellipsis"
                         GetValue={() => StrCast(doc.title)}
                         SetValue={(value: string) => {
                             doc.title = !value.trim().length ? '-untitled-' : value;
@@ -178,10 +184,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         e.preventDefault();
         if (element && !(e.ctrlKey || e.metaKey || e.button === 2)) {
             this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false);
-            setupMoveUpEvents(this, e, this.startDrag, emptyFunction, e => {
-                e.stopPropagation();
-                e.preventDefault();
-                this.presBoxView?.modifierSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+            setupMoveUpEvents(this, e, this.startDrag, emptyFunction, clickEv => {
+                clickEv.stopPropagation();
+                clickEv.preventDefault();
+                this.presBoxView?.modifierSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, clickEv.shiftKey || clickEv.ctrlKey || clickEv.metaKey, clickEv.ctrlKey || clickEv.metaKey, clickEv.shiftKey);
                 this.presBoxView?.activeItem && this.showRecording(this.presBoxView?.activeItem);
             });
         }
@@ -210,7 +216,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         } else if (dragArray.length >= 1) {
             const doc = document.createElement('div');
             doc.className = 'presItem-multiDrag';
-            doc.innerText = 'Move ' + this.selectedArray?.size + ' slides';
+            doc.innerText = 'Move ' + (this.selectedArray?.size ?? 0) + ' slides';
             doc.style.position = 'absolute';
             doc.style.top = e.clientY + 'px';
             doc.style.left = e.clientX - 50 + 'px';
@@ -218,7 +224,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
 
         if (activeItem) {
-            runInAction(() => (this._dragging = true));
+            runInAction(() => {
+                this._dragging = true;
+            });
             DragManager.StartDocumentDrag(
                 dragItem.map(ele => ele),
                 dragData,
@@ -226,7 +234,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 e.clientY,
                 undefined,
                 action(() => {
-                    Array.from(classesToRestore).forEach(pair => (pair[0].className = pair[1]));
+                    Array.from(classesToRestore).forEach(pair => {
+                        // eslint-disable-next-line prefer-destructuring
+                        pair[0].className = pair[1];
+                    });
                     this._dragging = false;
                 })
             );
@@ -235,7 +246,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return false;
     };
 
-    onPointerOver = (e: any) => {
+    onPointerOver = () => {
         document.removeEventListener('pointermove', this.onPointerMove);
         document.addEventListener('pointermove', this.onPointerMove);
     };
@@ -245,7 +256,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const dragIsPresItem = DragManager.docsBeingDragged.some(d => d.presentation_targetDoc);
         if (slide && dragIsPresItem) {
             const rect = slide.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 = slide.clientHeight;
             const halfLine = height / 2;
             if (y <= halfLine) {
@@ -259,7 +270,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         document.removeEventListener('pointermove', this.onPointerMove);
     };
 
-    onPointerLeave = (e: any) => {
+    onPointerLeave = () => {
         const slide = this._itemRef.current;
         if (slide) {
             slide.style.borderTop = '0px';
@@ -341,7 +352,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     hideRecording = undoable(
-        action((e: React.MouseEvent, iconClick: boolean = false) => {
+        action((e: React.MouseEvent) => {
             e.stopPropagation();
             this.removeAllRecordingInOverlay();
         }),
@@ -396,7 +407,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     lfg = (e: React.MouseEvent) => {
         e.stopPropagation();
         // TODO: fix this bug
-        const { toggleChildrenRun } = this.slideDoc;
+        // const { toggleChildrenRun } = this.slideDoc;
         TreeView.ToggleChildrenRun.get(this.slideDoc)?.();
 
         // call this.slideDoc.recurChildren() to get all the children
@@ -407,15 +418,13 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     get toolbarWidth(): number {
         const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
         const width = NumCast(this.presBox?._width);
-        return presBoxDocView ? presBoxDocView._props.PanelWidth() : width ? width : 300;
+        return presBoxDocView ? presBoxDocView._props.PanelWidth() : width || 300;
     }
 
     @computed get presButtons() {
-        const presBox = this.presBox;
+        const { presBox, targetDoc, slideDoc: activeItem } = this;
         const presBoxColor = StrCast(presBox?._backgroundColor);
         const presColorBool = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
-        const targetDoc = this.targetDoc;
-        const activeItem = this.slideDoc;
         const hasChildren = BoolCast(this.slideDoc?.hasChildren);
 
         const items: JSX.Element[] = [];
@@ -442,7 +451,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         );
         items.push(
             <Tooltip key="slash" title={<div className="dash-tooltip">{this.videoRecordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
-                <div className="slideButton" onClick={e => (this.videoRecordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
+                <div className="slideButton" onClick={e => (this.videoRecordingIsInOverlay ? this.hideRecording(e) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
                     <FontAwesomeIcon icon={`video${this.videoRecordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} />
                 </div>
             </Tooltip>
@@ -462,7 +471,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     }>
                     <div
                         className="slideButton"
-                        onClick={() => (activeItem.presentation_groupWithUp = (NumCast(activeItem.presentation_groupWithUp) + 1) % 3)}
+                        onClick={() => {
+                            activeItem.presentation_groupWithUp = (NumCast(activeItem.presentation_groupWithUp) + 1) % 3;
+                        }}
                         style={{
                             zIndex: 1000 - this.indexInPres,
                             fontWeight: 700,
@@ -472,7 +483,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             transform: activeItem.presentation_groupWithUp ? 'translate(0, -17px)' : undefined,
                         }}>
                         <div style={{ transform: activeItem.presentation_groupWithUp ? 'rotate(180deg) translate(0, -17.5px)' : 'rotate(0deg)' }}>
-                            <FontAwesomeIcon icon={'arrow-up'} onPointerDown={e => e.stopPropagation()} />
+                            <FontAwesomeIcon icon="arrow-up" onPointerDown={e => e.stopPropagation()} />
                         </div>
                     </div>
                 </Tooltip>
@@ -501,15 +512,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             this.lfg(e);
                         }}
                         style={{ fontWeight: 700 }}>
-                        <FontAwesomeIcon icon={'circle-play'} onPointerDown={e => e.stopPropagation()} />
+                        <FontAwesomeIcon icon="circle-play" onPointerDown={e => e.stopPropagation()} />
                     </div>
                 </Tooltip>
             );
         }
         items.push(
             <Tooltip key="trash" title={<div className="dash-tooltip">Remove from presentation</div>}>
-                <div className={'slideButton'} onClick={this.removePresentationItem}>
-                    <FontAwesomeIcon icon={'trash'} onPointerDown={e => e.stopPropagation()} />
+                <div className="slideButton" onClick={this.removePresentationItem}>
+                    <FontAwesomeIcon icon="trash" onPointerDown={e => e.stopPropagation()} />
                 </div>
             </Tooltip>
         );
@@ -517,18 +528,17 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get mainItem() {
-        const isSelected: boolean = this.selectedArray?.has(this.slideDoc) ? true : false;
+        const { presBox, slideDoc: activeItem } = this;
+        const isSelected: boolean = !!this.selectedArray?.has(activeItem);
         const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres;
         const miniView: boolean = this.toolbarWidth <= 110;
-        const presBox = this.presBox; //presBox
         const presBoxColor: string = StrCast(presBox?._backgroundColor);
         const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
-        const activeItem: Doc = this.slideDoc;
 
         return (
             <div
                 className="presItem-container"
-                key={this.slideDoc[Id] + this.indexInPres}
+                key={activeItem[Id] + this.indexInPres}
                 ref={this._itemRef}
                 style={{
                     backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
@@ -538,9 +548,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
                     paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
                 }}
-                onDoubleClick={action(e => {
+                onDoubleClick={action(() => {
                     this.toggleProperties();
-                    this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, false);
+                    this.presBoxView?.regularSelect(activeItem, this._itemRef.current!, this._dragRef.current!, false);
                 })}
                 onPointerOver={this.onPointerOver}
                 onPointerLeave={this.onPointerLeave}
@@ -552,11 +562,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 ) : (
                     <div
                         ref={this._dragRef}
-                        className={`presItem-slide ${isCurrent ? 'active' : ''}${this.slideDoc.runProcess ? ' testingv2' : ''}`}
+                        className={`presItem-slide ${isCurrent ? 'active' : ''}${activeItem.runProcess ? ' testingv2' : ''}`}
                         style={{
                             display: 'infline-block',
                             backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
-                            //layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
+                            // layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
                             border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined,
                         }}>
                         <div
@@ -564,7 +574,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             style={{
                                 display: 'inline-flex',
                                 pointerEvents: isSelected ? undefined : 'none',
-                                width: `calc(100% ${this.slideDoc.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
+                                width: `calc(100% ${activeItem.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
                                 cursor: isSelected ? 'text' : 'grab',
                             }}>
                             <div
@@ -577,7 +587,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     }
                                 }}
                                 onClick={e => e.stopPropagation()}>{`${this.indexInPres + 1}.  `}</div>
-                            <EditableView ref={this._titleRef} oneLine={true} editing={!isSelected ? false : undefined} contents={activeItem.title} overflow={'ellipsis'} GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} />
+                            <EditableView ref={this._titleRef} oneLine editing={!isSelected ? false : undefined} contents={activeItem.title} overflow="ellipsis" GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} />
                         </div>
                         {/* <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip> */}
                         {/* <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip> */}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index a1f5ce703..053c88e17 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -62,7 +62,7 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps>
     pinToPres = () => this._props.pinToPres(this.annoTextRegion, {});
 
     @undoBatch
-    makeTargretToggle = () => (this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle);
+    makeTargetToggle = () => { this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle }; // prettier-ignore
 
     isTargetToggler = () => BoolCast(this.annoTextRegion.followLinkToggle);
     @undoBatch
@@ -80,7 +80,7 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps>
         AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this);
         AnchorMenu.Instance.Pinned = false;
         AnchorMenu.Instance.PinToPres = this.pinToPres;
-        AnchorMenu.Instance.MakeTargetToggle = this.makeTargretToggle;
+        AnchorMenu.Instance.MakeTargetToggle = this.makeTargetToggle;
         AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler;
         AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.annoTextRegion);
         AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index cd13d4cbc..4362c4fbb 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Button, IconButton, Type } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
@@ -294,7 +295,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     render() {
         return (
             <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
-                {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <></>}
+                {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : null}
             </div>
         );
     }
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index af9f05a14..0c1a419aa 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -40,6 +42,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(SearchBox, fieldKey);
     }
+    // eslint-disable-next-line no-use-before-define
     public static Instance: SearchBox;
 
     private _inputRef = React.createRef<HTMLInputElement>();
@@ -135,20 +138,23 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
      * This method iterates asynchronously through an array of docs and all docs within those
      * docs, calling the function func on each doc.
      */
-    static async foreachRecursiveDocAsync(docs: Doc[], func: (depth: number, doc: Doc) => void) {
+    static async foreachRecursiveDocAsync(docsIn: Doc[], func: (depth: number, doc: Doc) => void) {
+        let docs = docsIn;
         let newarray: Doc[] = [];
-        var depth = 0;
+        let depth = 0;
         while (docs.length > 0) {
             newarray = [];
+            // eslint-disable-next-line no-await-in-loop
             await Promise.all(
                 docs
                     .filter(d => d)
+                    // eslint-disable-next-line no-loop-func
                     .map(async d => {
                         const fieldKey = Doc.LayoutFieldKey(d);
                         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);
+                        const dataDocs = await DocListCastAsync(data);
+                        dataDocs && newarray.push(...dataDocs);
                         func(depth, d);
                     })
             );
@@ -170,6 +176,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
             case DocumentType.IMG : return 'Img';
             case DocumentType.RTF : return 'Rtf';
             case DocumentType.COL : return 'Col:'+colType.substring(0,3);
+            default:
         } // prettier-ignore
 
         return type.charAt(0).toUpperCase() + type.substring(1, 3);
@@ -298,35 +305,27 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
         if (query) {
             this.searchCollection(query);
             const response = await fetchRecommendations('', query, [], true);
-            const recs = response.recommendations;
+            const recs = response.recommendations as any[];
             const recommendations: IRecommendation[] = [];
-            for (const key in recs) {
-                const title = recs[key].title;
-                const url = recs[key].url;
-                const type = recs[key].type;
-                const text = recs[key].text;
-                const transcript = recs[key].transcript;
-                const previewUrl = recs[key].previewUrl;
-                const embedding = recs[key].embedding;
-                const distance = recs[key].distance;
-                const source = recs[key].source;
-                const related_concepts = recs[key].related_concepts;
-                const docId = recs[key].doc_id;
+            recs.forEach(rec => {
+                const { title, url, type, text, transcript, previewUrl, embedding, distance, source, related_concepts: relatedConcepts, doc_id: docId } = rec;
                 recommendations.push({
-                    title: title,
+                    title,
                     data: url,
-                    type: type,
-                    text: text,
-                    transcript: transcript,
-                    previewUrl: previewUrl,
-                    embedding: embedding,
+                    type,
+                    text,
+                    transcript,
+                    previewUrl,
+                    embedding,
                     distance: Math.round(distance * 100) / 100,
                     source: source,
-                    related_concepts: related_concepts,
-                    docId: docId,
+                    related_concepts: relatedConcepts,
+                    docId,
                 });
-            }
-            const setRecommendations = action(() => (this._recommendations = recommendations));
+            });
+            const setRecommendations = action(() => {
+                this._recommendations = recommendations;
+            });
             setRecommendations();
         }
     };
@@ -375,30 +374,30 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
      * This method renders the search input box, select drop-down menu, and search results.
      */
     render() {
-        var validResults = 0;
+        let validResults = 0;
 
         const isLinkSearch: boolean = this._props.linkSearch;
 
         const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank
 
-        const resultsJSX = Array();
+        const resultsJSX = [] as any[];
 
         const fromDoc = this._props.linkFrom?.();
 
         sortedResults.forEach(result => {
-            var className = 'searchBox-results-scroll-view-result';
+            let className = 'searchBox-results-scroll-view-result';
 
             if (this._selectedResult === result[0]) {
                 className += ' searchBox-results-scroll-view-result-selected';
             }
 
             const formattedType = SearchBox.formatType(StrCast(result[0].type), StrCast(result[0].type_collection));
-            const title = result[0].title;
+            const { title } = result[0];
 
             if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === result[0].type) {
                 validResults++;
                 resultsJSX.push(
-                    <Tooltip key={result[0][Id]} placement={'right'} title={<div className="dash-tooltip">{title as string}</div>}>
+                    <Tooltip key={result[0][Id]} placement="right" title={<div className="dash-tooltip">{title as string}</div>}>
                         <div
                             onClick={
                                 isLinkSearch
@@ -429,6 +428,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
             }
         });
 
+        // eslint-disable-next-line react/jsx-props-no-spreading
         const recommendationsJSX: JSX.Element[] = this._recommendations.map(props => <Recommendation {...props} />);
 
         return (
@@ -459,7 +459,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
                     <div className="searchBox-results-container">
                         <div className="section-header" style={{ background: SettingsManager.userVariantColor }}>
                             <div className="section-title">Results</div>
-                            <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+                            <div className="section-subtitle">{`${validResults} result` + (validResults === 1 ? '' : 's')}</div>
                         </div>
                         <div className="searchBox-results-view">{resultsJSX}</div>
                     </div>
@@ -468,7 +468,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
                     <div className="searchBox-recommendations-container">
                         <div className="section-header" style={{ background: SettingsManager.userVariantColor }}>
                             <div className="section-title">Recommendations</div>
-                            <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+                            <div className="section-subtitle">{`${validResults} result` + (validResults === 1 ? '' : 's')}</div>
                         </div>
                         <div className="searchBox-recommendations-view">{recommendationsJSX}</div>
                     </div>
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index b87e5cdde..1ab0932a3 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -36,6 +36,7 @@ import { dropActionType } from '../../util/DropActionTypes';
  */
 @observer
 export class TopBar extends ObservableReactComponent<{}> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: TopBar;
     @observable private _flipDocumentation = 0;
     constructor(props: any) {
@@ -45,10 +46,12 @@ export class TopBar extends ObservableReactComponent<{}> {
     }
 
     navigateToHome = () => {
-        (CollectionDockingView.Instance?.CaptureThumbnail() ?? new Promise<void>(res => res())).then(() => {
+        (CollectionDockingView.Instance?.CaptureThumbnail() ??
+            new Promise<void>(res => {  res();  })) .then(() => 
+        {
             Doc.ActivePage = 'home';
             DashboardView.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use
-        });
+        }); // prettier-ignore
     };
 
     @computed get color() {
@@ -62,7 +65,9 @@ export class TopBar extends ObservableReactComponent<{}> {
     }
 
     @observable happyHeart: boolean = PingManager.Instance.IsBeating;
-    setHappyHeart = action((status: boolean) => (this.happyHeart = status));
+    setHappyHeart = action((status: boolean) => {
+        this.happyHeart = status;
+    });
     dispose = reaction(
         () => PingManager.Instance.IsBeating,
         isBeating => this.setHappyHeart(isBeating)
@@ -86,7 +91,7 @@ export class TopBar extends ObservableReactComponent<{}> {
                     />
                 ) : (
                     <div className="logo-container">
-                        <img className="logo" src="/assets/medium-blue-light-blue-circle.png" alt="dash logo"></img>
+                        <img className="logo" src="/assets/medium-blue-light-blue-circle.png" alt="dash logo" />
                         <span style={{ color: isDark(this.backgroundColor) ? Colors.LIGHT_GRAY : Colors.DARK_GRAY, fontWeight: 200 }}>brown</span>
                         <span style={{ color: isDark(this.backgroundColor) ? Colors.LIGHT_BLUE : Colors.MEDIUM_BLUE, fontWeight: 500 }}>dash</span>
                     </div>
@@ -193,11 +198,11 @@ export class TopBar extends ObservableReactComponent<{}> {
                         }}
                     />
                 ) : null}
-                <IconButton tooltip={'Issue Reporter ⌘I'} size={Size.SMALL} color={this.color} onClick={ReportManager.Instance.open} icon={<FaBug />} />
+                <IconButton tooltip="Issue Reporter ⌘I" size={Size.SMALL} color={this.color} onClick={ReportManager.Instance.open} icon={<FaBug />} />
                 <Flip key={this._flipDocumentation}>
-                    <IconButton tooltip={'Documentation ⌘D'} size={Size.SMALL} color={this.color} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
+                    <IconButton tooltip="Documentation ⌘D" size={Size.SMALL} color={this.color} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
                 </Flip>
-                <IconButton tooltip={'Settings ⌘⇧S'} size={Size.SMALL} color={this.color} onClick={SettingsManager.Instance.open} icon={<FontAwesomeIcon icon="cog" />} />
+                <IconButton tooltip="Settings ⌘⇧S" size={Size.SMALL} color={this.color} onClick={SettingsManager.Instance.openMgr} icon={<FontAwesomeIcon icon="cog" />} />
                 <IconButton
                     size={Size.SMALL}
                     onClick={ServerStats.Instance.open}
@@ -214,11 +219,13 @@ export class TopBar extends ObservableReactComponent<{}> {
     /**
      * Make the documentation icon flip around to draw attention to it.
      */
-    FlipDocumentationIcon = action(() => (this._flipDocumentation = this._flipDocumentation + 1));
+    FlipDocumentationIcon = action(() => {
+        this._flipDocumentation += 1;
+    });
 
     render() {
         return (
-            //TODO:glr Add support for light / dark mode
+            // TODO:glr Add support for light / dark mode
             <div
                 style={{
                     pointerEvents: 'all',
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 7714ce46d..4512d5c5b 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -7,7 +7,7 @@ import { DocServer } from '../client/DocServer';
 import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
 import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper';
-import { undoable } from '../client/util/UndoManager';
+import { undoable, UndoManager } from '../client/util/UndoManager';
 import { ClientUtils, incrementTitleCopy } from '../ClientUtils';
 import {
     AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks,
@@ -38,7 +38,7 @@ export namespace Field {
      */
     export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean): string {
         const isOnDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
-        const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+        const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
         const valFunc = (field: FieldType): string => {
             const res =
                 field instanceof ComputedField && showComputedValue
@@ -55,7 +55,7 @@ export namespace Field {
                       .trim()
                       .replace(/^new List\((.*)\)$/, '$1');
         };
-        return !Field.IsField(field) ? (key.startsWith('_') ? '=' : '') : (isOnDelegate ? '=' : '') + valFunc(field);
+        return !Field.IsField(cfield) ? (key.startsWith('_') ? '=' : '') : (isOnDelegate ? '=' : '') + valFunc(cfield);
     }
     export function toScriptString(field: FieldType) {
         switch (typeof field) {
@@ -98,9 +98,11 @@ export namespace Field {
     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);
     }
+    // eslint-disable-next-line @typescript-eslint/no-shadow
     export function Copy(field: any) {
         return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
     }
+    UndoManager.SetFieldPrinter(toJavascriptString);
 }
 export type FieldType = number | string | boolean | ObjectField | RefField;
 export type Opt<T> = T | undefined;
@@ -369,7 +371,7 @@ export class Doc extends RefField {
         const sameAuthor = this.author === ClientUtils.CurrentUserEmail();
         const fprefix = 'fields.';
         Object.keys(set ?? {})
-            .filter(key => Object.prototype.hasOwnProperty.call(set, key) && key.startsWith(fprefix))
+            .filter(key => key.startsWith(fprefix))
             .forEach(async key => {
                 const fKey = key.substring(fprefix.length);
                 const fn = async () => {
@@ -396,7 +398,7 @@ export class Doc extends RefField {
             });
         const unset = diff.$unset;
         Object.keys(unset ?? {})
-            .filter(key => Object.prototype.hasOwnProperty.call(unset, key) && key.startsWith(fprefix))
+            .filter(key => key.startsWith(fprefix))
             .forEach(async key => {
                 const fKey = key.substring(7);
                 const fn = () => {
@@ -511,15 +513,13 @@ export namespace Doc {
      */
     export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<FieldType>>>, skipUndefineds: boolean = false, isInitializing = false) {
         isInitializing && (doc[Initializing] = true);
-        Object.keys(fields)
-            .filter(key => Object.prototype.hasOwnProperty.call(fields, key))
-            .forEach(key => {
-                const value = (fields as any)[key];
-                if (!skipUndefineds || value !== undefined) {
-                    // Do we want to filter out undefineds?
-                    doc[key] = value;
-                }
-            });
+        Object.keys(fields).forEach(key => {
+            const value = (fields as any)[key];
+            if (!skipUndefineds || value !== undefined) {
+                // Do we want to filter out undefineds?
+                doc[key] = value;
+            }
+        });
         isInitializing && (doc[Initializing] = false);
         return doc;
     }
@@ -640,7 +640,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 === ClientUtils.CurrentUserEmail());
+        const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(d => !d.embedContainer && d.author === ClientUtils.CurrentUserEmail());
         bestEmbedding && Doc.AddEmbedding(doc, doc);
         return bestEmbedding ?? Doc.MakeEmbedding(doc);
     }
@@ -673,27 +673,27 @@ export namespace Doc {
                 };
                 const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
                 const field = ProxyField.WithoutProxy(() => doc[key]);
-                const copyObjectField = async (field: ObjectField) => {
+                const copyObjectField = async (objField: ObjectField) => {
                     const list = await Cast(doc[key], listSpec(Doc));
                     const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
                     if (docs !== undefined && docs.length) {
                         const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates)));
                         assignKey(new List<Doc>(clones));
                     } else {
-                        assignKey(ObjectField.MakeCopy(field));
-                        if (field instanceof RichTextField) {
-                            if (DocsInTextFieldIds.some(id => field.Data.includes(`"${id}":`))) {
+                        assignKey(ObjectField.MakeCopy(objField));
+                        if (objField instanceof RichTextField) {
+                            if (DocsInTextFieldIds.some(id => objField.Data.includes(`"${id}":`))) {
                                 const docidsearch = new RegExp('(' + DocsInTextFieldIds.map(exp => '(' + exp + ')').join('|') + ')":"([a-z-A-Z0-9_]*)"', 'g');
-                                const rawdocids = field.Data.match(docidsearch);
+                                const rawdocids = objField.Data.match(docidsearch);
                                 const docids = rawdocids?.map((str: string) =>
                                     DocsInTextFieldIds.reduce((output, exp) => output.replace(new RegExp(`${exp}":`, 'g'), ''), str)
                                         .replace(/"/g, '')
                                         .trim()
                                 );
                                 const results = docids && (await DocServer.GetRefFields(docids));
-                                const docs = results && Array.from(Object.keys(results)).map(key => DocCast(results[key]));
-                                docs?.map(doc => doc && Doc.makeClone(doc, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
-                                rtfs.push({ copy, key, field });
+                                const rdocs = results && Array.from(Object.keys(results)).map(rkey => DocCast(results[rkey]));
+                                rdocs?.map(d => d && Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
+                                rtfs.push({ copy, key, field: objField });
                             }
                         }
                     }
@@ -769,7 +769,7 @@ export namespace Doc {
     export async function MakeClone(doc: Doc, cloneLinks = true, cloneTemplates = true, cloneMap: Map<string, Doc> = new Map()) {
         const linkMap = new Map<string, Doc>();
         const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = [];
-        const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.embedContainer ? [DocCast(doc.embedContainer)] : [], cloneLinks, cloneTemplates);
+        const clone = 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.forEach(link => Doc.AddLink?.(link, true));
@@ -787,8 +787,8 @@ export namespace Doc {
             copy[key] = new RichTextField(field.Data.replace(docidsearch, replacer).replace(re, replacer2), field.Text);
         });
         const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs];
-        clonedDocs.forEach(clone => Doc.repairClone(clone, cloneMap, cloneTemplates, repaired));
-        return { clone: copy, map: cloneMap, linkMap };
+        clonedDocs.forEach(cloneDoc => Doc.repairClone(cloneDoc, cloneMap, cloneTemplates, repaired));
+        return { clone, map: cloneMap, linkMap };
     }
 
     const _pendingMap = new Set<string>();
@@ -1236,9 +1236,9 @@ export namespace Doc {
         });
     }
     /// if doc is defined, then it is unhighlighted, otherwise all highlighted docs are unhighlighted
-    export function UnHighlightDoc(doc?: Doc) {
+    export function UnHighlightDoc(docs?: Doc) {
         runInAction(() => {
-            (doc ? [doc] : Array.from(highlightedDocs)).forEach(doc => {
+            (docs ? [docs] : Array.from(highlightedDocs)).forEach(doc => {
                 highlightedDocs.delete(doc);
                 highlightedDocs.delete(doc[DocData]);
                 doc[Highlight] = doc[DocData][Highlight] = false;
diff --git a/src/fields/List.ts b/src/fields/List.ts
index f6e0473ea..f97f208fe 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -2,14 +2,12 @@ import { action, computed, makeObservable, observable } from 'mobx';
 import { alias, list, serializable } from 'serializr';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { Deserializable, afterDocDeserialize, autoObject } from '../client/util/SerializationHelper';
-import { Field, FieldType } from './Doc';
+import { Field, FieldType, StrListCast } from './Doc';
 import { FieldTuples, Self, SelfProxy } from './DocSymbols';
 import { Copy, FieldChanged, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 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: FieldType) {
@@ -223,7 +221,7 @@ class ListImpl<T extends FieldType> extends ObjectField {
         },
     };
     static listGetter(target: any, prop: string | symbol, receiver: any): any {
-        if (ListImpl.listHandlers.hasOwnProperty(prop)) {
+        if (Object.prototype.hasOwnProperty.call(ListImpl.listHandlers, prop)) {
             return ListImpl.listHandlers[prop];
         }
         return getter(target, prop, receiver);
@@ -252,6 +250,7 @@ class ListImpl<T extends FieldType> extends ObjectField {
                 throw new Error("Currently properties can't be defined on documents using Object.defineProperty");
             },
         });
+        // eslint-disable-next-line no-use-before-define
         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);
@@ -287,13 +286,13 @@ class ListImpl<T extends FieldType> extends ObjectField {
 
     private set __fieldTuples(value) {
         this[FieldTuples] = value;
-        for (const key in value) {
-            const item = value[key];
+        Object.keys(value).forEach(key => {
+            const item = value[Number(key)];
             if (item instanceof ObjectField) {
                 item[Parent] = this[Self];
                 item[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], Number(key), item);
             }
-        }
+        });
     }
 
     [Copy]() {
@@ -306,25 +305,24 @@ class ListImpl<T extends FieldType> extends ObjectField {
     @observable
     private [FieldTuples]: StoredType<T>[] = [];
     private [Self] = this;
+    // eslint-disable-next-line no-use-before-define
     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))}]`;
-    }
-    [ToScriptString]() {
-        return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`;
-    }
-    [ToString]() {
-        return `[${(this as any).map((field: any) => Field.toString(field))}]`;
-    }
+    [ToScriptString]()     { return `new List(${this[ToJavascriptString]})`; } // prettier-ignore
+    [ToJavascriptString]() { return `[${(this as any).map((field: any) => Field.toScriptString(field))}]`; } // prettier-ignore
+    [ToString]()           { return `[${(this as any).map((field: any) => Field.toString(field))}]`; } // prettier-ignore
 }
+
+// declare List as a type so you can use it in type declarations, e.g., { l: List, ...}
 export type List<T extends FieldType> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
+// decalre List as a value so you can invoke 'new' on it, e.g., new List<Doc>()
+// eslint-disable-next-line no-redeclare
 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'), []);
+    const L1 = StrListCast(l1);
+    const L2 = StrListCast(l2);
     return !L1 && !L2 ? true : L1 && L2 && L1.length === L2.length && L2.reduce((p, v) => p && L1.includes(v), true);
 }, 'compare two lists');
diff --git a/src/fields/ObjectField.ts b/src/fields/ObjectField.ts
index 6c70adc1d..231086262 100644
--- a/src/fields/ObjectField.ts
+++ b/src/fields/ObjectField.ts
@@ -5,6 +5,7 @@ import { RefField } from './RefField';
 export abstract class ObjectField {
     // prettier-ignore
     public [FieldChanged]?: (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; 
+                             // eslint-disable-next-line no-use-before-define
                              items: FieldType[] | undefined; 
                              length: number | undefined; 
                              hint?: any }, serverOp?: any) => void;
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index 820d9b6ff..4f8058ce4 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -21,7 +21,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
     constructor(value?: T | string) {
         super();
         if (typeof value === 'string') {
-            //this.cache = DocServer.GetCachedRefField(value) as any;
+            // this.cache = DocServer.GetCachedRefField(value) as any;
             this.fieldId = value;
         } else if (value) {
             this.cache = { field: value, p: undefined };
@@ -29,7 +29,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
         }
     }
 
-    [ToValue](doc: any) {
+    [ToValue](/* doc: any */) {
         return ProxyField.toValue(this);
     }
 
@@ -39,10 +39,10 @@ export class ProxyField<T extends RefField> extends ObjectField {
     }
 
     [ToJavascriptString]() {
-        return Field.toScriptString(this[ToValue](undefined)?.value);
+        return Field.toScriptString(this[ToValue]()?.value);
     }
     [ToScriptString]() {
-        return Field.toScriptString(this[ToValue](undefined)?.value); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ?
+        return Field.toScriptString(this[ToValue]()?.value); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ?
     }
     [ToString]() {
         return 'ProxyField';
@@ -59,7 +59,9 @@ export class ProxyField<T extends RefField> extends ObjectField {
         return this._cache;
     }
     private set cache(val: { field: T | undefined; p: FieldWaiting<T> | undefined }) {
-        runInAction(() => (this._cache = { ...val }));
+        runInAction(() => {
+            this._cache = { ...val };
+        });
     }
 
     private failed = false;
@@ -78,7 +80,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
         return this.cache.field ?? this.cache.p;
     }
     @computed get needsRequesting(): boolean {
-        return !this.cache.field && !this.failed && !this._cache.p && !DocServer.GetCachedRefField(this.fieldId) ? true : false;
+        return !!(!this.cache.field && !this.failed && !this._cache.p && !DocServer.GetCachedRefField(this.fieldId));
     }
 
     setExternalValuePromise(externalValuePromise: Promise<any>) {
@@ -92,6 +94,7 @@ export class ProxyField<T extends RefField> extends ObjectField {
     }
 }
 
+// eslint-disable-next-line no-redeclare
 export namespace ProxyField {
     let useProxy = true;
     export function DisableProxyFields() {
@@ -115,9 +118,11 @@ export namespace ProxyField {
         if (useProxy) {
             return { value: value.value };
         }
+        return undefined;
     }
 }
 
+// eslint-disable-next-line no-use-before-define
 function prefetchValue(proxy: PrefetchProxy<RefField>) {
     return proxy.value as any;
 }
diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts
index 364899dc7..ed603e5de 100644
--- a/src/fields/Schema.ts
+++ b/src/fields/Schema.ts
@@ -36,7 +36,7 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
                 if (prop in schema) {
                     const desc = prop === 'proto' ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should?
                     if (typeof desc === 'object' && 'defaultVal' in desc && 'type' in desc) {
-                        //defaultSpec
+                        // defaultSpec
                         return Cast(field, desc.type, desc.defaultVal);
                     }
                     if (typeof desc === 'function' && !ObjectField.isPrototypeOf(desc) && !RefField.isPrototypeOf(desc)) {
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index 6ed94d341..57a310f6d 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -7,6 +7,7 @@ import { RichTextField } from './RichTextField';
 import { ScriptField } from './ScriptField';
 import { CsvField, ImageField, WebField } from './URLField';
 
+// eslint-disable-next-line no-use-before-define
 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> = {
@@ -27,6 +28,7 @@ export type ToType<T extends InterfaceValue> = T extends 'string'
         : T extends ListSpec<infer U>
           ? List<U>
           : // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+            // eslint-disable-next-line @typescript-eslint/no-unused-vars
             T extends DefaultFieldConstructor<infer _U>
             ? never
             : T extends { new (...args: any[]): List<FieldType> }
@@ -37,6 +39,10 @@ export type ToType<T extends InterfaceValue> = T extends 'string'
                   ? R
                   : never;
 
+export interface Interface {
+    [key: string]: InterfaceValue;
+    // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
+}
 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]>>;
 };
@@ -47,10 +53,6 @@ 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;
 // 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 FieldType> = T extends RefField ? never : T;
 
 export type CastCtor = ToConstructor<FieldType> | ListSpec<FieldType>;
@@ -58,13 +60,16 @@ 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>>;
+// eslint-disable-next-line no-redeclare
 export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
+// eslint-disable-next-line no-redeclare
 export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined {
     if (field instanceof Promise) {
         return defaultVal === undefined ? (field.then(f => Cast(f, ctor) as any) as any) : defaultVal === null ? undefined : defaultVal;
     }
     if (field !== undefined && !(field instanceof Promise)) {
         if (typeof ctor === 'string') {
+            // eslint-disable-next-line valid-typeof
             if (typeof field === ctor) {
                 return field as ToType<T>;
             }
@@ -118,7 +123,9 @@ export function ImageCast(field: FieldResult, defaultVal: ImageField | null = nu
 }
 
 export function FieldValue<T extends FieldType, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
+// eslint-disable-next-line no-redeclare
 export function FieldValue<T extends FieldType>(field: FieldResult<T>): Opt<T>;
+// eslint-disable-next-line no-redeclare
 export function FieldValue<T extends FieldType>(field: FieldResult<T>, defaultValue?: T): Opt<T> {
     return field instanceof Promise || field === undefined ? defaultValue : field;
 }
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 1cacfe30c..c73689e1f 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -104,7 +104,7 @@ export const documentSchema = createSchema({
 
 export const collectionSchema = createSchema({
     childLayoutTemplate: Doc, // layout template to use to render children of a collecion
-    childLayoutString: 'string', //layout string to use to render children of a collection
+    childLayoutString: 'string', // layout string to use to render children of a collection
     childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template)
     childDontRegisterViews: 'boolean', // whether views made of this document are registered so that they can be found when drawing links
     onChildClick: ScriptField, // script to run for each child when its clicked
@@ -113,4 +113,5 @@ export const collectionSchema = createSchema({
 });
 
 export type Document = makeInterface<[typeof documentSchema]>;
+// eslint-disable-next-line no-redeclare
 export const Document = makeInterface(documentSchema);
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 4e9d85b6d..4fff2a8e7 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -27,7 +27,6 @@ const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
 
 @observer
 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;
@@ -40,8 +39,8 @@ export class Uploader extends React.Component<ImageUploadProps> {
             await Docs.Prototypes.initialize();
             const imgPrev = document.getElementById('img_preview');
             this.setOpacity(1, '1'); // Slab 1
-            if (imgPrev) {
-                const files: FileList | null = inputRef.current!.files;
+            if (imgPrev && inputRef.current) {
+                const { files } = inputRef.current;
                 this.setOpacity(2, '1'); // Slab 2
                 if (files && files.length !== 0) {
                     this.process = 'Uploading Files';
@@ -69,11 +68,11 @@ 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(ClientUtils.prepend('/getUserDocumentIds'));
-                            if (!res) {
+                            const docidsRes = await rp.get(ClientUtils.prepend('/getUserDocumentIds'));
+                            if (!docidsRes) {
                                 throw new Error('No user id returned');
                             }
-                            const field = await DocServer.GetRefField(JSON.parse(res).userDocumentId);
+                            const field = await DocServer.GetRefField(JSON.parse(docidsRes).userDocumentId);
                             let pending: Opt<Doc>;
                             if (field instanceof Doc) {
                                 pending = col;
@@ -100,10 +99,40 @@ export class Uploader extends React.Component<ImageUploadProps> {
                 setTimeout(this.clearUpload, 3000);
             }
         } catch (error) {
-            this.error = JSON.stringify(error);
+            console.log(JSON.stringify(error));
         }
     };
 
+    // Returns the upload interface for mobile
+    private get uploadInterface() {
+        return (
+            <div className="imgupload_cont">
+                <div className="closeUpload" onClick={() => this.closeUpload()}>
+                    <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 />
+                <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="" alt="" />
+                <div className="loadingImage">
+                    <div className="loadingSlab" id="slab1" />
+                    <div className="loadingSlab" id="slab2" />
+                    <div className="loadingSlab" id="slab3" />
+                    <div className="loadingSlab" id="slab4" />
+                    <div className="loadingSlab" id="slab5" />
+                    <div className="loadingSlab" id="slab6" />
+                    <div className="loadingSlab" id="slab7" />
+                </div>
+                <p className="status">{this.process}</p>
+            </div>
+        );
+    }
+
     // Updates label after a files is selected (so user knows a file is uploaded)
     inputLabel = async () => {
         const files: FileList | null = await inputRef.current!.files;
@@ -112,9 +141,7 @@ export class Uploader extends React.Component<ImageUploadProps> {
         } else if (files && files.length > 1) {
             this.nm = files.length.toString() + ' files selected';
         }
-    };
-
-    // Loops through load icons, and resets buttons
+    }; // Loops through load icons, and resets buttons
     @action
     clearUpload = () => {
         for (let i = 1; i < 8; i++) {
@@ -139,36 +166,6 @@ export class Uploader extends React.Component<ImageUploadProps> {
         if (slab) slab.style.opacity = opacity;
     };
 
-    // Returns the upload interface for mobile
-    private get uploadInterface() {
-        return (
-            <div className="imgupload_cont">
-                <div className="closeUpload" onClick={() => this.closeUpload()}>
-                    <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 />
-                <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="" alt="" />
-                <div className="loadingImage">
-                    <div className="loadingSlab" id="slab1" />
-                    <div className="loadingSlab" id="slab2" />
-                    <div className="loadingSlab" id="slab3" />
-                    <div className="loadingSlab" id="slab4" />
-                    <div className="loadingSlab" id="slab5" />
-                    <div className="loadingSlab" id="slab6" />
-                    <div className="loadingSlab" id="slab7" />
-                </div>
-                <p className="status">{this.process}</p>
-            </div>
-        );
-    }
-
     render() {
         return <MainViewModal contents={this.uploadInterface} isDisplayed interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.closeUpload} />;
     }
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 1d4c0adce..b355e70a7 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -857,7 +857,7 @@ ScriptingGlobals.add(function switchToMobilePresentation() {
     return MobileInterface.Instance.setupDefaultPresentation();
 }, 'opens the presentation on Dash Mobile');
 ScriptingGlobals.add(function openMobileSettings() {
-    return SettingsManager.Instance.open();
+    return SettingsManager.Instance.openMgr();
 }, 'opens settings on Dash Mobile');
 
 // Other global functions for mobile
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 1a759f04d..4cb3d8baf 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -231,9 +231,9 @@ export default class UploadManager extends ApiManager {
                                             await Promise.all(
                                                 [...rdocs, ...ldocs].map(
                                                     doc =>
-                                                        new Promise<void>(res => {
+                                                        new Promise<void>(dbRes => {
                                                             // overwrite mongo doc with json doc contents
-                                                            Database.Instance.replace(doc.id, doc, err => res(err && console.log(err)), true);
+                                                            Database.Instance.replace(doc.id, doc, err => dbRes(err && console.log(err)), true);
                                                         })
                                                 )
                                             );
-- 
cgit v1.2.3-70-g09d2


From 9e809f8748d1812bb03ec6471aa6f97467f8f75a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 23 Apr 2024 16:20:08 -0400
Subject: fixes for rich text menu updates and setting parameters on text doc
 vs within in RTF. Lots of lint cleanup.

---
 package-lock.json                                  |   2 +-
 package.json                                       |   2 +-
 src/client/DocServer.ts                            |  15 +-
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/util/DocumentManager.ts                 |  36 ++-
 src/client/util/request-image-size.ts              |  26 +-
 src/client/views/ScriptingRepl.tsx                 | 191 +++++++-------
 src/client/views/StyleProvider.tsx                 | 108 +++++---
 src/client/views/collections/CollectionMenu.tsx    |  73 ++++--
 .../collectionSchema/CollectionSchemaView.tsx      | 181 +++++++++----
 src/client/views/nodes/DocumentContentsView.tsx    |   2 -
 .../nodes/formattedText/FormattedTextBox.scss      |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  53 ++--
 .../formattedText/ProsemirrorExampleTransfer.ts    |  14 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |  56 ++--
 src/client/views/nodes/formattedText/nodes_rts.ts  |  19 +-
 src/client/views/webcam/DashWebRTCVideo.scss       |  82 ------
 src/client/views/webcam/DashWebRTCVideo.tsx        |  76 ------
 src/client/views/webcam/WebCamLogic.js             | 292 ---------------------
 src/fields/Doc.ts                                  |   3 +
 src/fields/util.ts                                 |   6 +-
 src/server/ActionUtilities.ts                      |  79 +++---
 src/server/ApiManagers/ApiManager.ts               |   4 +-
 src/server/ApiManagers/DeleteManager.ts            |  14 +-
 src/server/ApiManagers/DownloadManager.ts          |   6 +-
 src/server/ApiManagers/MongoStore.js               |  21 +-
 src/server/ApiManagers/SearchManager.ts            |   9 +-
 src/server/ApiManagers/SessionManager.ts           |  26 +-
 src/server/ApiManagers/UtilManager.ts              |  31 +--
 .../Session/agents/applied_session_agent.ts        |  23 +-
 src/server/DashSession/Session/agents/monitor.ts   |  48 ++--
 .../Session/agents/process_message_router.ts       |  12 +-
 .../Session/agents/promisified_ipc_manager.ts      |  51 ++--
 .../DashSession/Session/agents/server_worker.ts    |  46 ++--
 src/server/DashSession/Session/utilities/repl.ts   |  76 +++---
 .../Session/utilities/session_config.ts            | 104 ++++----
 .../DashSession/Session/utilities/utilities.ts     |  43 ++-
 src/server/DashStats.ts                            |   5 +-
 src/server/DashUploadUtils.ts                      |   8 +-
 src/server/GarbageCollector.ts                     |  70 +++--
 src/server/MemoryDatabase.ts                       |  37 +--
 src/server/PdfTypes.ts                             |  20 +-
 src/server/ProcessFactory.ts                       |  54 ++--
 src/server/RouteSubscriber.ts                      |   5 +-
 src/server/SharedMediaTypes.ts                     |  31 +--
 src/server/apis/google/CredentialsLoader.ts        |  24 +-
 src/server/apis/google/GoogleApiServerUtils.ts     |   8 +-
 src/server/apis/google/SharedTypes.ts              |  13 +-
 src/server/apis/youtube/youtubeApiSample.d.ts      |   2 +-
 src/server/authentication/DashUserModel.ts         |   8 +-
 src/server/authentication/Passport.ts              |  25 +-
 src/server/database.ts                             |   1 +
 src/server/index.ts                                |  48 ++--
 src/server/updateProtos.ts                         |   6 +-
 src/server/websocket.ts                            |  40 +--
 55 files changed, 965 insertions(+), 1274 deletions(-)
 delete mode 100644 src/client/views/webcam/DashWebRTCVideo.scss
 delete mode 100644 src/client/views/webcam/DashWebRTCVideo.tsx
 delete mode 100644 src/client/views/webcam/WebCamLogic.js

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

diff --git a/package-lock.json b/package-lock.json
index c616bb6ea..03b788ba4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -86,6 +86,7 @@
                 "D": "^1.0.0",
                 "d3": "^7.8.5",
                 "depcheck": "^1.4.7",
+                "dotenv": "^16.3.1",
                 "eslint-webpack-plugin": "^4.1.0",
                 "exif": "^0.6.0",
                 "exifr": "^7.1.3",
@@ -273,7 +274,6 @@
                 "@types/youtube": "0.0.50",
                 "chai": "^5.0.0",
                 "cross-env": "^7.0.3",
-                "dotenv": "^16.3.1",
                 "eslint": "^8.55.0",
                 "eslint-config-airbnb": "^19.0.4",
                 "eslint-config-node": "^4.1.0",
diff --git a/package.json b/package.json
index a3ba6bdd9..9be1e093d 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,6 @@
         "@types/youtube": "0.0.50",
         "chai": "^5.0.0",
         "cross-env": "^7.0.3",
-        "dotenv": "^16.3.1",
         "eslint": "^8.55.0",
         "eslint-config-airbnb": "^19.0.4",
         "eslint-config-node": "^4.1.0",
@@ -169,6 +168,7 @@
         "D": "^1.0.0",
         "d3": "^7.8.5",
         "depcheck": "^1.4.7",
+        "dotenv": "^16.3.1",
         "eslint-webpack-plugin": "^4.1.0",
         "exif": "^0.6.0",
         "exifr": "^7.1.3",
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index b833d3287..cf7a61d24 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -8,7 +8,7 @@ import { FieldLoader } from '../fields/FieldLoader';
 import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols';
 import { ObjectField, SetObjGetRefField, SetObjGetRefFields } from '../fields/ObjectField';
 import { RefField } from '../fields/RefField';
-import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message';
+import { GestureContent, Message, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from '../server/Message';
 import { SerializationHelper } from './util/SerializationHelper';
 
 /**
@@ -63,7 +63,7 @@ export namespace DocServer {
 
         return foundDocId ? (_cache[foundDocId] as Doc) : undefined;
     }
-    export let _socket: Socket;
+    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
@@ -317,7 +317,7 @@ export namespace DocServer {
         // 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)) {
+        for (const id of ids.filter(filterid => filterid)) {
             const cached = _cache[id];
             if (cached === undefined) {
                 defaultPromises.push({
@@ -362,6 +362,7 @@ export namespace DocServer {
             for (const field of serializedFields) {
                 processed++;
                 if (processed % 150 === 0) {
+                    // eslint-disable-next-line no-loop-func
                     runInAction(() => {
                         FieldLoader.ServerLoadStatus.retrieved = processed;
                     });
@@ -375,6 +376,7 @@ export namespace DocServer {
                     // deserialize
                     // adds to a list of promises that will be awaited asynchronously
                     promises.push(
+                        // eslint-disable-next-line no-loop-func
                         (_cache[field.id] = SerializationHelper.Deserialize(field).then(deserialized => {
                             // overwrite or delete any promises (that we inserted as flags
                             // to indicate that the field was in the process of being fetched). Now everything
@@ -447,7 +449,10 @@ export namespace DocServer {
     }
 
     // WRITE A NEW DOCUMENT TO THE SERVER
-    export let CacheNeedsUpdate = false;
+    let _cacheNeedsUpdate = false;
+    export function CacheNeedsUpdate() {
+        return _cacheNeedsUpdate;
+    }
 
     /**
      * A wrapper around the function local variable _createField.
@@ -456,7 +461,7 @@ export namespace DocServer {
      * @param field the [RefField] to be serialized and sent to the server to be stored in the database
      */
     export function CreateField(field: RefField) {
-        CacheNeedsUpdate = true;
+        _cacheNeedsUpdate = true;
         _CreateField(field);
     }
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6dba8027d..acbd0c0b9 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -946,7 +946,7 @@ pie title Minerals in my tap water
         // eslint-disable-next-line no-new
         new LinkManager();
 
-        DocServer.CacheNeedsUpdate && setTimeout(UPDATE_SERVER_CACHE, 2500);
+        DocServer.CacheNeedsUpdate() && setTimeout(UPDATE_SERVER_CACHE, 2500);
         setInterval(UPDATE_SERVER_CACHE, 120000);
         return doc;
     }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 9a7786125..cca92816f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -178,11 +178,13 @@ export class DocumentManager {
         return containerDocContext;
     }
 
+    static _howl: Howl;
     static playAudioAnno(doc: Doc) {
         const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement();
         if (anno) {
+            this._howl?.stop();
             if (anno instanceof AudioField) {
-                new Howl({
+                this._howl = new Howl({
                     src: [anno.url.href],
                     format: ['mp3'],
                     autoplay: true,
@@ -200,13 +202,13 @@ export class DocumentManager {
     static _overlayViews = new ObservableSet<DocumentView>();
 
     public static LinkCommonAncestor(linkDoc: Doc) {
-        const anchor = (which: number) => {
+        const getAnchor = (which: number) => {
             const anch = DocCast(linkDoc['link_anchor_' + which]);
             const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
             return DocumentManager.Instance.getDocumentView(anchor);
         };
-        const anchor1 = anchor(1);
-        const anchor2 = anchor(2);
+        const anchor1 = getAnchor(1);
+        const anchor2 = getAnchor(2);
         return anchor1
             ?.docViewPath()
             .reverse()
@@ -275,9 +277,13 @@ export class DocumentManager {
 
         if (rootContextView) {
             const target = await this.focusViewsInPath(rootContextView, options, childViewIterator);
-            this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
-            finished?.(target.focused);
-        } else finished?.(false);
+            if (target) {
+                this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
+                finished?.(target.focused);
+                return;
+            }
+        }
+        finished?.(false);
     };
 
     focusViewsInPath = async (
@@ -289,12 +295,15 @@ export class DocumentManager {
         let focused = false;
         let docView = docViewIn;
         const options = optionsIn;
-        while (true) {
+        const maxFocusLength = 100; // want to keep focusing until we get to target, but avoid an infinite loop
+        for (let i = 0; i < maxFocusLength; i++) {
             if (docView.Document.layout_fieldKey === 'layout_icon') {
-                // eslint-disable-next-line no-await-in-loop
-                await new Promise<any>(res => {
+                // eslint-disable-next-line no-loop-func
+                const prom = new Promise<void>(res => {
                     docView.iconify(res);
                 });
+                // eslint-disable-next-line no-await-in-loop
+                await prom;
                 options.didMove = true;
             }
             const nextFocus = docView._props.focus(docView.Document, options); // focus the view within its container
@@ -305,6 +314,7 @@ export class DocumentManager {
             contextView = options.anchorDoc?.layout_unrendered && !childDocView.Document.layout_unrendered ? childDocView : docView;
             docView = childDocView;
         }
+        return undefined;
     };
 
     @action
@@ -347,9 +357,9 @@ export function DocFocusOrOpen(docIn: Doc, optionsIn: FocusViewOptions = { willZ
             const showDoc = !Doc.IsSystem(container) && !cv ? container : doc;
             options.toggleTarget = undefined;
             DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
-                const cv = DocumentManager.Instance.getDocumentView(containingDoc);
-                const dv = DocumentManager.Instance.getDocumentView(doc, cv);
-                dv && Doc.linkFollowHighlight(dv.Document);
+                const cvFound = DocumentManager.Instance.getDocumentView(containingDoc);
+                const dvFound = DocumentManager.Instance.getDocumentView(doc, cvFound);
+                dvFound && Doc.linkFollowHighlight(dvFound.Document);
             });
         }
     };
diff --git a/src/client/util/request-image-size.ts b/src/client/util/request-image-size.ts
index 57e8516ac..0f98a2710 100644
--- a/src/client/util/request-image-size.ts
+++ b/src/client/util/request-image-size.ts
@@ -14,19 +14,17 @@ const imageSize = require('image-size');
 const HttpError = require('standard-http-error');
 
 module.exports = function requestImageSize(options: any) {
-    let opts = {
+    let opts: any = {
         encoding: null,
     };
 
     if (options && typeof options === 'object') {
         opts = Object.assign(options, opts);
     } else if (options && typeof options === 'string') {
-        opts = Object.assign(
-            {
-                uri: options,
-            },
-            opts
-        );
+        opts = {
+            uri: options,
+            ...opts,
+        };
     } else {
         return Promise.reject(new Error('You should provide an URI string or a "request" options object.'));
     }
@@ -38,7 +36,8 @@ module.exports = function requestImageSize(options: any) {
 
         req.on('response', (res: any) => {
             if (res.statusCode >= 400) {
-                return reject(new HttpError(res.statusCode, res.statusMessage));
+                reject(new HttpError(res.statusCode, res.statusMessage));
+                return;
             }
 
             let buffer = Buffer.from([]);
@@ -51,20 +50,23 @@ module.exports = function requestImageSize(options: any) {
                     size = imageSize(buffer);
                     if (size) {
                         resolve(size);
-                        return req.abort();
+                        req.abort();
                     }
-                } catch (err) {}
+                } catch (err) {
+                    /* empty */
+                }
             });
 
             res.on('error', reject);
 
             res.on('end', () => {
                 if (!size) {
-                    return reject(new Error('Image has no size'));
+                    reject(new Error('Image has no size'));
+                    return;
                 }
 
                 size.downloaded = buffer.length;
-                return resolve(size);
+                resolve(size);
             });
         });
 
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index acf0ecff4..ba2e22b3b 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable react/no-array-index-key */
+/* 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, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -12,6 +15,36 @@ import { OverlayView } from './OverlayView';
 import './ScriptingRepl.scss';
 import { DocumentIconContainer } from './nodes/DocumentIcon';
 
+interface replValueProps {
+    scrollToBottom: () => void;
+    value: any;
+    name?: string;
+}
+@observer
+export class ScriptingValueDisplay extends ObservableReactComponent<replValueProps> {
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
+    render() {
+        const val = this._props.name ? this._props.value[this._props.name] : this._props.value;
+        const title = (name: string) => (
+            <>
+                {this._props.name ? <b>{this._props.name} : </b> : <> </>}
+                {name}
+            </>
+        );
+        if (typeof val === 'object') {
+            // eslint-disable-next-line no-use-before-define
+            return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />;
+        }
+        if (typeof val === 'function') {
+            return <div className="scriptingObject-leaf">{title('[Function]')}</div>;
+        }
+        return <div className="scriptingObject-leaf">{title(String(val))}</div>;
+    }
+}
 interface ReplProps {
     scrollToBottom: () => void;
     value: { [key: string]: any };
@@ -37,7 +70,7 @@ export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps>
         const name = (proto && proto.constructor && proto.constructor.name) || String(val);
         const title = (
             <>
-                {this.props.name ? <b>{this._props.name} : </b> : <></>}
+                {this.props.name ? <b>{this._props.name} : </b> : null}
                 {name}
             </>
         );
@@ -50,53 +83,23 @@ export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps>
                     {title} (+{Object.keys(val).length})
                 </div>
             );
-        } else {
-            return (
-                <div className="scriptingObject-open">
-                    <div>
-                        <span onClick={this.toggle} className="scriptingObject-icon">
-                            <FontAwesomeIcon icon="caret-down" size="sm" />
-                        </span>
-                        {title}
-                    </div>
-                    <div className="scriptingObject-fields">
-                        {Object.keys(val).map(key => (
-                            <ScriptingValueDisplay {...this._props} name={key} />
-                        ))}
-                    </div>
-                </div>
-            );
         }
-    }
-}
-
-interface replValueProps {
-    scrollToBottom: () => void;
-    value: any;
-    name?: string;
-}
-@observer
-export class ScriptingValueDisplay extends ObservableReactComponent<replValueProps> {
-    constructor(props: any) {
-        super(props);
-        makeObservable(this);
-    }
-
-    render() {
-        const val = this._props.name ? this._props.value[this._props.name] : this._props.value;
-        const title = (name: string) => (
-            <>
-                {this._props.name ? <b>{this._props.name} : </b> : <> </>}
-                {name}
-            </>
+        return (
+            <div className="scriptingObject-open">
+                <div>
+                    <span onClick={this.toggle} className="scriptingObject-icon">
+                        <FontAwesomeIcon icon="caret-down" size="sm" />
+                    </span>
+                    {title}
+                </div>
+                <div className="scriptingObject-fields">
+                    {Object.keys(val).map(key => (
+                        // eslint-disable-next-line react/jsx-props-no-spreading
+                        <ScriptingValueDisplay {...this._props} name={key} />
+                    ))}
+                </div>
+            </div>
         );
-        if (typeof val === 'object') {
-            return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />;
-        } else if (typeof val === 'function') {
-            const name = '[Function]';
-            return <div className="scriptingObject-leaf">{title('[Function]')}</div>;
-        }
-        return <div className="scriptingObject-leaf">{title(String(val))}</div>;
     }
 }
 
@@ -119,47 +122,45 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
 
     private args: any = {};
 
-    getTransformer = (): Transformer => {
-        return {
-            transformer: context => {
-                const knownVars: { [name: string]: number } = {};
-                const usedDocuments: number[] = [];
-                ScriptingGlobals.getGlobals().forEach((global: any) => (knownVars[global] = 1));
-                return root => {
-                    function visit(node: ts.Node) {
-                        let skip = false;
-                        if (ts.isIdentifier(node)) {
-                            if (ts.isParameter(node.parent)) {
-                                skip = true;
-                                knownVars[node.text] = 1;
-                            }
+    getTransformer = (): Transformer => ({
+        transformer: context => {
+            const knownVars: { [name: string]: number } = {};
+            const usedDocuments: number[] = [];
+            ScriptingGlobals.getGlobals().forEach((global: any) => {
+                knownVars[global] = 1;
+            });
+            return root => {
+                function visit(nodeIn: ts.Node) {
+                    if (ts.isIdentifier(nodeIn)) {
+                        if (ts.isParameter(nodeIn.parent)) {
+                            knownVars[nodeIn.text] = 1;
                         }
-                        node = ts.visitEachChild(node, visit, context);
+                    }
+                    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;
-                            if (ts.isParameter(node.parent)) {
-                                // delete knownVars[node.text];
-                            } else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
-                                const match = node.text.match(/d([0-9]+)/);
-                                if (match) {
-                                    const m = parseInt(match[1]);
-                                    usedDocuments.push(m);
-                                } else {
-                                    return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('args'), node);
-                                    //  ts.createPropertyAccess(ts.createIdentifier('args'), node);
-                                }
+                    if (ts.isIdentifier(node)) {
+                        const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node;
+                        const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node;
+                        if (ts.isParameter(node.parent)) {
+                            // delete knownVars[node.text];
+                        } else if (isntPropAccess && isntPropAssign && !(node.text in knownVars) && !(node.text in globalThis)) {
+                            const match = node.text.match(/d([0-9]+)/);
+                            if (match) {
+                                const m = parseInt(match[1]);
+                                usedDocuments.push(m);
+                            } else {
+                                return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('args'), node);
+                                //  ts.createPropertyAccess(ts.createIdentifier('args'), node);
                             }
                         }
-
-                        return node;
                     }
-                    return ts.visitNode(root, visit);
-                };
-            },
-        };
-    };
+
+                    return node;
+                }
+                return ts.visitNode(root, visit);
+            };
+        },
+    });
 
     @action
     onKeyDown = (e: React.KeyboardEvent) => {
@@ -168,14 +169,16 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
             case 'Enter': {
                 e.stopPropagation();
                 const docGlobals: { [name: string]: any } = {};
-                DocumentManager.Instance.DocumentViews.forEach((dv, i) => (docGlobals[`d${i}`] = dv.Document));
+                DocumentManager.Instance.DocumentViews.forEach((dv, i) => {
+                    docGlobals[`d${i}`] = dv.Document;
+                });
                 const globals = ScriptingGlobals.makeMutableGlobalsCopy(docGlobals);
                 const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: 'any' }, transformer: this.getTransformer(), globals });
                 if (!script.compiled) {
                     this.commands.push({ command: this.commandString, result: script.errors });
                     return;
                 }
-                const result = undoable(() => script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() })), 'run:' + this.commandString)();
+                const result = undoable(() => script.run({ args: this.args }, () => this.commands.push({ command: this.commandString, result: e.toString() })), 'run:' + this.commandString)();
                 if (result.success) {
                     this.commands.push({ command: this.commandString, result: result.result });
                     this.commandsHistory.push(this.commandString);
@@ -260,18 +263,16 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
         return (
             <div className="scriptingRepl-outerContainer">
                 <div className="scriptingRepl-commandsContainer" style={{ background: SettingsManager.userBackgroundColor }} ref={this.commandsRef}>
-                    {this.commands.map(({ command, result }, i) => {
-                        return (
-                            <div className="scriptingRepl-resultContainer" style={{ background: SettingsManager.userBackgroundColor }} key={i}>
-                                <div className="scriptingRepl-commandString" style={{ background: SettingsManager.userBackgroundColor }}>
-                                    {command || <br />}
-                                </div>
-                                <div className="scriptingRepl-commandResult" style={{ background: SettingsManager.userBackgroundColor }}>
-                                    {<ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />}
-                                </div>
+                    {this.commands.map(({ command, result }, i) => (
+                        <div className="scriptingRepl-resultContainer" style={{ background: SettingsManager.userBackgroundColor }} key={i}>
+                            <div className="scriptingRepl-commandString" style={{ background: SettingsManager.userBackgroundColor }}>
+                                {command || <br />}
+                            </div>
+                            <div className="scriptingRepl-commandResult" style={{ background: SettingsManager.userBackgroundColor }}>
+                                <ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />
                             </div>
-                        );
-                    })}
+                        </div>
+                    ))}
                 </div>
                 <input
                     className="scriptingRepl-commandInput"
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 75f1a7d80..3697aa010 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -49,6 +49,7 @@ export enum StyleProp {
     TitleHeight = 'titleHeight', // Height of Title area
     ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix)
     BorderPath = 'customBorder', // border path for document view
+    FontColor = 'fontColor', // color o tet
     FontSize = 'fontSize', // size of text font
     FontFamily = 'fontFamily', // font family of text
     FontWeight = 'fontWeight', // font weight of text
@@ -109,13 +110,35 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
     const layoutDoc = doc ? Doc.Layout(doc) : doc;
     const isOpen = property.includes(':open');
     const boxBackground = property.includes(':box');
-    const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : '';
-    const isInk = () => layoutDoc?._layout_isSvg && !props?.LayoutTemplateString;
+    const {
+        fieldKey: fieldKeyProp,
+        styleProvider,
+        pointerEvents,
+        isGroupActive,
+        isDocumentActive,
+        containerViewPath,
+        childFilters,
+        hideCaptions,
+        // eslint-disable-next-line camelcase
+        layout_showTitle,
+        childFiltersByRanges,
+        renderDepth,
+        docViewPath,
+        DocumentView,
+        LayoutTemplateString,
+        disableBrushing,
+        NativeDimScaling,
+        PanelWidth,
+        PanelHeight,
+    } = props || {}; // extract props that are not shared between fieldView and documentView props.
+    const fieldKey = fieldKeyProp ? fieldKeyProp + '_' : isCaption ? 'caption_' : '';
+    const isInk = () => layoutDoc?._layout_isSvg && !LayoutTemplateString;
     const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
-    const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight);
-    const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1));
-    const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
-    const layoutShowTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
+    const titleHeight = () => styleProvider?.(doc, props, StyleProp.TitleHeight);
+    const backgroundCol = () => styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1));
+    const color = () => styleProvider?.(doc, props, StyleProp.Color);
+    const opacity = () => styleProvider?.(doc, props, StyleProp.Opacity);
+    const layoutShowTitle = () => styleProvider?.(doc, props, StyleProp.ShowTitle);
     // prettier-ignore
     switch (property.split(':')[0]) {
         case StyleProp.TreeViewIcon: {
@@ -137,7 +160,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         }
         case StyleProp.Highlighting:
             if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined;            
-            if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
+            if (doc && !doc.layout_disableBrushing && !disableBrushing) {
                 const selected = Array.from(doc?.[DocViews]??[]).filter(dv => dv.IsSelected).length;
                 const highlightIndex = Doc.GetBrushHighlightStatus(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
@@ -152,26 +175,27 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 }
             }
             return  undefined;
-        case StyleProp.DocContents:return undefined;
-        case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
-        case StyleProp.Opacity:    return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
-        case StyleProp.FontSize:   return StrCast(doc?.[fieldKey + 'fontSize'],   StrCast(Doc.UserDoc().fontSize));
-        case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
-        case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'],  StrCast(Doc.UserDoc().fontWeight));
-        case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
-        case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
-        case StyleProp.TitleHeight:return Math.min(4,(props?.DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
+        case StyleProp.DocContents:  return undefined;
+        case StyleProp.WidgetColor:  return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
+        case StyleProp.Opacity:      return LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
+        case StyleProp.FontColor:    return StrCast(doc?.[fieldKey + 'fontColor'],  StrCast(Doc.UserDoc().fontColor, color()));
+        case StyleProp.FontSize:     return StrCast(doc?.[fieldKey + 'fontSize'],   StrCast(Doc.UserDoc().fontSize));
+        case StyleProp.FontFamily:   return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
+        case StyleProp.FontWeight:   return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight));
+        case StyleProp.FillColor:    return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
+        case StyleProp.ShowCaption:  return hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
+        case StyleProp.TitleHeight:  return Math.min(4,(DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
         case StyleProp.ShowTitle:
             return (
                 (doc &&
-                    !(props?.DocumentView?.().ComponentView instanceof CollectionSchemaView) &&
-                    !props?.LayoutTemplateString &&
+                    !(DocumentView?.().ComponentView instanceof CollectionSchemaView) &&
+                    !LayoutTemplateString &&
                     !doc.presentation_targetDoc &&
-                    !props?.LayoutTemplateString?.includes(KeyValueBox.name) &&
-                    props?.layout_showTitle?.() !== '' &&
+                    !LayoutTemplateString?.includes(KeyValueBox.name) &&
+                    layout_showTitle?.() !== '' &&
                     StrCast(
                         doc._layout_showTitle,
-                        props?.layout_showTitle?.() ||
+                        layout_showTitle?.() ||
                             (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.FUNCPLOT, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any)
                                 ? doc.author === ClientUtils.CurrentUserEmail()
                                     ? StrCast(Doc.UserDoc().layout_showTitle)
@@ -195,15 +219,15 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         }
         case StyleProp.BorderPath: {
             const borderPath = Doc.IsComicStyle(doc) &&
-                props?.renderDepth &&
-                !doc?.layout_isSvg && { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 };
+                renderDepth &&
+                !doc?.layout_isSvg && { path: wavyBorderPath(PanelWidth?.() || 0, PanelHeight?.() || 0), fill: wavyBorderPath(PanelWidth?.() || 0, PanelHeight?.() || 0, 0.08), width: 3 };
                 return !borderPath
                     ? null
                     : {
                         clipPath: `path('${borderPath.path}')`,
                         jsx: (
                             <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
-                                <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${props.PanelWidth()} ${props.PanelHeight()}`}>
+                                <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${PanelWidth?.()} ${PanelHeight?.()}`}>
                                     <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
                                 </svg>
                             </div>
@@ -251,13 +275,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                             ? undefined
                             : doc?._type_collection === CollectionViewType.Stacking ?
                             (Colors.DARK_GRAY)
-                            : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
+                            : Cast((renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
                     break;
                 // if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
                 default: docColor = docColor || (Colors.WHITE);
             }
-            if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && props?.styleProvider) {
-                return props.styleProvider(DocCast(doc.embedContainer), props, StyleProp.BackgroundColor+":nonTransparent"+(isNonTransparentLevel+1));
+            if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && styleProvider) {
+                return styleProvider(DocCast(doc.embedContainer), props, StyleProp.BackgroundColor+":nonTransparent"+(isNonTransparentLevel+1));
             }
             return (docColor && !doc) ? DashColor(docColor).fade(0.5).toString() : docColor;
         }
@@ -271,7 +295,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                         doc?.layout_boxShadow,
                         doc?._type_collection === CollectionViewType.Pile
                             ? '4px 4px 10px 2px'
-                            : lockedPosition() || doc?.isGroup || props?.LayoutTemplateString
+                            : lockedPosition() || doc?.isGroup || LayoutTemplateString
                             ? undefined // groups have no drop shadow -- they're supposed to be "invisible".  LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
                             : `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
                     );
@@ -282,10 +306,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 default:
                     return doc.z
                         ? `#9c9396  ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
-                        : props?.containerViewPath?.().lastElement()?.Document._freeform_useClusters
-                        ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                        : containerViewPath?.().lastElement()?.Document._freeform_useClusters
+                        ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : NumCast(doc.group, -1) !== -1
-                        ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                        ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : lockedPosition()
                         ? undefined // if it's a background & has a cluster color, make the shadow spread really big
                         : fieldKey.includes('_inline')  // if doc is an inline document in a text box
@@ -296,14 +320,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             }
         }
         case StyleProp.PointerEvents:
-            if (StrCast(doc?.pointerEvents) && !props?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
-            if (props?.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
+            if (StrCast(doc?.pointerEvents) && !LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
+            if (LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
             if (SnappingManager.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
-            if (props?.pointerEvents?.() === 'none') return 'none';
+            if (pointerEvents?.() === 'none') return 'none';
             if (opacity() === 0) return 'none';
-            if (props?.isGroupActive?.() ) return isInk()  ? 'visiblePainted':  (doc?.
+            if (isGroupActive?.() ) return isInk()  ? 'visiblePainted':  (doc?.
                 isGroup )? undefined: 'all' 
-            if (props?.isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
+            if (isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations: {
             const lock = () => doc?.pointerEvents !== 'none' ? null : (
@@ -312,7 +336,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                         </div>
                     );
             const paint = () => !doc?.onPaint ? null : (
-                        <div className={`styleProvider-paint${props?.DocumentView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
+                        <div className={`styleProvider-paint${DocumentView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
                             <FontAwesomeIcon icon='pen' size="lg" />
                         </div>
                     );
@@ -321,7 +345,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 => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || props?.childFiltersByRanges().length
+                        : childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length
                         ? 'orange' // 'inheritsFilter'
                         : undefined;
                 return !showFilterIcon ? null : (
@@ -353,7 +377,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                                 "this view inherits filters from one of its parents"}
                             color={SettingsManager.userColor}
                             background={showFilterIcon}
-                            items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[])]
+                            items={[ ...(dashView ? [dashView]: []), ...(docViewPath?.()??[])]
                                     .filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length)
                                     .map(dv => ({
                                 text: StrCast(dv?.Document.title),
@@ -365,9 +389,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 );
             };
             const audio = () => {
-                const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, AudioAnnoState.stopped);
-                const audioAnnosCount = (doc: Doc) => StrListCast(doc[fieldKey + 'audioAnnotations']).length;
-                if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null;
+                const audioAnnoState = (audioDoc: Doc) => StrCast(audioDoc.audioAnnoState, AudioAnnoState.stopped);
+                const audioAnnosCount = (audioDoc: Doc) => StrListCast(audioDoc[fieldKey + 'audioAnnotations']).length;
+                if (!doc || renderDepth === -1 || !audioAnnosCount(doc)) return null;
                 const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: 'blue' };
                 return (
                     <Tooltip title={<div>{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}</div>}>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 5a509128d..6dba9e155 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,3 +1,9 @@
+/* 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 jsx-a11y/control-has-associated-label */
+/* eslint-disable react/no-unused-class-component-methods */
+/* eslint-disable react/sort-comp */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { Toggle, ToggleType, Type } from 'browndash-components';
@@ -312,8 +318,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     }
     private get _buttonizableCommands() {
         switch (this.props.type) {
-            default:
-                return this._doc_commands;
             case CollectionViewType.Freeform:
                 return this._freeform_commands;
             case CollectionViewType.Tree:
@@ -332,6 +336,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
                 return this._freeform_commands;
             case CollectionViewType.Carousel3D:
                 return this._freeform_commands;
+            default:
+                return this._doc_commands;
         }
     }
     private _commandRef = React.createRef<HTMLInputElement>();
@@ -345,11 +351,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     @undoBatch
     viewChanged = (e: React.ChangeEvent) => {
         const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : DocCast(this.document.proto);
-        target._type_collection = e.target.selectedOptions[0].value;
+        target._type_collection = (e.target as any).selectedOptions[0].value;
     };
 
     commandChanged = (e: React.ChangeEvent) => {
-        runInAction(() => (this._currentKey = e.target.selectedOptions[0].value));
+        runInAction(() => {
+            this._currentKey = (e.target as any).selectedOptions[0].value;
+        });
     };
 
     @action closeViewSpecs = () => {
@@ -367,7 +375,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     @undoBatch
     @action
     protected drop(e: Event, de: DragManager.DropEvent): boolean {
-        const docDragData = de.complete.docDragData;
+        const { docDragData } = de.complete;
         if (docDragData?.draggedDocuments.length) {
             this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || []));
             e.stopPropagation();
@@ -420,11 +428,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
             <div className="collectionViewBaseChrome-template" ref={this.createDropTarget}>
                 <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom">
                     <div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
-                        <button className={'antimodeMenu-button'}>
+                        <button type="button" className="antimodeMenu-button">
                             <FontAwesomeIcon icon="bullseye" size="lg" />
                         </button>
                         <select className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}>
-                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={'empty'} value={''} />
+                            <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key="empty" value="" />
                             {this._buttonizableCommands?.map(cmd => (
                                 <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>
                                     {cmd.title}
@@ -471,23 +479,19 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
             if (docs instanceof Doc) {
                 const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
                 return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
-            } else {
-                const keys = new Set<string>();
-                docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
-                const noviceKeys = Array.from(keys).filter(
-                    key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_')
-                );
-                return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
             }
+            const keys = new Set<string>();
+            docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+            const noviceKeys = Array.from(keys).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_'));
+            return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
         }
 
         if (docs instanceof Doc) {
             return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1);
-        } else {
-            const keys = new Set<string>();
-            docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
-            return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
         }
+        const keys = new Set<string>();
+        docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+        return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
     };
 
     @action
@@ -497,9 +501,7 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
 
     getSuggestionValue = (suggestion: string) => suggestion;
 
-    renderSuggestion = (suggestion: string) => {
-        return <p>{suggestion}</p>;
-    };
+    renderSuggestion = (suggestion: string) => <p>{suggestion}</p>;
 
     onSuggestionFetch = async ({ value }: { value: string }) => {
         const sugg = await this.getKeySuggestions(value);
@@ -587,12 +589,16 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
     }
 
     componentDidMount() {
-        runInAction(() => (this.resize = this.props.docView.props.PanelWidth() < 700));
+        runInAction(() => {
+            this.resize = this.props.docView.props.PanelWidth() < 700;
+        });
 
         // listener to reduce text on chrome resize (panel resize)
         this.resizeListenerDisposer = reaction(
             () => this.panelWidth,
-            newValue => (this.resize = newValue < 700)
+            newValue => {
+                this.resize = newValue < 700;
+            }
         );
     }
 
@@ -608,7 +614,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
      * Sets the value of `numCols` on the grid's Document to the value entered.
      */
     onNumColsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        if (e.currentTarget.valueAsNumber > 0) undoBatch(() => (this.document.gridNumCols = e.currentTarget.valueAsNumber))();
+        if (e.currentTarget.valueAsNumber > 0)
+            undoBatch(() => {
+                this.document.gridNumCols = e.currentTarget.valueAsNumber;
+            })();
     };
 
     /**
@@ -637,7 +646,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
     onIncrementButtonClick = () => {
         this.clicked = true;
         this.entered && (this.document.gridNumCols as number)--;
-        undoBatch(() => (this.document.gridNumCols = this.numCols + 1))();
+        undoBatch(() => {
+            this.document.gridNumCols = this.numCols + 1;
+        })();
         this.entered = false;
     };
 
@@ -648,7 +659,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
         this.clicked = true;
         if (this.numCols > 1 && !this.decrementLimitReached) {
             this.entered && (this.document.gridNumCols as number)++;
-            undoBatch(() => (this.document.gridNumCols = this.numCols - 1))();
+            undoBatch(() => {
+                this.document.gridNumCols = this.numCols - 1;
+            })();
             if (this.numCols === 1) this.decrementLimitReached = true;
         }
         this.entered = false;
@@ -741,7 +754,13 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
                     <label className="flexLabel">{this.resize ? 'Flex' : 'Flexible'}</label>
                 </span>
 
-                <button onClick={() => (this.document.gridResetLayout = true)}>{!this.resize ? 'Reset' : <FontAwesomeIcon icon="redo-alt" size="1x" />}</button>
+                <button
+                    type="button"
+                    onClick={() => {
+                        this.document.gridResetLayout = true;
+                    }}>
+                    {!this.resize ? 'Reset' : <FontAwesomeIcon icon="redo-alt" size="1x" />}
+                </button>
             </div>
         );
     }
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 46bf56dc8..ee79812a1 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable no-restricted-syntax */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Popup, PopupTrigger, Type } from 'browndash-components';
 import { ObservableMap, action, computed, makeObservable, observable, observe } from 'mobx';
@@ -31,6 +32,7 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
+
 const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
 
 export const FInfotoColType: { [key: string]: ColumnType } = {
@@ -90,12 +92,11 @@ export class CollectionSchemaView extends CollectionSubView() {
     @computed get _selectedDocs() {
         const selected = SelectionManager.Docs.filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document));
         if (!selected.length) {
-            for (const sel of SelectionManager.Docs) {
-                const contextPath = DocumentManager.GetContextPath(sel, true);
-                if (contextPath.includes(this.Document)) {
-                    const parentInd = contextPath.indexOf(this.Document);
-                    return parentInd < contextPath.length - 1 ? [contextPath[parentInd + 1]] : [];
-                }
+            // if no schema doc is directly selected, test if a child of a schema doc is selected (such as in the preview window)
+            const childOfSchemaDoc = SelectionManager.Docs.find(sel => DocumentManager.GetContextPath(sel, true).includes(this.Document));
+            if (childOfSchemaDoc) {
+                const contextPath = DocumentManager.GetContextPath(childOfSchemaDoc, true);
+                return [contextPath[contextPath.indexOf(childOfSchemaDoc) - 1]]; // the schema doc that is "selected" by virtue of one of its children being selected
             }
         }
         return selected;
@@ -158,7 +159,9 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them
                                     !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo("-no description-", key === 'author'))))));
                         break;
-                    case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
+                    case 'update': // let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
+                        break;
+                    default:
                 }
             },
             true
@@ -187,7 +190,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                             if (this._selectedDocs.includes(newDoc)) {
                                 SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
                             } else {
-                                this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
+                                this.addDocToSelection(newDoc, e.shiftKey);
                                 this._selectedCell && (this._selectedCell[0] = newDoc);
                                 this.scrollToDoc(newDoc, {});
                             }
@@ -206,7 +209,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                             const newDoc = this.sortedDocs.docs[firstIndex - 1];
                             if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
                             else {
-                                this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1);
+                                this.addDocToSelection(newDoc, e.shiftKey);
                                 this._selectedCell && (this._selectedCell[0] = newDoc);
                                 this.scrollToDoc(newDoc, {});
                             }
@@ -235,7 +238,9 @@ export class CollectionSchemaView extends CollectionSubView() {
                 }
                 case 'Escape': {
                     this.deselectCell();
+                    break;
                 }
+                default:
             }
         }
     };
@@ -254,7 +259,7 @@ export class CollectionSchemaView extends CollectionSubView() {
             this.addNewKey(newKey, defaultVal);
         }
 
-        let currKeys = [...this.columnKeys];
+        const currKeys = [...this.columnKeys];
         currKeys[index] = newKey;
         this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
     };
@@ -271,13 +276,16 @@ export class CollectionSchemaView extends CollectionSubView() {
         const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0);
         this.layoutDoc.schema_columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth)));
 
-        let currKeys = this.columnKeys.slice();
+        const currKeys = this.columnKeys.slice();
         currKeys.splice(0, 0, key);
         this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
     };
 
     @action
-    addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[DocData][key] = defaultVal));
+    addNewKey = (key: string, defaultVal: any) =>
+        this.childDocs.forEach(doc => {
+            doc[DocData][key] = defaultVal;
+        });
 
     @undoBatch
     removeColumn = (index: number) => {
@@ -287,7 +295,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         const newDesiredTableWidth = currWidths.reduce((w, cw) => w + cw, 0);
         this.layoutDoc.schema_columnWidths = new List<number>(currWidths.map(w => (w / newDesiredTableWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth)));
 
-        let currKeys = this.columnKeys.slice();
+        const currKeys = this.columnKeys.slice();
         currKeys.splice(index, 1);
         this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
     };
@@ -295,7 +303,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     @action
     startResize = (e: any, index: number) => {
         this._displayColumnWidths = this.storedColumnWidths;
-        setupMoveUpEvents(this, e, (e, delta) => this.resizeColumn(e, index), this.finishResize, emptyFunction);
+        setupMoveUpEvents(this, e, moveEv => this.resizeColumn(moveEv, index), this.finishResize, emptyFunction);
     };
 
     @action
@@ -334,11 +342,11 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     @undoBatch
     moveColumn = (fromIndex: number, toIndex: number) => {
-        let currKeys = this.columnKeys.slice();
+        const currKeys = this.columnKeys.slice();
         currKeys.splice(toIndex, 0, currKeys.splice(fromIndex, 1)[0]);
         this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
 
-        let currWidths = this.storedColumnWidths.slice();
+        const currWidths = this.storedColumnWidths.slice();
         currWidths.splice(toIndex, 0, currWidths.splice(fromIndex, 1)[0]);
         this.layoutDoc.schema_columnWidths = new List<number>(currWidths);
     };
@@ -352,7 +360,7 @@ export class CollectionSchemaView extends CollectionSubView() {
 
         document.removeEventListener('pointermove', this.highlightDropColumn);
         document.addEventListener('pointermove', this.highlightDropColumn);
-        let stopHighlight = (e: PointerEvent) => {
+        const stopHighlight = () => {
             document.removeEventListener('pointermove', this.highlightDropColumn);
             document.removeEventListener('pointerup', stopHighlight);
         };
@@ -405,7 +413,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    addDocToSelection = (doc: Doc, extendSelection: boolean, index: number) => {
+    addDocToSelection = (doc: Doc, extendSelection: boolean) => {
         const rowDocView = DocumentManager.Instance.getDocumentView(doc);
         if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection);
     };
@@ -420,19 +428,25 @@ export class CollectionSchemaView extends CollectionSubView() {
         const endRow = Math.max(lastSelectedRow, index);
         for (let i = startRow; i <= endRow; i++) {
             const currDoc = this.sortedDocs.docs[i];
-            if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true, i);
+            if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true);
         }
     };
 
     @action
-    selectCell = (doc: Doc, index: number) => (this._selectedCell = [doc, index]);
+    selectCell = (doc: Doc, index: number) => {
+        this._selectedCell = [doc, index];
+    };
 
     @action
-    deselectCell = () => (this._selectedCell = undefined);
+    deselectCell = () => {
+        this._selectedCell = undefined;
+    };
 
     sortedSelectedDocs = () => this.sortedDocs.docs.filter(doc => this._selectedDocs.includes(doc));
 
-    setDropIndex = (index: number) => (this._closestDropIndex = index);
+    setDropIndex = (index: number) => {
+        this._closestDropIndex = index;
+    };
 
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.columnDragData) {
@@ -476,7 +490,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     onDividerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction);
 
     @action
-    onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
+    onDividerMove = (e: PointerEvent) => {
         const nativeWidth = this._previewRef!.getBoundingClientRect();
         const minWidth = 40;
         const maxWidth = 1000;
@@ -506,37 +520,76 @@ export class CollectionSchemaView extends CollectionSubView() {
             const rect = found.getBoundingClientRect();
             const localRect = this.ScreenToLocalBoxXf().transformBounds(rect.left, rect.top, rect.width, rect.height);
             if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this._props.PanelHeight()) {
-                let focusSpeed = options.zoomTime ?? 50;
+                const focusSpeed = options.zoomTime ?? 50;
                 smoothScroll(focusSpeed, this._tableContentRef!, localRect.y + this._tableContentRef!.scrollTop - this.rowHeightFunc(), options.easeFunc);
                 return focusSpeed;
             }
         }
+        return undefined;
     };
 
     @computed get fieldDefaultInput() {
         switch (this._newFieldType) {
             case ColumnType.Number:
-                return <input type="number" name="" id="" value={this._newFieldDefault ?? 0} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />;
+                return (
+                    <input
+                        type="number"
+                        name=""
+                        id=""
+                        value={this._newFieldDefault ?? 0}
+                        onPointerDown={e => e.stopPropagation()}
+                        onChange={action(e => {
+                            this._newFieldDefault = e.target.value;
+                        })}
+                    />
+                );
             case ColumnType.Boolean:
                 return (
                     <>
-                        <input type="checkbox" name="" id="" value={this._newFieldDefault} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.checked))} />
+                        <input
+                            type="checkbox"
+                            name=""
+                            id=""
+                            value={this._newFieldDefault}
+                            onPointerDown={e => e.stopPropagation()}
+                            onChange={action(e => {
+                                this._newFieldDefault = e.target.checked;
+                            })}
+                        />
                         {this._newFieldDefault ? 'true' : 'false'}
                     </>
                 );
             case ColumnType.String:
-                return <input type="text" name="" id="" value={this._newFieldDefault ?? ''} onPointerDown={e => e.stopPropagation()} onChange={action(e => (this._newFieldDefault = e.target.value))} />;
+                return (
+                    <input
+                        type="text"
+                        name=""
+                        id=""
+                        value={this._newFieldDefault ?? ''}
+                        onPointerDown={e => e.stopPropagation()}
+                        onChange={action(e => {
+                            this._newFieldDefault = e.target.value;
+                        })}
+                    />
+                );
+            default:
+                return undefined;
         }
     }
 
     onSearchKeyDown = (e: React.KeyboardEvent) => {
         switch (e.key) {
             case 'Enter':
-                this._menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuKeys[0]) : action(() => (this._makeNewField = true))();
+                this._menuKeys.length > 0 && this._menuValue.length > 0
+                    ? this.setKey(this._menuKeys[0])
+                    : action(() => {
+                          this._makeNewField = true;
+                      })();
                 break;
             case 'Escape':
                 this.closeColumnMenu();
                 break;
+            default:
         }
     };
 
@@ -568,7 +621,9 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    closeColumnMenu = () => (this._columnMenuIndex = undefined);
+    closeColumnMenu = () => {
+        this._columnMenuIndex = undefined;
+    };
 
     @action
     openFilterMenu = (index: number) => {
@@ -577,7 +632,9 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    closeFilterMenu = () => (this._filterColumnIndex = undefined);
+    closeFilterMenu = () => {
+        this._filterColumnIndex = undefined;
+    };
 
     openContextMenu = (x: number, y: number, index: number) => {
         this.closeColumnMenu();
@@ -607,7 +664,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         this._menuKeys = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
     };
 
-    getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(Doc.FilterSep)[0] == field);
+    getFieldFilters = (field: string) => StrListCast(this.Document._childFilters).filter(filter => filter.split(Doc.FilterSep)[0] === field);
 
     removeFieldFilters = (field: string) => {
         this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(Doc.FilterSep)[1], 'remove'));
@@ -619,11 +676,14 @@ export class CollectionSchemaView extends CollectionSubView() {
             case 'Escape':
                 this.closeFilterMenu();
                 break;
+            default:
         }
     };
 
     @action
-    updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterSearchValue = e.target.value);
+    updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
+        this._filterSearchValue = e.target.value;
+    };
 
     @computed get newFieldMenu() {
         return (
@@ -632,7 +692,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                     <input
                         type="radio"
                         name="newFieldType"
-                        checked={this._newFieldType == ColumnType.Number}
+                        checked={this._newFieldType === ColumnType.Number}
                         onChange={action(() => {
                             this._newFieldType = ColumnType.Number;
                             this._newFieldDefault = 0;
@@ -644,7 +704,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                     <input
                         type="radio"
                         name="newFieldType"
-                        checked={this._newFieldType == ColumnType.Boolean}
+                        checked={this._newFieldType === ColumnType.Boolean}
                         onChange={action(() => {
                             this._newFieldType = ColumnType.Boolean;
                             this._newFieldDefault = false;
@@ -656,7 +716,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                     <input
                         type="radio"
                         name="newFieldType"
-                        checked={this._newFieldType == ColumnType.String}
+                        checked={this._newFieldType === ColumnType.String}
                         onChange={action(() => {
                             this._newFieldType = ColumnType.String;
                             this._newFieldDefault = '';
@@ -668,7 +728,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                 <div className="schema-key-warning">{this._newFieldWarning}</div>
                 <div
                     className="schema-column-menu-button"
-                    onPointerDown={action(e => {
+                    onPointerDown={action(() => {
                         if (this.documentKeys.includes(this._menuValue)) {
                             this._newFieldWarning = 'Field already exists';
                         } else if (this._menuValue.length === 0) {
@@ -733,7 +793,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     }
 
     @computed get renderColumnMenu() {
-        const x = this._columnMenuIndex! == -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
+        const x = this._columnMenuIndex! === -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
         return (
             <div className="schema-column-menu" style={{ left: x, minWidth: CollectionSchemaView._minColWidth }}>
                 <input className="schema-key-search-input" type="text" onKeyDown={this.onSearchKeyDown} onChange={this.updateKeySearch} onPointerDown={e => e.stopPropagation()} />
@@ -817,7 +877,7 @@ export class CollectionSchemaView extends CollectionSubView() {
             : [...this.childDocs].sort((docA, docB) => {
                   const aStr = Field.toString(docA[field] as FieldType);
                   const bStr = Field.toString(docB[field] as FieldType);
-                  var out = 0;
+                  let out = 0;
                   if (aStr < bStr) out = -1;
                   if (aStr > bStr) out = 1;
                   if (desc) out *= -1;
@@ -835,7 +895,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     render() {
         return (
             <div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} onDrop={this.onExternalDrop.bind(this)}>
-                <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }}></div>
+                <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }} />
                 <div
                     className="schema-table"
                     style={{ width: `calc(100% - ${this.previewWidth}px)` }}
@@ -851,7 +911,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 placement="right"
                                 background={SettingsManager.userBackgroundColor}
                                 color={SettingsManager.userColor}
-                                toggle={<FontAwesomeIcon onPointerDown={e => this.openColumnMenu(-1, true)} icon="plus" />}
+                                toggle={<FontAwesomeIcon onPointerDown={() => this.openColumnMenu(-1, true)} icon="plus" />}
                                 trigger={PopupTrigger.CLICK}
                                 type={Type.TERT}
                                 isOpen={this._columnMenuIndex !== -1 ? false : undefined}
@@ -860,6 +920,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                         </div>
                         {this.columnKeys.map((key, index) => (
                             <SchemaColumnHeader
+                                // eslint-disable-next-line react/no-array-index-key
                                 key={index}
                                 columnIndex={index}
                                 columnKeys={this.columnKeys}
@@ -879,28 +940,42 @@ export class CollectionSchemaView extends CollectionSubView() {
                     </div>
                     {this._columnMenuIndex !== undefined && this._columnMenuIndex !== -1 && this.renderColumnMenu}
                     {this._filterColumnIndex !== undefined && this.renderFilterMenu}
-                    <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} rowHeight={this.rowHeightFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} />
+                    {
+                        // eslint-disable-next-line no-use-before-define
+                        <CollectionSchemaViewDocs
+                            schema={this}
+                            childDocs={this.sortedDocsFunc}
+                            rowHeight={this.rowHeightFunc}
+                            setRef={(ref: HTMLDivElement | null) => {
+                                this._tableContentRef = ref;
+                            }}
+                        />
+                    }
                     {this.layoutDoc.chromeHidden ? null : (
                         <div className="schema-add">
                             <EditableView
                                 GetValue={returnEmptyString}
                                 SetValue={undoable(value => (value ? this.addRow(Docs.Create.TextDocument(value, { title: value, _layout_autoHeight: true })) : false), 'add text doc')}
                                 placeholder={"Type text to create note or ':' to create specific type"}
-                                contents={'+ New Node'}
+                                contents="+ New Node"
                                 menuCallback={this.menuCallback}
                                 height={CollectionSchemaView._newNodeInputHeight}
                             />
                         </div>
                     )}
                 </div>
-                {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>}
+                {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown} />}
                 {this.previewWidth > 0 && (
-                    <div style={{ width: `${this.previewWidth}px` }} ref={ref => (this._previewRef = ref)}>
+                    <div
+                        style={{ width: `${this.previewWidth}px` }}
+                        ref={ref => {
+                            this._previewRef = ref;
+                        }}>
                         {Array.from(this._selectedDocs).lastElement() && (
                             <DocumentView
                                 Document={Array.from(this._selectedDocs).lastElement()}
                                 fitContentsToBox={returnTrue}
-                                dontCenter={'y'}
+                                dontCenter="y"
                                 onClickScriptDisable="always"
                                 focus={emptyFunction}
                                 defaultDoubleClick={returnIgnore}
@@ -945,7 +1020,10 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
             <div className="schema-table-content" ref={this.props.setRef} style={{ height: `calc(100% - ${CollectionSchemaView._newNodeInputHeight + this.props.rowHeight()}px)` }}>
                 {this.props.childDocs().docs.map((doc: Doc, index: number) => (
                     <div key={doc[Id]} className="schema-row-wrapper" style={{ height: this.props.rowHeight() }}>
-                        <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} />
+                        {
+                            // eslint-disable-next-line no-use-before-define
+                            <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} />
+                        }
                     </div>
                 ))}
             </div>
@@ -977,6 +1055,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
         return (
             <DocumentView
                 key={this._props.doc[Id]}
+                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props.schema._props}
                 containerViewPath={this._props.schema.childContainerViewPath}
                 LayoutTemplate={this._props.schema._props.childLayoutTemplate}
@@ -996,14 +1075,14 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
                 searchFilterDocs={this._props.schema.searchFilterDocs}
                 rootSelected={this._props.schema.rootSelected}
                 ScreenToLocalTransform={this.screenToLocalXf}
-                dragWhenActive={true}
+                dragWhenActive
                 isDocumentActive={this._props.schema._props.childDocumentsActive?.() ? this._props.schema._props.isDocumentActive : this._props.schema.isContentActive}
                 isContentActive={emptyFunction}
                 whenChildContentsActiveChanged={this._props.schema._props.whenChildContentsActiveChanged}
-                hideDecorations={true}
-                hideTitle={true}
-                hideDocumentButtonBar={true}
-                hideLinkAnchors={true}
+                hideDecorations
+                hideTitle
+                hideDocumentButtonBar
+                hideLinkAnchors
                 layout_fitWidth={returnTrue}
             />
         );
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 832e18b68..cc4b5b67f 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -21,7 +21,6 @@ import { CollectionSchemaView } from '../collections/collectionSchema/Collection
 import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
 import { PresElementBox } from './trails/PresElementBox';
 import { SearchBox } from '../search/SearchBox';
-import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo';
 import { AudioBox } from './AudioBox';
 import { ComparisonBox } from './ComparisonBox';
 import { DataVizBox } from './DataVizBox/DataVizBox';
@@ -248,7 +247,6 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
                     PresElementBox,
                     SearchBox,
                     FunctionPlotBox,
-                    DashWebRTCVideo,
                     LinkAnchorBox,
                     InkingStroke,
                     LinkBox,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 3a1a72910..99b4a84fc 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -349,7 +349,7 @@ footnote::before {
     touch-action: none;
     span {
         font-family: inherit;
-        background-color: inherit;
+        //  background-color: inherit;  // intended to allow texts to inherit background from list container, but this prevents css highlights e.,g highlight text from others
         display: inline; // needs to be inline for search highlighting to appear
         // display: contents; // BUT needs to be 'contents' to avoid Chrome bug where extra space is added above and <ol> lists when inside a prosemirror span
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 99a2f4ab9..ba37c3265 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -65,6 +65,7 @@ import { FootnoteView } from './FootnoteView';
 import './FormattedTextBox.scss';
 import { findLinkMark, FormattedTextBoxComment } from './FormattedTextBoxComment';
 import { buildKeymap, updateBullets } from './ProsemirrorExampleTransfer';
+// eslint-disable-next-line import/extensions
 import { removeMarkWithAttrs } from './prosemirrorPatches';
 import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
 import { RichTextRules } from './RichTextRules';
@@ -291,7 +292,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             });
         };
         AnchorMenu.Instance.Highlight = undoable((color: string) => {
-            this._editorView?.state && RichTextMenu.Instance?.setHighlight(color);
+            this._editorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight');
             return undefined;
         }, 'highlght text');
         AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
@@ -637,8 +638,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                             const pos = view.posAtCoords({ left: de.x, top: de.y })?.pos;
                             pos && view.dispatch(view.state.tr.insert(pos, node));
                             added = !!pos; // pos will be null if you don't drop onto an actual text location
-                        } catch (e) {
-                            console.log('Drop failed', e);
+                        } catch (err) {
+                            console.log('Drop failed', err);
                             added = false;
                         } finally {
                             this._inDrop = false;
@@ -778,7 +779,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             this,
             e,
             this.sidebarMove,
-            (e, movement, isClick) => !isClick && batch.end(),
+            (moveEv, movement, isClick) => !isClick && batch.end(),
             () => {
                 this.toggleSidebar();
                 batch.end();
@@ -1301,7 +1302,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         this._disposers.selected = reaction(
             () => this._props.rootSelected?.(),
             action(selected => {
-                // selected && setTimeout(() => this.prepareForTyping());
+                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
@@ -1498,8 +1499,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
                 this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
                 this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
-            } else if (curText && !FormattedTextBox.DontSelectInitialText) {
-                selectAll(this._editorView.state, this._editorView?.dispatch);
+            } else if (!FormattedTextBox.DontSelectInitialText) {
+                const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) });
+                selectAll(this._editorView.state, (tx: Transaction) => {
+                    this._editorView?.dispatch(tx.deleteSelection().addStoredMark(mark));
+                });
                 this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data
             } else {
                 const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
@@ -1526,17 +1530,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
     prepareForTyping = () => {
-        if (!this._editorView) return;
-        const docDefaultMarks = [
-            ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
-            ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
-            ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
-            ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { fontFamily: 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: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })],
-        ];
-        this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks));
+        if (this._editorView) {
+            const { text, paragraph } = schema.nodes;
+            const selNode = this._editorView.state.selection.$anchor.node();
+            if (this._editorView.state.selection.from === 1 && this._editorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
+                const docDefaultMarks = [schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })];
+                this._editorView.state.selection.empty && this._editorView.state.selection.from === 1 && this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor));
+            }
+        }
     };
 
     componentWillUnmount() {
@@ -1601,10 +1602,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const state = this.EditorView?.state;
         if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
             if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
-            let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
-            for (let target = e.target as any; target && !target.dataset?.targethrefs; target = target.parentElement);
-            while (target && !target.dataset?.targethrefs) target = target.parentElement;
-            FormattedTextBoxComment.update(this, this.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
+            let clickTarget = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+            for (let { target } = e as any; target && !target.dataset?.targethrefs; target = target.parentElement);
+            while (clickTarget && !clickTarget.dataset?.targethrefs) clickTarget = clickTarget.parentElement;
+            FormattedTextBoxComment.update(this, this.EditorView!, undefined, clickTarget?.dataset?.targethrefs, clickTarget?.dataset.linkdoc, clickTarget?.dataset.nopreview === 'true');
         }
     };
     @action
@@ -1724,9 +1725,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             if (!(this.EditorView?.state.selection instanceof NodeSelection)) {
                 this.autoLink();
                 if (this._editorView?.state.tr) {
-                    const tr = stordMarks?.reduce((tr, m) => {
-                        tr.addStoredMark(m);
-                        return tr;
+                    const tr = stordMarks?.reduce((tr2, m) => {
+                        tr2.addStoredMark(m);
+                        return tr2;
                     }, this._editorView.state.tr);
                     tr && this._editorView.dispatch(tr);
                 }
@@ -2038,7 +2039,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     };
     _oldWheel: any;
     @computed get fontColor() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontColor);
     }
     @computed get fontSize() {
         return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 073ed91c3..5d448d40e 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -7,7 +7,7 @@ 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 { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
 import { GetEffectiveAcl } from '../../../../fields/util';
 import { Docs } from '../../../documents/Documents';
 import { RTFMarkup } from '../../../util/RTFMarkup';
@@ -48,11 +48,12 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
     }
 
     const canEdit = (state: any) => {
-        switch (GetEffectiveAcl(props.TemplateDataDocument)) {
+        const permissions = GetEffectiveAcl(props.TemplateDataDocument ?? props.Document[DocData]);
+        switch (permissions) {
             case AclAugment:
                 {
                     const prevNode = state.selection.$cursor.nodeBefore;
-                    const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks[prevNode.marks.length - 1].attrs.userid;
+                    const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks.lastElement()?.attrs.userid;
                     if (prevUser !== ClientUtils.CurrentUserEmail()) {
                         return false;
                     }
@@ -278,7 +279,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
                     dispatch(updateBullets(tx, schema));
                     if (view.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) {
                         // gets rid of an extra paragraph when joining two list items together.
-                        joinBackward(view.state, (tx: Transaction) => view.dispatch(tx));
+                        joinBackward(view.state, (tx2: Transaction) => view.dispatch(tx2));
                     }
                 })
             ) {
@@ -344,7 +345,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
                         !splitBlockKeepMarks(state, (tx3: Transaction) => {
                             const tonode = tx3.selection.$to.node();
                             if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
-                                const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks);
+                                const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks).setStoredMarks(marks || []);
                                 dispatch(tx4);
                             }
 
@@ -365,7 +366,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
 
     // Command to create a blank space
     bind('Space', () => {
-        if (props.TemplateDataDocument && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(props.TemplateDataDocument))) return true;
+        const editDoc = props.TemplateDataDocument ?? props.Document[DocData];
+        if (editDoc && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(editDoc))) return true;
         return false;
     });
 
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 6108383c2..6c12b9991 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -10,7 +10,6 @@ import { EditorView } from 'prosemirror-view';
 import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
-import { numberRange } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { LinkManager } from '../../../util/LinkManager';
 import { SelectionManager } from '../../../util/SelectionManager';
@@ -147,12 +146,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         const { activeHighlights } = active;
         const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc();
         const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey);
+        const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt));
 
         this._activeListType = this.getActiveListStyle();
         this._activeAlignment = this.getActiveAlignment();
-        this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(refDoc[refField + 'fontFamily'], 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
-        this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(refDoc[refField + 'fontSize'], '10px')) : activeSizes[0];
-        this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(refDoc[refField + 'fontColor'], 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
+        this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, refVal('fontFamily', 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+        this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, refVal('fontSize', '10px')) : activeSizes[0];
+        this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('fontColor', 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
         this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
 
         // update link in current selection
@@ -161,12 +161,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => {
         if (mark) {
-            const liFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.list_item);
-            const liTo = numberRange(state.selection.$to.depth + 1).find(i => state.selection.$to.node(i)?.type === state.schema.nodes.list_item);
-            const olFirst = numberRange(state.selection.$from.depth + 1).find(i => state.selection.$from.node(i)?.type === state.schema.nodes.ordered_list);
-            const nodeOl = (liFirst && liTo && state.selection.$from.node(liFirst) !== state.selection.$to.node(liTo) && olFirst) || (!liFirst && !liTo && olFirst);
-            const fromRange = numberRange(state.selection.from).reverse();
-            const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from;
+            const newPos = state.selection.$anchor.node()?.type === schema.nodes.ordered_list ? state.selection.from : state.selection.from;
             const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
             if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) {
                 const hasMark = node.marks.some(m => m.type === mark.type);
@@ -174,18 +169,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
                 const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey]));
                 const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]);
                 dispatch(updateBullets(markup, state.schema));
-            } else {
-                const state = this.view?.state;
-                if (state) {
-                    const { tr } = state;
-                    if (dontToggle) {
-                        tr.addMark(state.selection.from, state.selection.to, mark);
-                        dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
-                    } else {
-                        toggleMark(mark.type, mark.attrs)(state, dispatch);
-                    }
+            } else if (state) {
+                const { tr } = state;
+                if (dontToggle) {
+                    tr.addMark(state.selection.from, state.selection.to, mark);
+                    dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise
+                } else {
+                    toggleMark(mark.type, mark.attrs)(state, dispatch);
                 }
             }
+            this.updateMenu(this.view, undefined, undefined, this.layoutDoc);
         }
     };
 
@@ -242,7 +235,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
                 m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.fontFamily);
                 m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.fontColor);
                 m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize);
-                m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHigh));
+                m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHighlight));
             });
         } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) {
             SelectionManager.Views.forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
@@ -359,18 +352,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => {
         if (this.view) {
-            if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === value))) {
+            const { text, paragraph } = this.view.state.schema.nodes;
+            const selNode = this.view.state.selection.$anchor.node();
+            if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) {
                 this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value;
                 this.view.focus();
-            } else {
-                const attrs: { [key: string]: string } = {};
-                attrs[fontField] = value;
-                const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
-                this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
-                this.view.focus();
             }
-        } else Doc.UserDoc()[fontField] = value;
-        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+            const attrs: { [key: string]: string } = {};
+            attrs[fontField] = value;
+            const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs);
+            this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
+            this.view.focus();
+        } else {
+            Doc.UserDoc()[fontField] = value;
+            this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+        }
     };
 
     // TODO: remove doesn't work
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 184487b7d..5bf942218 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -3,6 +3,7 @@ import { listItem, orderedList } from 'prosemirror-schema-list';
 import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
 import { DocServer } from '../../../DocServer';
 import { Doc, Field, FieldType } from '../../../../fields/Doc';
+import { schema } from './schema_rts';
 
 const blockquoteDOM: DOMOutputSpec = ['blockquote', 0];
 const hrDOM: DOMOutputSpec = ['hr'];
@@ -353,7 +354,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             },
             {
                 style: 'list-style-type=disc',
-                getAttrs(dom: any) {
+                getAttrs() {
                     return { mapStyle: 'bullet' };
                 },
             },
@@ -373,10 +374,10 @@ export const nodes: { [index: string]: NodeSpec } = {
         ],
         toDOM(node: Node) {
             const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
-            const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
-            const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
-            const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
-            const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
+            const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontHighlight)?.attrs.fontHighlight);
+            const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontSize)?.attrs.fontSize);
+            const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontFamily)?.attrs.fontFamily);
+            const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontColor)?.attrs.fontColor);
             const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : '';
             if (node.attrs.mapStyle === 'bullet') {
                 return [
@@ -421,10 +422,10 @@ export const nodes: { [index: string]: NodeSpec } = {
             },
         ],
         toDOM(node: Node) {
-            const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
-            const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
-            const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
-            const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
+            const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontHighlight)?.attrs.fontHighlight);
+            const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontSize)?.attrs.fontSize);
+            const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontFamily)?.attrs.fontFamily);
+            const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type === schema.marks.pFontColor)?.attrs.fontColor);
             const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
             return [
                 'li',
diff --git a/src/client/views/webcam/DashWebRTCVideo.scss b/src/client/views/webcam/DashWebRTCVideo.scss
deleted file mode 100644
index 5744ebbcd..000000000
--- a/src/client/views/webcam/DashWebRTCVideo.scss
+++ /dev/null
@@ -1,82 +0,0 @@
-@import '../global/globalCssVariables.module.scss';
-
-.webcam-cont {
-    background: whitesmoke;
-    color: grey;
-    border-radius: 15px;
-    box-shadow: #9c9396 0.2vw 0.2vw 0.4vw;
-    border: solid #bbbbbbbb 5px;
-    pointer-events: all;
-    display: flex;
-    flex-direction: column;
-    overflow: hidden;
-
-    .webcam-header {
-        height: 50px;
-        text-align: center;
-        text-transform: uppercase;
-        letter-spacing: 2px;
-        font-size: 16px;
-        width: 100%;
-        margin-top: 20px;
-    }
-
-    .videoContainer {
-        position: relative;
-        width: calc(100% - 20px);
-        height: 100%;
-        /* border: 10px solid red; */
-        margin-left: 10px;
-    }
-
-    .buttonContainer {
-        display: flex;
-        width: calc(100% - 20px);
-        height: 50px;
-        justify-content: center;
-        text-align: center;
-        /* border: 1px solid black; */
-        margin-left: 10px;
-        margin-top: 0;
-        margin-bottom: 15px;
-    }
-
-    #roomName {
-        outline: none;
-        border-radius: inherit;
-        border: 1px solid #bbbbbbbb;
-        margin: 10px;
-        padding: 10px;
-    }
-
-    .side {
-        width: 25%;
-        height: 20%;
-        position: absolute;
-        /* top: 65%; */
-        z-index: 2;
-        right: 0px;
-        bottom: 18px;
-    }
-
-    .main {
-        position: absolute;
-        width: 100%;
-        height: 100%;
-        /* top: 20%; */
-        align-self: center;
-    }
-
-    .videoButtons {
-        border-radius: 50%;
-        height: 30px;
-        width: 30px;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        justify-self: center;
-        align-self: center;
-        margin: 5px;
-        border: 1px solid black;
-    }
-}
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
deleted file mode 100644
index 4e984f3d6..000000000
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { IconLookup } from '@fortawesome/fontawesome-svg-core';
-import { faPhoneSlash, faSync } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc } from '../../../fields/Doc';
-import { InkTool } from '../../../fields/InkField';
-import { SnappingManager } from '../../util/SnappingManager';
-import '../../views/nodes/WebBox.scss';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
-import './DashWebRTCVideo.scss';
-import { hangup, initialize, refreshVideos } from './WebCamLogic';
-
-/**
- * This models the component that will be rendered, that can be used as a doc that will reflect the video cams.
- */
-@observer
-export class DashWebRTCVideo extends React.Component<FieldViewProps> {
-    private roomText: HTMLInputElement | undefined;
-    @observable remoteVideoAdded: boolean = false;
-
-    @action
-    changeUILook = () => (this.remoteVideoAdded = true);
-
-    /**
-     * Function that submits the title entered by user on enter press.
-     */
-    private onEnterKeyDown = (e: React.KeyboardEvent) => {
-        if (e.keyCode === 13) {
-            const submittedTitle = this.roomText!.value;
-            this.roomText!.value = '';
-            this.roomText!.blur();
-            initialize(submittedTitle, this.changeUILook);
-        }
-    };
-
-    public static LayoutString(fieldKey: string) {
-        return FieldView.LayoutString(DashWebRTCVideo, fieldKey);
-    }
-
-    onClickRefresh = () => refreshVideos();
-
-    onClickHangUp = () => hangup();
-
-    render() {
-        const content = (
-            <div className="webcam-cont" style={{ width: '100%', height: '100%' }}>
-                <div className="webcam-header">DashWebRTC</div>
-                <input id="roomName" type="text" placeholder="Enter room name" ref={e => (this.roomText = e!)} onKeyDown={this.onEnterKeyDown} />
-                <div className="videoContainer">
-                    <video id="localVideo" className={'RTCVideo' + (this.remoteVideoAdded ? ' side' : ' main')} autoPlay playsInline muted ref={e => {}}></video>
-                    <video id="remoteVideo" className="RTCVideo main" autoPlay playsInline ref={e => {}}></video>
-                </div>
-                <div className="buttonContainer">
-                    <div className="videoButtons" style={{ background: 'red' }} onClick={this.onClickHangUp}>
-                        <FontAwesomeIcon icon={faPhoneSlash as IconLookup} color="white" />
-                    </div>
-                    <div className="videoButtons" style={{ background: 'green' }} onClick={this.onClickRefresh}>
-                        <FontAwesomeIcon icon={faSync as IconLookup} color="white" />
-                    </div>
-                </div>
-            </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" />}
-            </>
-        );
-    }
-}
diff --git a/src/client/views/webcam/WebCamLogic.js b/src/client/views/webcam/WebCamLogic.js
deleted file mode 100644
index 5f6202bc8..000000000
--- a/src/client/views/webcam/WebCamLogic.js
+++ /dev/null
@@ -1,292 +0,0 @@
-'use strict';
-import io from "socket.io-client";
-
-var socket;
-var isChannelReady = false;
-var isInitiator = false;
-var isStarted = false;
-var localStream;
-var pc;
-var remoteStream;
-var turnReady;
-var room;
-
-export function initialize(roomName, handlerUI) {
-
-    var pcConfig = {
-        'iceServers': [{
-            'urls': 'stun:stun.l.google.com:19302'
-        }]
-    };
-
-    // Set up audio and video regardless of what devices are present.
-    var sdpConstraints = {
-        offerToReceiveAudio: true,
-        offerToReceiveVideo: true
-    };
-
-    /////////////////////////////////////////////
-
-    room = roomName;
-
-    socket = io.connect(`${window.location.protocol}//${window.location.hostname}:4321`);
-
-    if (room !== '') {
-        socket.emit('create or join', room);
-        console.log('Attempted to create or  join room', room);
-    }
-
-    socket.on('created', function (room) {
-        console.log('Created room ' + room);
-        isInitiator = true;
-    });
-
-    socket.on('full', function (room) {
-        console.log('Room ' + room + ' is full');
-    });
-
-    socket.on('join', function (room) {
-        console.log('Another peer made a request to join room ' + room);
-        console.log('This peer is the initiator of room ' + room + '!');
-        isChannelReady = true;
-    });
-
-    socket.on('joined', function (room) {
-        console.log('joined: ' + room);
-        isChannelReady = true;
-    });
-
-    socket.on('log', function (array) {
-        console.log.apply(console, array);
-    });
-
-    ////////////////////////////////////////////////
-
-
-    // This client receives a message
-    socket.on('message', function (message) {
-        console.log('Client received message:', message);
-        if (message === 'got user media') {
-            maybeStart();
-        } else if (message.type === 'offer') {
-            if (!isInitiator && !isStarted) {
-                maybeStart();
-            }
-            pc.setRemoteDescription(new RTCSessionDescription(message));
-            doAnswer();
-        } else if (message.type === 'answer' && isStarted) {
-            pc.setRemoteDescription(new RTCSessionDescription(message));
-        } else if (message.type === 'candidate' && isStarted) {
-            var candidate = new RTCIceCandidate({
-                sdpMLineIndex: message.label,
-                candidate: message.candidate
-            });
-            pc.addIceCandidate(candidate);
-        } else if (message === 'bye' && isStarted) {
-            handleRemoteHangup();
-        }
-    });
-
-    ////////////////////////////////////////////////////
-
-    var localVideo = document.querySelector('#localVideo');
-    var remoteVideo = document.querySelector('#remoteVideo');
-
-    const gotStream = (stream) => {
-        console.log('Adding local stream.');
-        localStream = stream;
-        localVideo.srcObject = stream;
-        sendMessage('got user media');
-        if (isInitiator) {
-            maybeStart();
-        }
-    }
-
-
-    navigator.mediaDevices.getUserMedia({
-            audio: true,
-            video: true
-        })
-        .then(gotStream)
-        .catch(function (e) {
-            alert('getUserMedia() error: ' + e.name);
-        });
-
-
-
-    var constraints = {
-        video: true
-    };
-
-    console.log('Getting user media with constraints', constraints);
-
-    const requestTurn = (turnURL) => {
-        var turnExists = false;
-        for (var i in pcConfig.iceServers) {
-            if (pcConfig.iceServers[i].urls.substr(0, 5) === 'turn:') {
-                turnExists = true;
-                turnReady = true;
-                break;
-            }
-        }
-        if (!turnExists) {
-            console.log('Getting TURN server from ', turnURL);
-            // No TURN server. Get one from computeengineondemand.appspot.com:
-            var xhr = new XMLHttpRequest();
-            xhr.onreadystatechange = function () {
-                if (xhr.readyState === 4 && xhr.status === 200) {
-                    var turnServer = JSON.parse(xhr.responseText);
-                    console.log('Got TURN server: ', turnServer);
-                    pcConfig.iceServers.push({
-                        'urls': 'turn:' + turnServer.username + '@' + turnServer.turn,
-                        'credential': turnServer.password
-                    });
-                    turnReady = true;
-                }
-            };
-            xhr.open('GET', turnURL, true);
-            xhr.send();
-        }
-    }
-
-
-
-
-    if (location.hostname !== 'localhost') {
-        requestTurn(
-            `${window.location.origin}/corsProxy/${encodeURIComponent("https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913")}`
-        );
-    }
-
-    const maybeStart = () => {
-        console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady);
-        if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) {
-            console.log('>>>>>> creating peer connection');
-            createPeerConnection();
-            pc.addStream(localStream);
-            isStarted = true;
-            console.log('isInitiator', isInitiator);
-            if (isInitiator) {
-                doCall();
-            }
-        }
-    };
-
-    window.onbeforeunload = function () {
-        sendMessage('bye');
-    };
-
-    /////////////////////////////////////////////////////////
-
-    const createPeerConnection = () => {
-        try {
-            pc = new RTCPeerConnection(null);
-            pc.onicecandidate = handleIceCandidate;
-            pc.onaddstream = handleRemoteStreamAdded;
-            pc.onremovestream = handleRemoteStreamRemoved;
-            console.log('Created RTCPeerConnnection');
-        } catch (e) {
-            console.log('Failed to create PeerConnection, exception: ' + e.message);
-            alert('Cannot create RTCPeerConnection object.');
-            return;
-        }
-    }
-
-    const handleIceCandidate = (event) => {
-        console.log('icecandidate event: ', event);
-        if (event.candidate) {
-            sendMessage({
-                type: 'candidate',
-                label: event.candidate.sdpMLineIndex,
-                id: event.candidate.sdpMid,
-                candidate: event.candidate.candidate
-            });
-        } else {
-            console.log('End of candidates.');
-        }
-    }
-
-    const handleCreateOfferError = (event) => {
-        console.log('createOffer() error: ', event);
-    }
-
-    const doCall = () => {
-        console.log('Sending offer to peer');
-        pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
-    }
-
-    const doAnswer = () => {
-        console.log('Sending answer to peer.');
-        pc.createAnswer().then(
-            setLocalAndSendMessage,
-            onCreateSessionDescriptionError
-        );
-    }
-
-    const setLocalAndSendMessage = (sessionDescription) => {
-        pc.setLocalDescription(sessionDescription);
-        console.log('setLocalAndSendMessage sending message', sessionDescription);
-        sendMessage(sessionDescription);
-    }
-
-    const onCreateSessionDescriptionError = (error) => {
-        trace('Failed to create session description: ' + error.toString());
-    }
-
-
-
-    const handleRemoteStreamAdded = (event) => {
-        console.log('Remote stream added.');
-        remoteStream = event.stream;
-        remoteVideo.srcObject = remoteStream;
-        handlerUI();
-
-    };
-
-    const handleRemoteStreamRemoved = (event) => {
-        console.log('Remote stream removed. Event: ', event);
-    }
-}
-
-export function hangup() {
-    console.log('Hanging up.');
-    stop();
-    sendMessage('bye');
-    if (localStream) {
-        localStream.getTracks().forEach(track => track.stop());
-    }
-}
-
-function stop() {
-    isStarted = false;
-    if (pc) {
-        pc.close();
-    }
-    pc = null;
-}
-
-function handleRemoteHangup() {
-    console.log('Session terminated.');
-    stop();
-    isInitiator = false;
-    if (localStream) {
-        localStream.getTracks().forEach(track => track.stop());
-    }
-}
-
-function sendMessage(message) {
-    console.log('Client sending message: ', message);
-    socket.emit('message', message, room);
-};
-
-export function refreshVideos() {
-    var localVideo = document.querySelector('#localVideo');
-    var remoteVideo = document.querySelector('#remoteVideo');
-    if (localVideo) {
-        localVideo.srcObject = localStream;
-    }
-    if (remoteVideo) {
-        remoteVideo.srcObject = remoteStream;
-    }
-
-}
\ No newline at end of file
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 4512d5c5b..5028e1f8f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -116,7 +116,9 @@ export type FieldResult<T extends FieldType = FieldType> = Opt<T> | FieldWaiting
  * If no default value is given, and the returned value is not undefined, it can be safely modified.
  */
 export function DocListCastAsync(field: FieldResult): Promise<Doc[] | undefined>;
+// eslint-disable-next-line no-redeclare
 export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>;
+// eslint-disable-next-line no-redeclare
 export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
     const list = Cast(field, listSpec(Doc));
     return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
@@ -416,6 +418,7 @@ export class Doc extends RefField {
     }
 }
 
+// eslint-disable-next-line no-redeclare
 export namespace Doc {
     export function SetContainer(doc: Doc, container: Doc) {
         if (container !== Doc.MyRecentlyClosed) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index d7268f31a..72b0ef721 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -285,10 +285,10 @@ export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean)
  * 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 '@'
  * @param prop
- * @param setter
+ * @param propSetter
  */
-export function SetPropSetterCb(prop: string, setter: ((target: any, value: any) => void) | undefined) {
-    _propSetterCB.set(prop, setter);
+export function SetPropSetterCb(prop: string, propSetter: ((target: any, value: any) => void) | undefined) {
+    _propSetterCB.set(prop, propSetter);
 }
 
 //
diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts
index 6f5b9272a..520ebb42e 100644
--- a/src/server/ActionUtilities.ts
+++ b/src/server/ActionUtilities.ts
@@ -16,29 +16,30 @@ export function pathFromRoot(relative?: string) {
     return path.resolve(projectRoot, relative);
 }
 
-export async function fileDescriptorFromStream(path: string) {
-    const logStream = createWriteStream(path);
-    return new Promise<number>(resolve => logStream.on('open', resolve));
+export async function fileDescriptorFromStream(filePath: string) {
+    const logStream = createWriteStream(filePath);
+    return new Promise<number>(resolve => {
+        logStream.on('open', resolve);
+    });
 }
 
-export const command_line = (command: string, fromDirectory?: string) => {
-    return new Promise<string>((resolve, reject) => {
+export const commandLine = (command: string, fromDirectory?: string) =>
+    new Promise<string>((resolve, reject) => {
         const options: ExecOptions = {};
         if (fromDirectory) {
             options.cwd = fromDirectory ? path.resolve(projectRoot, fromDirectory) : projectRoot;
         }
         exec(command, options, (err, stdout) => (err ? reject(err) : resolve(stdout)));
     });
-};
 
-export const read_text_file = (relativePath: string) => {
+export const readTextFile = (relativePath: string) => {
     const target = path.resolve(__dirname, relativePath);
     return new Promise<string>((resolve, reject) => {
         readFile(target, (err, data) => (err ? reject(err) : resolve(data.toString())));
     });
 };
 
-export const write_text_file = (relativePath: string, contents: any) => {
+export const writeTextFile = (relativePath: string, contents: any) => {
     const target = path.resolve(__dirname, relativePath);
     return new Promise<void>((resolve, reject) => {
         writeFile(target, contents, err => (err ? reject(err) : resolve()));
@@ -55,39 +56,38 @@ export interface LogData<T> {
     color?: Color;
 }
 
+function logHelper(content: string, color: Color | string) {
+    if (typeof color === 'string') {
+        console.log(color, content);
+    } else {
+        console.log(color(content));
+    }
+}
+
 let current = Math.ceil(Math.random() * 20);
 export async function logExecution<T>({ startMessage, endMessage, action, color }: LogData<T>): Promise<T | undefined> {
-    let result: T | undefined = undefined,
-        error: Error | null = null;
+    let result: T | undefined;
+    let error: Error | null = null;
     const resolvedColor = color || `\x1b[${31 + (++current % 6)}m%s\x1b[0m`;
-    log_helper(`${startMessage}...`, resolvedColor);
+    logHelper(`${startMessage}...`, resolvedColor);
     try {
         result = await action();
     } catch (e: any) {
         error = e;
     } finally {
-        log_helper(typeof endMessage === 'string' ? endMessage : endMessage({ result, error }), resolvedColor);
+        logHelper(typeof endMessage === 'string' ? endMessage : endMessage({ result, error }), resolvedColor);
     }
     return result;
 }
-
-function log_helper(content: string, color: Color | string) {
-    if (typeof color === 'string') {
-        console.log(color, content);
-    } else {
-        console.log(color(content));
-    }
-}
-
 export function logPort(listener: string, port: number) {
     console.log(`${listener} listening on port ${yellow(String(port))}`);
 }
 
 export function msToTime(duration: number) {
-    const milliseconds = Math.floor((duration % 1000) / 100),
-        seconds = Math.floor((duration / 1000) % 60),
-        minutes = Math.floor((duration / (1000 * 60)) % 60),
-        hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
+    const milliseconds = Math.floor((duration % 1000) / 100);
+    const seconds = Math.floor((duration / 1000) % 60);
+    const minutes = Math.floor((duration / (1000 * 60)) % 60);
+    const hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
 
     const hoursS = hours < 10 ? '0' + hours : hours;
     const minutesS = minutes < 10 ? '0' + minutes : minutes;
@@ -96,21 +96,32 @@ export function msToTime(duration: number) {
     return hoursS + ':' + minutesS + ':' + secondsS + '.' + milliseconds;
 }
 
-export const createIfNotExists = async (path: string) => {
-    if (await new Promise<boolean>(resolve => exists(path, resolve))) {
+export const createIfNotExists = async (filePath: string) => {
+    if (
+        await new Promise<boolean>(resolve => {
+            exists(filePath, resolve);
+        })
+    ) {
         return true;
     }
-    return new Promise<boolean>(resolve => mkdir(path, error => resolve(error === null)));
+    return new Promise<boolean>(resolve => {
+        mkdir(filePath, error => resolve(error === null));
+    });
 };
 
 export async function Prune(rootDirectory: string): Promise<boolean> {
     // const error =  await new Promise<Error>(resolve => rimraf(rootDirectory).then(resolve));
-    await new Promise<void>(resolve => rimraf(rootDirectory).then(() => resolve()));
+    await new Promise<void>(resolve => {
+        rimraf(rootDirectory).then(() => resolve());
+    });
     // return error === null;
     return true;
 }
 
-export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => unlink(mediaPath, error => resolve(error === null)));
+export const Destroy = (mediaPath: string) =>
+    new Promise<boolean>(resolve => {
+        unlink(mediaPath, error => resolve(error === null));
+    });
 
 export namespace Email {
     const smtpTransport = nodemailer.createTransport({
@@ -137,9 +148,9 @@ export namespace Email {
         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) {
+                const error = await Email.dispatch({ to: recipient, subject, content, attachments: resolved });
+                if (error !== null) {
                     failures.push({
                         recipient,
                         error,
@@ -158,6 +169,8 @@ export namespace Email {
             text: `Hello ${to.split('@')[0]},\n\n${content}`,
             attachments,
         } as MailOptions;
-        return new Promise<Error | null>(resolve => smtpTransport.sendMail(mailOptions, resolve));
+        return new Promise<Error | null>(resolve => {
+            smtpTransport.sendMail(mailOptions, resolve);
+        });
     }
 }
diff --git a/src/server/ApiManagers/ApiManager.ts b/src/server/ApiManagers/ApiManager.ts
index 27e9de065..f55495b2e 100644
--- a/src/server/ApiManagers/ApiManager.ts
+++ b/src/server/ApiManagers/ApiManager.ts
@@ -1,4 +1,4 @@
-import { RouteInitializer } from "../RouteManager";
+import { RouteInitializer } from '../RouteManager';
 
 export type Registration = (initializer: RouteInitializer) => void;
 
@@ -8,4 +8,4 @@ export default abstract class ApiManager {
     public register(register: Registration) {
         this.initialize(register);
     }
-}
\ No newline at end of file
+}
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index 9a9b807ae..9ad334c1b 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -1,12 +1,12 @@
-import ApiManager, { Registration } from './ApiManager';
-import { Method, _permissionDenied } from '../RouteManager';
-import { WebSocket } from '../websocket';
-import { Database } from '../database';
+import { mkdirSync } from 'fs';
 import { rimraf } from 'rimraf';
-import { filesDirectory } from '..';
+import { filesDirectory } from '../SocketData';
 import { DashUploadUtils } from '../DashUploadUtils';
-import { mkdirSync } from 'fs';
+import { Method } from '../RouteManager';
 import RouteSubscriber from '../RouteSubscriber';
+import { Database } from '../database';
+import { WebSocket } from '../websocket';
+import ApiManager, { Registration } from './ApiManager';
 
 export default class DeleteManager extends ApiManager {
     protected initialize(register: Registration): void {
@@ -24,9 +24,11 @@ export default class DeleteManager extends ApiManager {
                     switch (target) {
                         case 'all':
                             all = true;
+                        // eslint-disable-next-line no-fallthrough
                         case 'database':
                             await WebSocket.doDelete(false);
                             if (!all) break;
+                        // eslint-disable-next-line no-fallthrough
                         case 'files':
                             rimraf.sync(filesDirectory);
                             mkdirSync(filesDirectory);
diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts
index b105c825c..5ee21fb44 100644
--- a/src/server/ApiManagers/DownloadManager.ts
+++ b/src/server/ApiManagers/DownloadManager.ts
@@ -153,7 +153,7 @@ async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hiera
     }
 }
 
-async function getDocs(id: string) {
+async function getDocs(docId: string) {
     const files = new Set<string>();
     const docs: { [id: string]: any } = {};
     const fn = (doc: any): string[] => {
@@ -209,8 +209,8 @@ async function getDocs(id: string) {
         }
         return ids;
     };
-    await Database.Instance.visit([id], fn);
-    return { id, docs, files };
+    await Database.Instance.visit([docId], fn);
+    return { id: docId, docs, files };
 }
 
 export default class DownloadManager extends ApiManager {
diff --git a/src/server/ApiManagers/MongoStore.js b/src/server/ApiManagers/MongoStore.js
index 28515fee4..5d91c2805 100644
--- a/src/server/ApiManagers/MongoStore.js
+++ b/src/server/ApiManagers/MongoStore.js
@@ -1,10 +1,9 @@
-'use strict';
-var __createBinding =
+const __createBinding =
     (this && this.__createBinding) ||
     (Object.create
         ? function (o, m, k, k2) {
               if (k2 === undefined) k2 = k;
-              var desc = Object.getOwnPropertyDescriptor(m, k);
+              let desc = Object.getOwnPropertyDescriptor(m, k);
               if (!desc || ('get' in desc ? !m.__esModule : desc.writable || desc.configurable)) {
                   desc = {
                       enumerable: true,
@@ -19,25 +18,25 @@ var __createBinding =
               if (k2 === undefined) k2 = k;
               o[k2] = m[k];
           });
-var __setModuleDefault =
+const __setModuleDefault =
     (this && this.__setModuleDefault) ||
     (Object.create
         ? function (o, v) {
               Object.defineProperty(o, 'default', { enumerable: true, value: v });
           }
         : function (o, v) {
-              o['default'] = v;
+              o.default = v;
           });
-var __importStar =
+const __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);
+        const result = {};
+        if (mod != null) for (const k in mod) if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
         __setModuleDefault(result, mod);
         return result;
     };
-var __importDefault =
+const __importDefault =
     (this && this.__importDefault) ||
     function (mod) {
         return mod && mod.__esModule ? mod : { default: mod };
@@ -246,7 +245,7 @@ class MongoStore extends session.Store {
      */
     set(sid, session, callback = noop) {
         (async () => {
-            var _a;
+            let _a;
             try {
                 debug(`MongoStore#set=${sid}`);
                 // Removing the lastModified prop from the session object before update
@@ -306,7 +305,7 @@ class MongoStore extends session.Store {
     }
     touch(sid, session, callback = noop) {
         (async () => {
-            var _a;
+            let _a;
             try {
                 debug(`MongoStore#touch=${sid}`);
                 const updateFields = {};
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 1b1db5809..f43ed6ac9 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-use-before-define */
 import { exec } from 'child_process';
 import { cyan, green, red, yellow } from 'colors';
 import { logExecution } from '../ActionUtilities';
@@ -17,8 +18,10 @@ export class SearchManager extends ApiManager {
                 switch (action) {
                     case 'start':
                     case 'stop':
-                        const status = req.params.action === 'start';
-                        SolrManager.SetRunning(status);
+                        {
+                            const status = req.params.action === 'start';
+                            SolrManager.SetRunning(status);
+                        }
                         break;
                     case 'update':
                         await SolrManager.update();
@@ -95,7 +98,7 @@ export namespace SolrManager {
             if (doc.__type !== 'Doc') {
                 return;
             }
-            const fields = doc.fields;
+            const { fields } = doc;
             if (!fields) {
                 return;
             }
diff --git a/src/server/ApiManagers/SessionManager.ts b/src/server/ApiManagers/SessionManager.ts
index c3139896f..bebe50a62 100644
--- a/src/server/ApiManagers/SessionManager.ts
+++ b/src/server/ApiManagers/SessionManager.ts
@@ -9,20 +9,18 @@ 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 authorizedAction = (handler: SecureHandler) => {
-        return (core: AuthorizedCore) => {
-            const {
-                req: { params },
-                res,
-            } = core;
-            if (!process.env.MONITORED) {
-                return res.send('This command only makes sense in the context of a monitored session.');
-            }
-            if (params.session_key !== process.env.session_key) {
-                return _permissionDenied(res, permissionError);
-            }
-            return handler(core);
-        };
+    private authorizedAction = (handler: SecureHandler) => (core: AuthorizedCore) => {
+        const {
+            req: { params },
+            res,
+        } = core;
+        if (!process.env.MONITORED) {
+            return res.send('This command only makes sense in the context of a monitored session.');
+        }
+        if (params.session_key !== process.env.session_key) {
+            return _permissionDenied(res, permissionError);
+        }
+        return handler(core);
     };
 
     protected initialize(register: Registration): void {
diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts
index e657866ce..8ad421a30 100644
--- a/src/server/ApiManagers/UtilManager.ts
+++ b/src/server/ApiManagers/UtilManager.ts
@@ -1,6 +1,7 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
 import { exec } from 'child_process';
+import ApiManager, { Registration } from './ApiManager';
+import { Method } from '../RouteManager';
+
 // import { IBM_Recommender } from "../../client/apis/IBM_Recommender";
 // import { Recommender } from "../Recommender";
 
@@ -8,9 +9,7 @@ import { exec } from 'child_process';
 // recommender.testModel();
 
 export default class UtilManager extends ApiManager {
-
     protected initialize(register: Registration): void {
-
         // register({
         //     method: Method.POST,
         //     subscription: "/IBMAnalysis",
@@ -33,26 +32,25 @@ export default class UtilManager extends ApiManager {
 
         register({
             method: Method.GET,
-            subscription: "/pull",
-            secureHandler: async ({ res }) => {
-                return new Promise<void>(resolve => {
+            subscription: '/pull',
+            secureHandler: async ({ res }) =>
+                new Promise<void>(resolve => {
                     exec('"C:\\Program Files\\Git\\git-bash.exe" -c "git pull"', err => {
                         if (err) {
                             res.send(err.message);
                             return;
                         }
-                        res.redirect("/");
+                        res.redirect('/');
                         resolve();
                     });
-                });
-            }
+                }),
         });
 
         register({
             method: Method.GET,
-            subscription: "/version",
-            secureHandler: ({ res }) => {
-                return new Promise<void>(resolve => {
+            subscription: '/version',
+            secureHandler: ({ res }) =>
+                new Promise<void>(resolve => {
                     exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout) => {
                         if (err) {
                             res.send(err.message);
@@ -61,10 +59,7 @@ export default class UtilManager extends ApiManager {
                         res.send(stdout);
                     });
                     resolve();
-                });
-            }
+                }),
         });
-
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/server/DashSession/Session/agents/applied_session_agent.ts b/src/server/DashSession/Session/agents/applied_session_agent.ts
index 2037e93e5..c42ba95cc 100644
--- a/src/server/DashSession/Session/agents/applied_session_agent.ts
+++ b/src/server/DashSession/Session/agents/applied_session_agent.ts
@@ -1,13 +1,13 @@
-import * as _cluster from "cluster";
-import { Monitor } from "./monitor";
-import { ServerWorker } from "./server_worker";
+import * as _cluster from 'cluster';
+import { Monitor } from './monitor';
+import { ServerWorker } from './server_worker';
+
 const cluster = _cluster as any;
 const isMaster = cluster.isPrimary;
 
 export type ExitHandler = (reason: Error | boolean) => void | Promise<void>;
 
 export abstract class AppliedSessionAgent {
-
     // the following two methods allow the developer to create a custom
     // session and use the built in customization options for each thread
     protected abstract initializeMonitor(monitor: Monitor): Promise<string>;
@@ -18,15 +18,15 @@ export abstract class AppliedSessionAgent {
     public killSession = (reason: string, graceful = true, errorCode = 0) => {
         const target = cluster.default.isPrimary ? this.sessionMonitor : this.serverWorker;
         target.killSession(reason, graceful, errorCode);
-    }
+    };
 
     private sessionMonitorRef: Monitor | undefined;
     public get sessionMonitor(): Monitor {
         if (!cluster.default.isPrimary) {
-            this.serverWorker.emit("kill", {
+            this.serverWorker.emit('kill', {
                 graceful: false,
-                reason: "Cannot access the session monitor directly from the server worker thread.",
-                errorCode: 1
+                reason: 'Cannot access the session monitor directly from the server worker thread.',
+                errorCode: 1,
             });
             throw new Error();
         }
@@ -36,7 +36,7 @@ export abstract class AppliedSessionAgent {
     private serverWorkerRef: ServerWorker | undefined;
     public get serverWorker(): ServerWorker {
         if (isMaster) {
-            throw new Error("Cannot access the server worker directly from the session monitor thread");
+            throw new Error('Cannot access the server worker directly from the session monitor thread');
         }
         return this.serverWorkerRef!;
     }
@@ -52,8 +52,7 @@ export abstract class AppliedSessionAgent {
                 this.serverWorkerRef = await this.initializeServerWorker();
             }
         } else {
-            throw new Error("Cannot launch a session thread more than once per process.");
+            throw new Error('Cannot launch a session thread more than once per process.');
         }
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/server/DashSession/Session/agents/monitor.ts b/src/server/DashSession/Session/agents/monitor.ts
index a6fde4356..6cdad46c2 100644
--- a/src/server/DashSession/Session/agents/monitor.ts
+++ b/src/server/DashSession/Session/agents/monitor.ts
@@ -1,21 +1,19 @@
-import { ExitHandler } from './applied_session_agent';
-import { Configuration, configurationSchema, defaultConfig, Identifiers, colorMapping } from '../utilities/session_config';
-import Repl, { ReplAction } from '../utilities/repl';
+import { ExecOptions, exec } from 'child_process';
 import * as _cluster from 'cluster';
 import { Worker } from 'cluster';
-import { manage, MessageHandler, ErrorLike } from './promisified_ipc_manager';
-import { red, cyan, white, yellow, blue } from 'colors';
-import { exec, ExecOptions } from 'child_process';
-import { validate, ValidationError } from 'jsonschema';
-import { Utilities } from '../utilities/utilities';
+import { blue, cyan, red, white, yellow } from 'colors';
 import { readFileSync } from 'fs';
+import { ValidationError, validate } from 'jsonschema';
+import Repl, { ReplAction } from '../utilities/repl';
+import { Configuration, Identifiers, colorMapping, configurationSchema, defaultConfig } from '../utilities/session_config';
+import { Utilities } from '../utilities/utilities';
+import { ExitHandler } from './applied_session_agent';
 import IPCMessageReceiver from './process_message_router';
+import { ErrorLike, MessageHandler, manage } from './promisified_ipc_manager';
 import { ServerWorker } from './server_worker';
+
 const cluster = _cluster as any;
-const isWorker = cluster.isWorker;
-const setupMaster = cluster.setupPrimary;
-const on = cluster.on;
-const fork = cluster.fork;
+const { isWorker, setupMaster, on, fork } = cluster;
 
 /**
  * Validates and reads the configuration file, accordingly builds a child process factory
@@ -41,9 +39,8 @@ export class Monitor extends IPCMessageReceiver {
         } else if (++Monitor.count > 1) {
             console.error(red('cannot create more than one monitor.'));
             process.exit(1);
-        } else {
-            return new Monitor();
         }
+        return new Monitor();
     }
 
     private constructor() {
@@ -128,25 +125,25 @@ export class Monitor extends IPCMessageReceiver {
         this.repl.registerCommand(basename, argPatterns, action);
     };
 
-    public exec = (command: string, options?: ExecOptions) => {
-        return new Promise<void>(resolve => {
+    public exec = (command: string, options?: ExecOptions) =>
+        new Promise<void>(resolve => {
             exec(command, { ...options, encoding: 'utf8' }, (error, stdout, stderr) => {
                 if (error) {
                     this.execLog(red(`unable to execute ${white(command)}`));
                     error.message.split('\n').forEach(line => line.length && this.execLog(red(`(error) ${line}`)));
                 } else {
-                    let outLines: string[], errorLines: string[];
-                    if ((outLines = stdout.split('\n').filter(line => line.length)).length) {
+                    const outLines = stdout.split('\n').filter(line => line.length);
+                    if (outLines.length) {
                         outLines.forEach(line => line.length && this.execLog(cyan(`(stdout) ${line}`)));
                     }
-                    if ((errorLines = stderr.split('\n').filter(line => line.length)).length) {
+                    const errorLines = stderr.split('\n').filter(line => line.length);
+                    if (errorLines.length) {
                         errorLines.forEach(line => line.length && this.execLog(yellow(`(stderr) ${line}`)));
                     }
                 }
                 resolve();
             });
         });
-    };
 
     /**
      * Generates a blue UTC string associated with the time
@@ -226,12 +223,10 @@ export class Monitor extends IPCMessageReceiver {
             const newPollingIntervalSeconds = Math.floor(Number(args[1]));
             if (newPollingIntervalSeconds < 0) {
                 this.mainLog(red('the polling interval must be a non-negative integer'));
-            } else {
-                if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) {
-                    this.config.polling.intervalSeconds = newPollingIntervalSeconds;
-                    if (args[2] === 'true') {
-                        Monitor.IPCManager.emit('updatePollingInterval', { newPollingIntervalSeconds });
-                    }
+            } else if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) {
+                this.config.polling.intervalSeconds = newPollingIntervalSeconds;
+                if (args[2] === 'true') {
+                    Monitor.IPCManager.emit('updatePollingInterval', { newPollingIntervalSeconds });
                 }
             }
         });
@@ -297,6 +292,7 @@ export class Monitor extends IPCMessageReceiver {
     };
 }
 
+// eslint-disable-next-line no-redeclare
 export namespace Monitor {
     export enum IntrinsicEvents {
         KeyGenerated = 'key_generated',
diff --git a/src/server/DashSession/Session/agents/process_message_router.ts b/src/server/DashSession/Session/agents/process_message_router.ts
index 0745ea455..3e2b7d8d0 100644
--- a/src/server/DashSession/Session/agents/process_message_router.ts
+++ b/src/server/DashSession/Session/agents/process_message_router.ts
@@ -1,7 +1,6 @@
-import { MessageHandler, PromisifiedIPCManager, HandlerMap } from "./promisified_ipc_manager";
+import { MessageHandler, PromisifiedIPCManager, HandlerMap } from './promisified_ipc_manager';
 
 export default abstract class IPCMessageReceiver {
-
     protected static IPCManager: PromisifiedIPCManager;
     protected handlers: HandlerMap = {};
 
@@ -18,7 +17,7 @@ export default abstract class IPCMessageReceiver {
         } else {
             handlers.push(handler);
         }
-    }
+    };
 
     /**
      * Unregister a given listener at this message.
@@ -31,11 +30,10 @@ export default abstract class IPCMessageReceiver {
                 handlers.splice(index, 1);
             }
         }
-    }
+    };
 
-    /** 
+    /**
      * Unregister all listeners at this message.
      */
     public clearMessageListeners = (...names: string[]) => names.map(name => delete this.handlers[name]);
-
-}
\ No newline at end of file
+}
diff --git a/src/server/DashSession/Session/agents/promisified_ipc_manager.ts b/src/server/DashSession/Session/agents/promisified_ipc_manager.ts
index 76e218977..99b4d4de3 100644
--- a/src/server/DashSession/Session/agents/promisified_ipc_manager.ts
+++ b/src/server/DashSession/Session/agents/promisified_ipc_manager.ts
@@ -1,13 +1,14 @@
-import { Utilities } from '../utilities/utilities';
 import { ChildProcess } from 'child_process';
+import { Utilities } from '../utilities/utilities';
 
 /**
- * Convenience constructor
- * @param target the process / worker to which to attach the specialized listeners
+ * Specifies a general message format for this API
  */
-export function manage(target: IPCTarget, handlers?: HandlerMap) {
-    return new PromisifiedIPCManager(target, handlers);
-}
+export type Message<T = any> = {
+    name: string;
+    args?: T;
+};
+export type MessageHandler<T = any> = (args: T) => any | Promise<any>;
 
 /**
  * Captures the logic to execute upon receiving a message
@@ -22,15 +23,10 @@ export type HandlerMap = { [name: string]: MessageHandler[] };
  */
 export type IPCTarget = NodeJS.Process | ChildProcess;
 
-/**
- * Specifies a general message format for this API
- */
-export type Message<T = any> = {
-    name: string;
-    args?: T;
-};
-export type MessageHandler<T = any> = (args: T) => any | Promise<any>;
-
+interface Metadata {
+    isResponse: boolean;
+    id: string;
+}
 /**
  * When a message is emitted, it is embedded with private metadata
  * to facilitate the resolution of promises, etc.
@@ -38,10 +34,6 @@ export type MessageHandler<T = any> = (args: T) => any | Promise<any>;
 interface InternalMessage extends Message {
     metadata: Metadata;
 }
-interface Metadata {
-    isResponse: boolean;
-    id: string;
-}
 
 /**
  * Allows for the transmission of the error's key features over IPC.
@@ -95,7 +87,7 @@ export class PromisifiedIPCManager {
         }
         return new Promise<Response<T>>(resolve => {
             const messageId = Utilities.guid();
-            type InternalMessageHandler = (message: any /* MessageListener*/) => any | Promise<any>;
+            type InternalMessageHandler = (message: any /* MessageListener */) => any | Promise<any>;
             const responseHandler: InternalMessageHandler = ({ metadata: { id, isResponse }, args }) => {
                 if (isResponse && id === messageId) {
                     this.target.removeListener('message', responseHandler);
@@ -118,8 +110,8 @@ export class PromisifiedIPCManager {
      * completion response for each of the pending messages, allowing their
      * promises in the caller to resolve.
      */
-    public destroy = () => {
-        return new Promise<void>(async resolve => {
+    public destroy = () =>
+        new Promise<void>(async resolve => {
             if (this.callerIsTarget) {
                 this.destroyHelper();
             } else {
@@ -127,7 +119,6 @@ export class PromisifiedIPCManager {
             }
             resolve();
         });
-    };
 
     /**
      * Dispatches the dummy responses and sets the isDestroyed flag to true.
@@ -168,12 +159,20 @@ export class PromisifiedIPCManager {
                     error = e;
                 }
                 if (!this.isDestroyed && this.target.send) {
-                    const metadata = { id, isResponse: true };
+                    const metadataRes = { id, isResponse: true };
                     const response: Response = { results, error };
-                    const message = { name, args: response, metadata };
+                    const messageRes = { name, args: response, metadata: metadataRes };
                     delete this.pendingMessages[id];
-                    this.target.send(message);
+                    this.target.send(messageRes);
                 }
             }
         };
 }
+
+/**
+ * Convenience constructor
+ * @param target the process / worker to which to attach the specialized listeners
+ */
+export function manage(target: IPCTarget, handlers?: HandlerMap) {
+    return new PromisifiedIPCManager(target, handlers);
+}
diff --git a/src/server/DashSession/Session/agents/server_worker.ts b/src/server/DashSession/Session/agents/server_worker.ts
index d8b3ee80b..85e1b31d6 100644
--- a/src/server/DashSession/Session/agents/server_worker.ts
+++ b/src/server/DashSession/Session/agents/server_worker.ts
@@ -1,10 +1,10 @@
-import cluster from "cluster";
-import { green, red, white, yellow } from "colors";
-import { get } from "request-promise";
-import { ExitHandler } from "./applied_session_agent";
-import { Monitor } from "./monitor";
-import IPCMessageReceiver from "./process_message_router";
-import { ErrorLike, manage } from "./promisified_ipc_manager";
+import cluster from 'cluster';
+import { green, red, white, yellow } from 'colors';
+import { get } from 'request-promise';
+import { ExitHandler } from './applied_session_agent';
+import { Monitor } from './monitor';
+import IPCMessageReceiver from './process_message_router';
+import { ErrorLike, manage } from './promisified_ipc_manager';
 
 /**
  * Effectively, each worker repairs the connection to the server by reintroducing a consistent state
@@ -23,18 +23,17 @@ export class ServerWorker extends IPCMessageReceiver {
     private isInitialized = false;
     public static Create(work: Function) {
         if (cluster.isPrimary) {
-            console.error(red("cannot create a worker on the monitor process."));
+            console.error(red('cannot create a worker on the monitor process.'));
             process.exit(1);
         } else if (++ServerWorker.count > 1) {
-            ServerWorker.IPCManager.emit("kill", {
-                reason: "cannot create more than one worker on a given worker process.",
+            ServerWorker.IPCManager.emit('kill', {
+                reason: 'cannot create more than one worker on a given worker process.',
                 graceful: false,
-                errorCode: 1
+                errorCode: 1,
             });
             process.exit(1);
-        } else {
-            return new ServerWorker(work);
         }
+        return new ServerWorker(work);
     }
 
     /**
@@ -48,7 +47,7 @@ export class ServerWorker extends IPCMessageReceiver {
      * server worker (child process). This will also kill
      * this process (child process).
      */
-    public killSession = (reason: string, graceful = true, errorCode = 0) => this.emit<never>("kill", { reason, graceful, errorCode });
+    public killSession = (reason: string, graceful = true, errorCode = 0) => this.emit<never>('kill', { reason, graceful, errorCode });
 
     /**
      * A convenience wrapper to tell the session monitor (parent process)
@@ -60,7 +59,7 @@ export class ServerWorker extends IPCMessageReceiver {
         super();
         this.configureInternalHandlers();
         ServerWorker.IPCManager = manage(process, this.handlers);
-        this.lifecycleNotification(green(`initializing process... ${white(`[${process.execPath} ${process.execArgv.join(" ")}]`)}`));
+        this.lifecycleNotification(green(`initializing process... ${white(`[${process.execPath} ${process.execArgv.join(' ')}]`)}`));
 
         const { pollingRoute, serverPort, pollingIntervalSeconds, pollingFailureTolerance } = process.env;
         this.serverPort = Number(serverPort);
@@ -78,8 +77,10 @@ export class ServerWorker extends IPCMessageReceiver {
      */
     protected configureInternalHandlers = () => {
         // updates the local values of variables to the those sent from master
-        this.on("updatePollingInterval", ({ newPollingIntervalSeconds }) => this.pollingIntervalSeconds = newPollingIntervalSeconds);
-        this.on("manualExit", async ({ isSessionEnd }) => {
+        this.on('updatePollingInterval', ({ newPollingIntervalSeconds }) => {
+            this.pollingIntervalSeconds = newPollingIntervalSeconds;
+        });
+        this.on('manualExit', async ({ isSessionEnd }) => {
             await ServerWorker.IPCManager.destroy();
             await this.executeExitHandlers(isSessionEnd);
             process.exit(0);
@@ -91,7 +92,7 @@ export class ServerWorker extends IPCMessageReceiver {
             const appropriateError = reason instanceof Error ? reason : new Error(`unhandled rejection: ${reason}`);
             this.proactiveUnplannedExit(appropriateError);
         });
-    }
+    };
 
     /**
      * Execute the list of functions registered to be called
@@ -102,7 +103,7 @@ export class ServerWorker extends IPCMessageReceiver {
     /**
      * Notify master thread (which will log update in the console) of initialization via IPC.
      */
-    public lifecycleNotification = (event: string) => this.emit("lifecycle", { event });
+    public lifecycleNotification = (event: string) => this.emit('lifecycle', { event });
 
     /**
      * Called whenever the process has a reason to terminate, either through an uncaught exception
@@ -120,11 +121,11 @@ export class ServerWorker extends IPCMessageReceiver {
         this.lifecycleNotification(red(error.message));
         await ServerWorker.IPCManager.destroy();
         process.exit(1);
-    }
+    };
 
     /**
      * This monitors the health of the server by submitting a get request to whatever port / route specified
-     * by the configuration every n seconds, where n is also given by the configuration. 
+     * by the configuration every n seconds, where n is also given by the configuration.
      */
     private pollServer = async (): Promise<void> => {
         await new Promise<void>(resolve => {
@@ -156,6 +157,5 @@ export class ServerWorker extends IPCMessageReceiver {
         });
         // controlled, asynchronous infinite recursion achieves a persistent poll that does not submit a new request until the previous has completed
         this.pollServer();
-    }
-
+    };
 }
diff --git a/src/server/DashSession/Session/utilities/repl.ts b/src/server/DashSession/Session/utilities/repl.ts
index 643141286..5d9f15e4c 100644
--- a/src/server/DashSession/Session/utilities/repl.ts
+++ b/src/server/DashSession/Session/utilities/repl.ts
@@ -1,5 +1,5 @@
-import { createInterface, Interface } from "readline";
-import { red, green, white } from "colors";
+import { createInterface, Interface } from 'readline';
+import { red, green, white } from 'colors';
 
 export interface Configuration {
     identifier: () => string | string;
@@ -32,76 +32,82 @@ export default class Repl {
         this.interface = createInterface(process.stdin, process.stdout).on('line', this.considerInput);
     }
 
-    private resolvedIdentifier = () => typeof this.identifier === "string" ? this.identifier : this.identifier();
+    private resolvedIdentifier = () => (typeof this.identifier === 'string' ? this.identifier : this.identifier());
 
     private usage = (command: string, validCommand: boolean) => {
         if (validCommand) {
             const formatted = white(command);
-            const patterns = green(this.commandMap.get(command)!.map(({ argPatterns }) => `${formatted}  ${argPatterns.join("  ")}`).join('\n'));
+            const patterns = green(
+                this.commandMap
+                    .get(command)!
+                    .map(({ argPatterns }) => `${formatted}  ${argPatterns.join('  ')}`)
+                    .join('\n')
+            );
             return `${this.resolvedIdentifier()}\nthe given arguments do not match any registered patterns for ${formatted}\nthe list of valid argument patterns is given by:\n${patterns}`;
-        } else {
-            const resolved = this.keys;
-            if (resolved) {
-                return resolved;
-            }
-            const members: string[] = [];
-            const keys = this.commandMap.keys();
-            let next: IteratorResult<string>;
-            while (!(next = keys.next()).done) {
-                members.push(next.value);
-            }
-            return `${this.resolvedIdentifier()} commands: { ${members.sort().join(", ")} }`;
         }
-    }
+        const resolved = this.keys;
+        if (resolved) {
+            return resolved;
+        }
+        const members: string[] = [];
+        const keys = this.commandMap.keys();
+        let next: IteratorResult<string>;
+        // eslint-disable-next-line no-cond-assign
+        while (!(next = keys.next()).done) {
+            members.push(next.value);
+        }
+        return `${this.resolvedIdentifier()} commands: { ${members.sort().join(', ')} }`;
+    };
 
     private success = (command: string) => `${this.resolvedIdentifier()} completed local execution of ${white(command)}`;
 
     public registerCommand = (basename: string, argPatterns: (RegExp | string)[], action: ReplAction) => {
         const existing = this.commandMap.get(basename);
-        const converted = argPatterns.map(input => input instanceof RegExp ? input : new RegExp(input));
+        const converted = argPatterns.map(input => (input instanceof RegExp ? input : new RegExp(input)));
         const registration = { argPatterns: converted, action };
         if (existing) {
             existing.push(registration);
         } else {
             this.commandMap.set(basename, [registration]);
         }
-    }
+    };
 
     private invalid = (command: string, validCommand: boolean) => {
-        console.log(red(typeof this.onInvalid === "string" ? this.onInvalid : this.onInvalid(command, validCommand)));
+        console.log(red(typeof this.onInvalid === 'string' ? this.onInvalid : this.onInvalid(command, validCommand)));
         this.busy = false;
-    }
+    };
 
     private valid = (command: string) => {
-        console.log(green(typeof this.onValid === "string" ? this.onValid : this.onValid(command)));
+        console.log(green(typeof this.onValid === 'string' ? this.onValid : this.onValid(command)));
         this.busy = false;
-    }
+    };
 
-    private considerInput = async (line: string) => {
+    private considerInput = async (lineIn: string) => {
         if (this.busy) {
-            console.log(red("Busy"));
+            console.log(red('Busy'));
             return;
         }
         this.busy = true;
-        line = line.trim();
+        let line = lineIn.trim();
         if (this.isCaseSensitive) {
             line = line.toLowerCase();
         }
         const [command, ...args] = line.split(/\s+/g);
         if (!command) {
-            return this.invalid(command, false);
+            this.invalid(command, false);
+            return;
         }
         const registered = this.commandMap.get(command);
         if (registered) {
             const { length } = args;
             const candidates = registered.filter(({ argPatterns: { length: count } }) => count === length);
-            for (const { argPatterns, action } of candidates) {
+            candidates.forEach(({ argPatterns, action }: { argPatterns: any; action: any }) => {
                 const parsed: string[] = [];
                 let matched = true;
                 if (length) {
                     for (let i = 0; i < length; i++) {
-                        let matches: RegExpExecArray | null;
-                        if ((matches = argPatterns[i].exec(args[i])) === null) {
+                        const matches = argPatterns[i].exec(args[i]);
+                        if (matches === null) {
                             matched = false;
                             break;
                         }
@@ -110,19 +116,17 @@ export default class Repl {
                 }
                 if (!length || matched) {
                     const result = action(parsed);
-                    const resolve = () => this.valid(`${command} ${parsed.join(" ")}`);
+                    const resolve = () => this.valid(`${command} ${parsed.join(' ')}`);
                     if (result instanceof Promise) {
                         result.then(resolve);
                     } else {
                         resolve();
                     }
-                    return;
                 }
-            }
+            });
             this.invalid(command, true);
         } else {
             this.invalid(command, false);
         }
-    }
-
-}
\ No newline at end of file
+    };
+}
diff --git a/src/server/DashSession/Session/utilities/session_config.ts b/src/server/DashSession/Session/utilities/session_config.ts
index 266759929..b42c1a3c7 100644
--- a/src/server/DashSession/Session/utilities/session_config.ts
+++ b/src/server/DashSession/Session/utilities/session_config.ts
@@ -1,85 +1,85 @@
-import { Schema } from "jsonschema";
-import { yellow, red, cyan, green, blue, magenta, Color, grey, gray, white, black } from "colors";
+import { Schema } from 'jsonschema';
+import { yellow, red, cyan, green, blue, magenta, Color, grey, gray, white, black } from 'colors';
 
 const colorPattern = /black|red|green|yellow|blue|magenta|cyan|white|gray|grey/;
 
 const identifierProperties: Schema = {
-    type: "object",
+    type: 'object',
     properties: {
         text: {
-            type: "string",
-            minLength: 1
+            type: 'string',
+            minLength: 1,
         },
         color: {
-            type: "string",
-            pattern: colorPattern
-        }
-    }
+            type: 'string',
+            pattern: colorPattern,
+        },
+    },
 };
 
 const portProperties: Schema = {
-    type: "number",
+    type: 'number',
     minimum: 443,
-    maximum: 65535
+    maximum: 65535,
 };
 
 export const configurationSchema: Schema = {
-    id: "/configuration",
-    type: "object",
+    id: '/configuration',
+    type: 'object',
     properties: {
-        showServerOutput: { type: "boolean" },
+        showServerOutput: { type: 'boolean' },
         ports: {
-            type: "object",
+            type: 'object',
             properties: {
                 server: portProperties,
-                socket: portProperties
+                socket: portProperties,
             },
-            required: ["server"],
-            additionalProperties: true
+            required: ['server'],
+            additionalProperties: true,
         },
         identifiers: {
-            type: "object",
+            type: 'object',
             properties: {
                 master: identifierProperties,
                 worker: identifierProperties,
-                exec: identifierProperties
-            }
+                exec: identifierProperties,
+            },
         },
         polling: {
-            type: "object",
+            type: 'object',
             additionalProperties: false,
             properties: {
                 intervalSeconds: {
-                    type: "number",
+                    type: 'number',
                     minimum: 1,
-                    maximum: 86400
+                    maximum: 86400,
                 },
                 route: {
-                    type: "string",
-                    pattern: /\/[a-zA-Z]*/g
+                    type: 'string',
+                    pattern: /\/[a-zA-Z]*/g,
                 },
                 failureTolerance: {
-                    type: "number",
+                    type: 'number',
                     minimum: 0,
-                }
-            }
+                },
+            },
         },
-    }
+    },
 };
 
-type ColorLabel = "yellow" | "red" | "cyan" | "green" | "blue" | "magenta" | "grey" | "gray" | "white" | "black";
+type ColorLabel = 'yellow' | 'red' | 'cyan' | 'green' | 'blue' | 'magenta' | 'grey' | 'gray' | 'white' | 'black';
 
 export const colorMapping: Map<ColorLabel, Color> = new Map([
-    ["yellow", yellow],
-    ["red", red],
-    ["cyan", cyan],
-    ["green", green],
-    ["blue", blue],
-    ["magenta", magenta],
-    ["grey", grey],
-    ["gray", gray],
-    ["white", white],
-    ["black", black]
+    ['yellow', yellow],
+    ['red', red],
+    ['cyan', cyan],
+    ['green', green],
+    ['blue', blue],
+    ['magenta', magenta],
+    ['grey', grey],
+    ['gray', gray],
+    ['white', white],
+    ['black', black],
 ]);
 
 interface Identifier {
@@ -108,22 +108,22 @@ export const defaultConfig: Configuration = {
     showServerOutput: false,
     identifiers: {
         master: {
-            text: "__monitor__",
-            color: "yellow"
+            text: '__monitor__',
+            color: 'yellow',
         },
         worker: {
-            text: "__server__",
-            color: "magenta"
+            text: '__server__',
+            color: 'magenta',
         },
         exec: {
-            text: "__exec__",
-            color: "green"
-        }
+            text: '__exec__',
+            color: 'green',
+        },
     },
     ports: { server: 1050 },
     polling: {
-        route: "/",
+        route: '/',
         intervalSeconds: 30,
-        failureTolerance: 0
-    }
-};
\ No newline at end of file
+        failureTolerance: 0,
+    },
+};
diff --git a/src/server/DashSession/Session/utilities/utilities.ts b/src/server/DashSession/Session/utilities/utilities.ts
index eb8de9d7e..a2ba29c67 100644
--- a/src/server/DashSession/Session/utilities/utilities.ts
+++ b/src/server/DashSession/Session/utilities/utilities.ts
@@ -1,31 +1,16 @@
-import { v4 } from "uuid";
+import { v4 } from 'uuid';
 
 export namespace Utilities {
-
     export function guid() {
         return v4();
     }
 
-    /**
-         * At any arbitrary layer of nesting within the configuration objects, any single value that
-         * is not specified by the configuration is given the default counterpart. If, within an object,
-         * one peer is given by configuration and two are not, the one is preserved while the two are given
-         * the default value.
-         * @returns the composition of all of the assigned objects, much like Object.assign(), but with more
-         * granularity in the overwriting of nested objects
-         */
-    export function preciseAssign(target: any, ...sources: any[]): any {
-        for (const source of sources) {
-            preciseAssignHelper(target, source);
-        }
-        return target;
-    }
-
     export function preciseAssignHelper(target: any, source: any) {
-        Array.from(new Set([...Object.keys(target), ...Object.keys(source)])).map(property => {
-            let targetValue: any, sourceValue: any;
-            if (sourceValue = source[property]) {
-                if (typeof sourceValue === "object" && typeof (targetValue = target[property]) === "object") {
+        Array.from(new Set([...Object.keys(target), ...Object.keys(source)])).forEach(property => {
+            const targetValue = target[property];
+            const sourceValue = source[property];
+            if (sourceValue) {
+                if (typeof sourceValue === 'object' && typeof targetValue === 'object') {
                     preciseAssignHelper(targetValue, sourceValue);
                 } else {
                     target[property] = sourceValue;
@@ -34,4 +19,18 @@ export namespace Utilities {
         });
     }
 
-}
\ No newline at end of file
+    /**
+     * At any arbitrary layer of nesting within the configuration objects, any single value that
+     * is not specified by the configuration is given the default counterpart. If, within an object,
+     * one peer is given by configuration and two are not, the one is preserved while the two are given
+     * the default value.
+     * @returns the composition of all of the assigned objects, much like Object.assign(), but with more
+     * granularity in the overwriting of nested objects
+     */
+    export function preciseAssign(target: any, ...sources: any[]): any {
+        sources.forEach(source => {
+            preciseAssignHelper(target, source);
+        });
+        return target;
+    }
+}
diff --git a/src/server/DashStats.ts b/src/server/DashStats.ts
index 485ab9f99..808d2c6f2 100644
--- a/src/server/DashStats.ts
+++ b/src/server/DashStats.ts
@@ -1,7 +1,6 @@
 import { cyan, magenta } from 'colors';
 import { Response } from 'express';
 import * as fs from 'fs';
-import SocketIO from 'socket.io';
 import { socketMap, timeMap, userOperations } from './SocketData';
 
 /**
@@ -242,7 +241,7 @@ export namespace DashStats {
      * @param username the username in the format of "username@domain.com logged in"
      * @param socket the websocket associated with the current connection
      */
-    export function logUserLogin(username: string | undefined, socket: SocketIO.Socket) {
+    export function logUserLogin(username: string | undefined) {
         if (!(username === undefined)) {
             const currentDate = new Date();
             console.log(magenta(`User ${username.split(' ')[0]} logged in at: ${currentDate.toISOString()}`));
@@ -267,7 +266,7 @@ export namespace DashStats {
      * @param username the username in the format of "username@domain.com logged in"
      * @param socket the websocket associated with the current connection.
      */
-    export function logUserLogout(username: string | undefined, socket: SocketIO.Socket) {
+    export function logUserLogout(username: string | undefined) {
         if (!(username === undefined)) {
             const currentDate = new Date();
 
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 3d8325da9..08cea1de5 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -186,8 +186,8 @@ export namespace DashUploadUtils {
         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;
+            new ExifImage({ image }, (exifError, data) => {
+                const reason = (exifError as any)?.code;
                 resolve({ data, error: reason });
             });
         });
@@ -506,8 +506,8 @@ export namespace DashUploadUtils {
         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));
+                const pdfTextFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
+                const readStream = createReadStream(serverPathToFile(Directory.text, pdfTextFilename));
                 let rawText = '';
                 readStream
                     .on('data', chunk => {
diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts
index 423c719c2..041f65592 100644
--- a/src/server/GarbageCollector.ts
+++ b/src/server/GarbageCollector.ts
@@ -1,11 +1,15 @@
+/* eslint-disable no-await-in-loop */
+/* eslint-disable no-continue */
+/* eslint-disable no-cond-assign */
+/* eslint-disable no-restricted-syntax */
 import * as fs from 'fs';
 import * as path from 'path';
 import { Database } from './database';
 import { Search } from './Search';
 
-
 function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
     for (const key in doc) {
+        // eslint-disable-next-line no-prototype-builtins
         if (!doc.hasOwnProperty(key)) {
             continue;
         }
@@ -13,22 +17,22 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
         if (field === undefined || field === null) {
             continue;
         }
-        if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
+        if (field.__type === 'proxy' || field.__type === 'prefetch_proxy') {
             ids.push(field.fieldId);
-        } else if (field.__type === "list") {
+        } else if (field.__type === 'list') {
             addDoc(field.fields, ids, files);
-        } else if (typeof field === "string") {
-            const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w\-]*)"/g;
+        } 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") {
+        } 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/");
+                const split = new URL(urlString).pathname.split('doc/');
                 if (split.length > 1) {
                     ids.push(split[split.length - 1]);
                 }
@@ -36,7 +40,7 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
             const re2 = /"src"\s*:\s*"(.*?)"/g;
             while ((match = re2.exec(field.Data)) !== null) {
                 const urlString = match[1];
-                const pathname = new URL(urlString).pathname;
+                const { pathname } = new URL(urlString);
                 const ext = path.extname(pathname);
                 const fileName = path.basename(pathname, ext);
                 let exts = files[fileName];
@@ -45,9 +49,9 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
                 }
                 exts.push(ext);
             }
-        } else if (["audio", "image", "video", "pdf", "web", "map"].includes(field.__type)) {
+        } else if (['audio', 'image', 'video', 'pdf', 'web', 'map'].includes(field.__type)) {
             const url = new URL(field.url);
-            const pathname = url.pathname;
+            const { pathname } = url;
             const ext = path.extname(pathname);
             const fileName = path.basename(pathname, ext);
             let exts = files[fileName];
@@ -60,12 +64,12 @@ function addDoc(doc: any, ids: string[], files: { [name: string]: string[] }) {
 }
 
 async function GarbageCollect(full: boolean = true) {
-    console.log("start GC");
+    console.log('start GC');
     const start = Date.now();
     // await new Promise(res => setTimeout(res, 3000));
     const cursor = await Database.Instance.query({}, { userDocumentId: 1 }, 'users');
     const users = await cursor.toArray();
-    const ids: string[] = [...users.map((user:any) => user.userDocumentId), ...users.map((user:any) => user.sharingDocumentId), ...users.map((user:any) => user.linkDatabaseId)];
+    const ids: string[] = [...users.map((user: any) => user.userDocumentId), ...users.map((user: any) => user.sharingDocumentId), ...users.map((user: any) => user.linkDatabaseId)];
     const visited = new Set<string>();
     const files: { [name: string]: string[] } = {};
 
@@ -76,9 +80,11 @@ async function GarbageCollect(full: boolean = true) {
         if (!fetchIds.length) {
             continue;
         }
-        const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res));
+        const docs = await new Promise<{ [key: string]: any }[]>(res => {
+            Database.Instance.getDocuments(fetchIds, res);
+        });
         for (const doc of docs) {
-            const id = doc.id;
+            const { id } = doc;
             if (doc === undefined) {
                 console.log(`Couldn't find field with Id ${id}`);
                 continue;
@@ -95,19 +101,27 @@ async function GarbageCollect(full: boolean = true) {
 
     const notToDelete = Array.from(visited);
     const toDeleteCursor = await Database.Instance.query({ _id: { $nin: notToDelete } }, { _id: 1 });
-    const toDelete: string[] = (await toDeleteCursor.toArray()).map((doc:any) => doc._id);
+    const toDelete: string[] = (await toDeleteCursor.toArray()).map((doc: any) => doc._id);
     toDeleteCursor.close();
     if (!full) {
-        await Database.Instance.updateMany({ _id: { $nin: notToDelete } }, { $set: { "deleted": true } });
-        await Database.Instance.updateMany({ _id: { $in: notToDelete } }, { $unset: { "deleted": true } });
-        console.log(await Search.updateDocuments(
-            notToDelete.map<any>(id => ({
-                id, deleted: { set: null }
-            }))
-                .concat(toDelete.map(id => ({
-                    id, deleted: { set: true }
-                })))));
-        console.log("Done with partial GC");
+        await Database.Instance.updateMany({ _id: { $nin: notToDelete } }, { $set: { deleted: true } });
+        await Database.Instance.updateMany({ _id: { $in: notToDelete } }, { $unset: { deleted: true } });
+        console.log(
+            await Search.updateDocuments(
+                notToDelete
+                    .map<any>(id => ({
+                        id,
+                        deleted: { set: null },
+                    }))
+                    .concat(
+                        toDelete.map(id => ({
+                            id,
+                            deleted: { set: true },
+                        }))
+                    )
+            )
+        );
+        console.log('Done with partial GC');
         console.log(`Took ${(Date.now() - start) / 1000} seconds`);
     } else {
         let i = 0;
@@ -123,15 +137,15 @@ async function GarbageCollect(full: boolean = true) {
         console.log(`${deleted} documents deleted`);
 
         await Search.deleteDocuments(toDelete);
-        console.log("Cleared search documents");
+        console.log('Cleared search documents');
 
-        const folder = "./src/server/public/files/";
+        const folder = './src/server/public/files/';
         fs.readdir(folder, (_, fileList) => {
             const filesToDelete = fileList.filter(file => {
                 const ext = path.extname(file);
                 let base = path.basename(file, ext);
                 const existsInDb = (base in files || (base = base.substring(0, base.length - 2)) in files) && files[base].includes(ext);
-                return file !== ".gitignore" && !existsInDb;
+                return file !== '.gitignore' && !existsInDb;
             });
             console.log(`Deleting ${filesToDelete.length} files`);
             filesToDelete.forEach(file => {
diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts
index b74332bf5..1432d91c4 100644
--- a/src/server/MemoryDatabase.ts
+++ b/src/server/MemoryDatabase.ts
@@ -3,16 +3,15 @@ import { DocumentsCollection, IDatabase } from './IDatabase';
 import { Transferable } from './Message';
 
 export class MemoryDatabase implements IDatabase {
-
     private db: { [collectionName: string]: { [id: string]: any } } = {};
 
     private getCollection(collectionName: string) {
         const collection = this.db[collectionName];
         if (collection) {
             return collection;
-        } else {
-            return this.db[collectionName] = {};
         }
+        this.db[collectionName] = {};
+        return {};
     }
 
     public getCollectionNames() {
@@ -21,15 +20,15 @@ export class MemoryDatabase implements IDatabase {
 
     public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise<void> {
         const collection = this.getCollection(collectionName);
-        const set = "$set";
+        const set = '$set';
         if (set in value) {
             let currentVal = collection[id] ?? (collection[id] = {});
             const val = value[set];
             for (const key in val) {
-                const keys = key.split(".");
+                const keys = key.split('.');
                 for (let i = 0; i < keys.length - 1; i++) {
                     const k = keys[i];
-                    if (typeof currentVal[k] === "object") {
+                    if (typeof currentVal[k] === 'object') {
                         currentVal = currentVal[k];
                     } else {
                         currentVal[k] = {};
@@ -45,7 +44,7 @@ export class MemoryDatabase implements IDatabase {
         return Promise.resolve(undefined);
     }
 
-    public updateMany(query: any, update: any, collectionName = DocumentsCollection): Promise<mongodb.UpdateResult> {
+    public updateMany(/* query: any, update: any, collectionName = DocumentsCollection */): Promise<mongodb.UpdateResult> {
         throw new Error("Can't updateMany a MemoryDatabase");
     }
 
@@ -54,7 +53,9 @@ export class MemoryDatabase implements IDatabase {
     }
 
     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>;
+    // eslint-disable-next-line no-dupe-class-members
     public delete(id: any, collectionName = DocumentsCollection): Promise<mongodb.DeleteResult> {
         const i = id.id ?? id;
         delete this.getCollection(collectionName)[i];
@@ -75,7 +76,7 @@ export class MemoryDatabase implements IDatabase {
     }
 
     public insert(value: any, collectionName = DocumentsCollection): Promise<void> {
-        const id = value.id;
+        const { id } = value;
         this.getCollection(collectionName)[id] = value;
         return Promise.resolve();
     }
@@ -93,14 +94,18 @@ export class MemoryDatabase implements IDatabase {
             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);
+                });
+                // eslint-disable-next-line no-restricted-syntax
+                for (const doc of docs) {
+                    const { id } = doc;
+                    visited.add(id);
+                    // eslint-disable-next-line no-await-in-loop
+                    ids.push(...(await fn(doc)));
+                }
             }
         }
     }
diff --git a/src/server/PdfTypes.ts b/src/server/PdfTypes.ts
index e87f08e1d..fb435a677 100644
--- a/src/server/PdfTypes.ts
+++ b/src/server/PdfTypes.ts
@@ -1,21 +1,19 @@
-export interface ParsedPDF {
-    numpages: number;
-    numrender: number;
-    info: PDFInfo;
-    metadata: PDFMetadata;
-    version: string; //https://mozilla.github.io/pdf.js/getting_started/
-    text: string;
-}
-
 export interface PDFInfo {
     PDFFormatVersion: string;
     IsAcroFormPresent: boolean;
     IsXFAPresent: boolean;
     [key: string]: any;
 }
-
 export interface PDFMetadata {
     parse(): void;
     get(name: string): string;
     has(name: string): boolean;
-}
\ No newline at end of file
+}
+export interface ParsedPDF {
+    numpages: number;
+    numrender: number;
+    info: PDFInfo;
+    metadata: PDFMetadata;
+    version: string; // https://mozilla.github.io/pdf.js/getting_started/
+    text: string;
+}
diff --git a/src/server/ProcessFactory.ts b/src/server/ProcessFactory.ts
index f69eda4c3..3791b0e1e 100644
--- a/src/server/ProcessFactory.ts
+++ b/src/server/ProcessFactory.ts
@@ -1,44 +1,42 @@
-import { ChildProcess, spawn, StdioOptions } from "child_process";
-import { existsSync, mkdirSync } from "fs";
-import { Stream } from "stream";
+import { ChildProcess, spawn, StdioOptions } from 'child_process';
+import { existsSync, mkdirSync } from 'fs';
+import { rimraf } from 'rimraf';
+import { Stream } from 'stream';
 import { fileDescriptorFromStream, pathFromRoot } from './ActionUtilities';
-import { rimraf } from "rimraf";
-
-export namespace ProcessFactory {
-
-    export type Sink = "pipe" | "ipc" | "ignore" | "inherit" | Stream | number | null | undefined;
-
-    export async function createWorker(command: string, args?: readonly string[], stdio?: StdioOptions | "logfile", detached = true): Promise<ChildProcess> {
-        if (stdio === "logfile") {
-            const log_fd = await Logger.create(command, args);
-            stdio = ["ignore", log_fd, log_fd];
-        }
-        const child = spawn(command, args ?? [], { detached, stdio });
-        child.unref();
-        return child;
-    }
-
-}
 
 export namespace Logger {
-
-    const logPath = pathFromRoot("./logs");
+    const logPath = pathFromRoot('./logs');
 
     export async function initialize() {
         if (existsSync(logPath)) {
             if (!process.env.SPAWNED) {
-                await new Promise<any>(resolve => rimraf(logPath).then(resolve));
+                await new Promise<any>(resolve => {
+                    rimraf(logPath).then(resolve);
+                });
             }
         }
         mkdirSync(logPath);
     }
 
-    export async function create(command: string, args?: readonly string[]): Promise<number> {
-        return fileDescriptorFromStream(generate_log_path(command, args));
+    function generateLogPath(command: string, args?: readonly string[]) {
+        return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`);
     }
 
-    function generate_log_path(command: string, args?: readonly string[]) {
-        return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`);
+    export async function create(command: string, args?: readonly string[]): Promise<number> {
+        return fileDescriptorFromStream(generateLogPath(command, args));
     }
+}
+export namespace ProcessFactory {
+    export type Sink = 'pipe' | 'ipc' | 'ignore' | 'inherit' | Stream | number | null | undefined;
 
-}
\ No newline at end of file
+    export async function createWorker(command: string, args?: readonly string[], stdio?: StdioOptions | 'logfile', detached = true): Promise<ChildProcess> {
+        if (stdio === 'logfile') {
+            const logFd = await Logger.create(command, args);
+            // eslint-disable-next-line no-param-reassign
+            stdio = ['ignore', logFd, logFd];
+        }
+        const child = spawn(command, args ?? [], { detached, stdio });
+        child.unref();
+        return child;
+    }
+}
diff --git a/src/server/RouteSubscriber.ts b/src/server/RouteSubscriber.ts
index a1cf7c1c4..b923805a8 100644
--- a/src/server/RouteSubscriber.ts
+++ b/src/server/RouteSubscriber.ts
@@ -18,9 +18,8 @@ export default class RouteSubscriber {
     public get build() {
         let output = this._root;
         if (this.requestParameters.length) {
-            output = `${output}/:${this.requestParameters.join("/:")}`;
+            output = `${output}/:${this.requestParameters.join('/:')}`;
         }
         return output;
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts
index 7db1c2dae..0a961f570 100644
--- a/src/server/SharedMediaTypes.ts
+++ b/src/server/SharedMediaTypes.ts
@@ -22,24 +22,17 @@ export namespace Upload {
         return 'duration' in uploadResponse;
     }
 
+    export interface AccessPathInfo {
+        [suffix: string]: { client: string; server: string };
+    }
     export interface FileInformation {
         accessPaths: AccessPathInfo;
         rawText?: string;
         duration?: number;
     }
-
-    export type FileResponse<T extends FileInformation = FileInformation> = { source: File; result: T | Error };
-
-    export type ImageInformation = FileInformation & InspectionResults;
-
-    export type VideoInformation = FileInformation & VideoResults;
-
-    export interface AccessPathInfo {
-        [suffix: string]: { client: string; server: string };
-    }
-
-    export interface VideoResults {
-        duration: number;
+    export interface EnrichedExifData {
+        data: ExifData & ExifData['gps'];
+        error?: string;
     }
     export interface InspectionResults {
         source: string;
@@ -51,9 +44,13 @@ export namespace Upload {
         nativeHeight: number;
         filename?: string;
     }
-
-    export interface EnrichedExifData {
-        data: ExifData & ExifData['gps'];
-        error?: string;
+    export interface VideoResults {
+        duration: number;
     }
+
+    export type FileResponse<T extends FileInformation = FileInformation> = { source: File; result: T | Error };
+
+    export type ImageInformation = FileInformation & InspectionResults;
+
+    export type VideoInformation = FileInformation & VideoResults;
 }
diff --git a/src/server/apis/google/CredentialsLoader.ts b/src/server/apis/google/CredentialsLoader.ts
index ef1f9a91e..46dc00b8a 100644
--- a/src/server/apis/google/CredentialsLoader.ts
+++ b/src/server/apis/google/CredentialsLoader.ts
@@ -1,10 +1,9 @@
-import { readFile, readFileSync } from "fs";
-import { pathFromRoot } from "../../ActionUtilities";
-import { SecureContextOptions } from "tls";
-import { blue, red } from "colors";
+import { readFile, readFileSync } from 'fs';
+import { SecureContextOptions } from 'tls';
+import { blue, red } from 'colors';
+import { pathFromRoot } from '../../ActionUtilities';
 
 export namespace GoogleCredentialsLoader {
-
     export interface InstalledCredentials {
         client_id: string;
         project_id: string;
@@ -28,18 +27,16 @@ export namespace GoogleCredentialsLoader {
             });
         });
     }
-
 }
 
 export namespace SSL {
-
     export let Credentials: SecureContextOptions = {};
     export let Loaded = false;
 
     const suffixes = {
-        privateKey: ".key",
-        certificate: ".crt",
-        caBundle: "-ca.crt"
+        privateKey: '.key',
+        certificate: '.crt',
+        caBundle: '-ca.crt',
     };
 
     export async function loadCredentials() {
@@ -57,11 +54,10 @@ export namespace SSL {
     }
 
     export function exit() {
-        console.log(red("Running this server in release mode requires the following SSL credentials in the project root:"));
-        const serverName = process.env.serverName ? process.env.serverName : "{process.env.serverName}";
+        console.log(red('Running this server in release mode requires the following SSL credentials in the project root:'));
+        const serverName = process.env.serverName ? process.env.serverName : '{process.env.serverName}';
         Object.values(suffixes).forEach(suffix => console.log(blue(`${serverName}${suffix}`)));
-        console.log(red("Please ensure these files exist and restart, or run this in development mode."));
+        console.log(red('Please ensure these files exist and restart, or run this in development mode.'));
         process.exit(0);
     }
-
 }
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 55e5fd7c0..d3acc968b 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -57,12 +57,12 @@ export namespace GoogleApiServerUtils {
      * global, intentionally unauthenticated worker OAuth2 client instance.
      */
     export function processProjectCredentials(): void {
-        const { client_secret, client_id, redirect_uris } = GoogleCredentialsLoader.ProjectCredentials;
+        const { client_secret: clientSecret, client_id: clientId, redirect_uris: redirectUris } = GoogleCredentialsLoader.ProjectCredentials;
         // initialize the global authorization client
         oAuthOptions = {
-            clientId: client_id,
-            clientSecret: client_secret,
-            redirectUri: redirect_uris[0],
+            clientId,
+            clientSecret,
+            redirectUri: redirectUris[0],
         };
         worker = generateClient();
     }
diff --git a/src/server/apis/google/SharedTypes.ts b/src/server/apis/google/SharedTypes.ts
index 9ad6130b6..f5e0e2e2b 100644
--- a/src/server/apis/google/SharedTypes.ts
+++ b/src/server/apis/google/SharedTypes.ts
@@ -1,9 +1,3 @@
-export interface NewMediaItemResult {
-    uploadToken: string;
-    status: { code: number, message: string };
-    mediaItem: MediaItem;
-}
-
 export interface MediaItem {
     id: string;
     description: string;
@@ -17,5 +11,10 @@ export interface MediaItem {
     };
     filename: string;
 }
+export interface NewMediaItemResult {
+    uploadToken: string;
+    status: { code: number; message: string };
+    mediaItem: MediaItem;
+}
 
-export type MediaItemCreationResult = { newMediaItemResults: NewMediaItemResult[] };
\ No newline at end of file
+export type MediaItemCreationResult = { newMediaItemResults: NewMediaItemResult[] };
diff --git a/src/server/apis/youtube/youtubeApiSample.d.ts b/src/server/apis/youtube/youtubeApiSample.d.ts
index 427f54608..97c3f0b54 100644
--- a/src/server/apis/youtube/youtubeApiSample.d.ts
+++ b/src/server/apis/youtube/youtubeApiSample.d.ts
@@ -1,2 +1,2 @@
 declare const YoutubeApi: any;
-export = YoutubeApi;
\ No newline at end of file
+export = YoutubeApi;
diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts
index 3bc21ecb6..a288bfeab 100644
--- a/src/server/authentication/DashUserModel.ts
+++ b/src/server/authentication/DashUserModel.ts
@@ -73,9 +73,9 @@ userSchema.pre('save', function save(next) {
             user.password,
             salt,
             () => {},
-            (err: mongoose.Error, hash: string) => {
-                if (err) {
-                    return next(err);
+            (cryptErr: mongoose.Error, hash: string) => {
+                if (cryptErr) {
+                    return next(cryptErr);
                 }
                 user.password = hash;
                 next();
@@ -97,7 +97,7 @@ const comparePassword: comparePasswordFunction = function (this: DashUserModel,
 
 userSchema.methods.comparePassword = comparePassword;
 
-const User = mongoose.model('User', userSchema);
+const User: any = mongoose.model('User', userSchema);
 export function initializeGuest() {
     new User({
         email: 'guest',
diff --git a/src/server/authentication/Passport.ts b/src/server/authentication/Passport.ts
index a9cf6698b..a5222e531 100644
--- a/src/server/authentication/Passport.ts
+++ b/src/server/authentication/Passport.ts
@@ -1,6 +1,6 @@
 import * as passport from 'passport';
 import * as passportLocal from 'passport-local';
-import { DashUserModel, default as User } from './DashUserModel';
+import User, { DashUserModel } from './DashUserModel';
 
 const LocalStrategy = passportLocal.Strategy;
 
@@ -11,22 +11,25 @@ passport.serializeUser<any, any>((req, user, done) => {
 passport.deserializeUser<any, any>((id, done) => {
     User.findById(id)
         .exec()
-        .then(user => done(undefined, user));
+        .then((user: any) => done(undefined, user));
 });
 
 // AUTHENTICATE JUST WITH EMAIL AND PASSWORD
 passport.use(
     new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => {
         User.findOne({ email: email.toLowerCase() })
-            .then(user => {
-                if (!user) return done(undefined, false, { message: 'Invalid email or password' }); // invalid email
-                (user as any as DashUserModel).comparePassword(password, (error: Error, isMatch: boolean) => {
-                    if (error) return done(error);
-                    if (!isMatch) return done(undefined, false, { message: 'Invalid email or password' }); // invalid password
-                    // valid authentication HERE
-                    return done(undefined, user);
-                });
+            .then((user: any) => {
+                if (!user) {
+                    done(undefined, false, { message: 'Invalid email or password' }); // invalid email
+                } else {
+                    (user as any as DashUserModel).comparePassword(password, (error: Error, isMatch: boolean) => {
+                        if (error) return done(error);
+                        if (!isMatch) return done(undefined, false, { message: 'Invalid email or password' }); // invalid password
+                        // valid authentication HERE
+                        return done(undefined, user);
+                    });
+                }
             })
-            .catch(error => done(error));
+            .catch((error: any) => done(error));
     })
 );
diff --git a/src/server/database.ts b/src/server/database.ts
index 3a28dc87e..ff5635b2c 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -58,6 +58,7 @@ export namespace Database {
         }
     }
 
+    // eslint-disable-next-line @typescript-eslint/no-shadow
     export class Database implements IDatabase {
         private MongoClient = mongodb.MongoClient;
         private currentWrites: { [id: string]: Promise<void> } = {};
diff --git a/src/server/index.ts b/src/server/index.ts
index 5a86f36d9..4374a72b7 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,15 +1,14 @@
 import { yellow } from 'colors';
+// eslint-disable-next-line import/no-extraneous-dependencies
 import * as dotenv from 'dotenv';
 import * as mobileDetect from 'mobile-detect';
 import * as path from 'path';
-import * as qs from 'query-string';
 import { logExecution } from './ActionUtilities';
 import { AdminPrivileges, resolvedPorts } from './SocketData';
 import DataVizManager from './ApiManagers/DataVizManager';
 import DeleteManager from './ApiManagers/DeleteManager';
 import DownloadManager from './ApiManagers/DownloadManager';
 import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager';
-//import GooglePhotosManager from './ApiManagers/GooglePhotosManager';
 import { SearchManager } from './ApiManagers/SearchManager';
 import SessionManager from './ApiManagers/SessionManager';
 import UploadManager from './ApiManagers/UploadManager';
@@ -26,8 +25,11 @@ import { Logger } from './ProcessFactory';
 import RouteManager, { Method, PublicHandler } from './RouteManager';
 import RouteSubscriber from './RouteSubscriber';
 import initializeServer from './server_Initialization';
+// import GooglePhotosManager from './ApiManagers/GooglePhotosManager';
+
 dotenv.config();
 export const onWindows = process.platform === 'win32';
+// eslint-disable-next-line import/no-mutable-exports
 export let sessionAgent: AppliedSessionAgent;
 
 /**
@@ -60,8 +62,18 @@ async function preliminaryFunctions() {
  * that will manage the registration of new routes
  * with the server
  */
-function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: RouteManager) {
-    const managers = [new SessionManager(), new UserManager(), new UploadManager(), new DownloadManager(), new SearchManager(), new DeleteManager(), new UtilManager(), new GeneralGoogleManager(), /* new GooglePhotosManager(),*/ new DataVizManager()];
+function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManager) {
+    const managers = [
+        new SessionManager(),
+        new UserManager(),
+        new UploadManager(),
+        new DownloadManager(),
+        new SearchManager(),
+        new DeleteManager(),
+        new UtilManager(),
+        new GeneralGoogleManager(),
+        /* new GooglePhotosManager(), */ new DataVizManager(),
+    ];
 
     // initialize API Managers
     console.log(yellow('\nregistering server routes...'));
@@ -102,6 +114,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
     });
 
     const serve: PublicHandler = ({ req, res }) => {
+        // eslint-disable-next-line new-cap
         const detector = new mobileDetect(req.headers['user-agent'] || '');
         const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';
         res.sendFile(path.join(__dirname, '../../deploy/' + filename));
@@ -116,9 +129,8 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
         secureHandler: ({ res, isRelease }) => {
             const { PASSWORD } = process.env;
             if (!(isRelease && PASSWORD)) {
-                return res.redirect('/home');
-            }
-            res.render('admin.pug', { title: 'Enter Administrator Password' });
+                res.redirect('/home');
+            } else res.render('admin.pug', { title: 'Enter Administrator Password' });
         },
     });
 
@@ -128,18 +140,19 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
         secureHandler: async ({ req, res, isRelease, user: { id } }) => {
             const { PASSWORD } = process.env;
             if (!(isRelease && PASSWORD)) {
-                return res.redirect('/home');
-            }
-            const { password } = req.body;
-            const { previous_target } = req.params;
-            let redirect: string;
-            if (password === PASSWORD) {
-                AdminPrivileges.set(id, true);
-                redirect = `/${previous_target.replace(':', '/')}`;
+                res.redirect('/home');
             } else {
-                redirect = `/admin/${previous_target}`;
+                const { password } = req.body;
+                const { previous_target: previousTarget } = req.params;
+                let redirect: string;
+                if (password === PASSWORD) {
+                    AdminPrivileges.set(id, true);
+                    redirect = `/${previousTarget.replace(':', '/')}`;
+                } else {
+                    redirect = `/admin/${previousTarget}`;
+                }
+                res.redirect(redirect);
             }
-            res.redirect(redirect);
         },
     });
 
@@ -149,7 +162,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
         secureHandler: serve,
         publicHandler: ({ req, res, ...remaining }) => {
             const { originalUrl: target } = req;
-            const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === 'true';
             const docAccess = target.startsWith('/doc/');
             // since this is the public handler, there's no meaning of '/home' to speak of
             // since there's no user logged in, so the only viable operation
diff --git a/src/server/updateProtos.ts b/src/server/updateProtos.ts
index 2f3772a77..72a44ebf4 100644
--- a/src/server/updateProtos.ts
+++ b/src/server/updateProtos.ts
@@ -6,15 +6,15 @@ const protos = ['text', 'image', 'web', 'collection', 'kvp', 'video', 'audio', '
     await Promise.all(
         protos.map(
             protoId =>
-                new Promise(res =>
+                new Promise(res => {
                     Database.Instance.update(
                         protoId,
                         {
                             $set: { 'fields.isBaseProto': true },
                         },
                         res
-                    )
-                )
+                    );
+                })
         )
     );
 
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index cb16bce72..cece8a1b7 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -79,7 +79,7 @@ export namespace WebSocket {
         timeMap[userEmail] = Date.now();
         socketMap.set(socket, userEmail + ' at ' + datetime);
         userOperations.set(userEmail, 0);
-        DashStats.logUserLogin(userEmail, socket);
+        DashStats.logUserLogin(userEmail);
     }
 
     function getField([id, callback]: [string, (result?: Transferable) => void]) {
@@ -417,38 +417,12 @@ export namespace WebSocket {
                 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);
-                        }
-                    });
-                }
+                networkInterfaces().keys?.forEach(dev => {
+                    if (dev.family === 'IPv4' && dev.address !== '127.0.0.1') {
+                        socket.emit('ipaddr', dev.address);
+                    }
+                });
             });
 
             socket.on('bye', () => {
@@ -459,7 +433,7 @@ export namespace WebSocket {
                 const currentUser = socketMap.get(socket);
                 if (!(currentUser === undefined)) {
                     const currentUsername = currentUser.split(' ')[0];
-                    DashStats.logUserLogout(currentUsername, socket);
+                    DashStats.logUserLogout(currentUsername);
                     delete timeMap[currentUsername];
                 }
             });
-- 
cgit v1.2.3-70-g09d2


From 6de58d7ecbfd14beb7389c6ff56e764b5c00ba25 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 30 Apr 2024 01:10:20 -0400
Subject: changed acl- and some other field- to acl_ and field_ style

---
 report.20231129.000028.55430.0.001.json            | 1343 --------------------
 src/client/documents/DocumentTypes.ts              |    1 -
 src/client/documents/Documents.ts                  |  137 +-
 src/client/util/CurrentUserUtils.ts                |    4 +-
 src/client/util/ServerStats.tsx                    |    2 +-
 src/client/util/SharingManager.tsx                 |   22 +-
 src/client/views/DashboardView.tsx                 |    2 +-
 src/client/views/FilterPanel.tsx                   |    2 +-
 src/client/views/PropertiesView.tsx                |   16 +-
 .../views/collections/CollectionCarouselView.tsx   |    2 +-
 src/client/views/collections/CollectionMenu.tsx    |   12 +-
 src/client/views/collections/CollectionView.tsx    |    4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |    2 +-
 src/client/views/linking/LinkMenuItem.tsx          |    3 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |    4 +-
 src/client/views/nodes/ImageBox.tsx                |    5 +-
 src/client/views/nodes/KeyValuePair.tsx            |    2 +-
 src/client/views/nodes/trails/PresBox.tsx          |    2 +-
 src/fields/Doc.ts                                  |   12 +-
 src/fields/util.ts                                 |   22 +-
 src/server/authentication/DashUserModel.ts         |    4 +-
 src/server/authentication/Passport.ts              |    4 +-
 22 files changed, 127 insertions(+), 1480 deletions(-)
 delete mode 100644 report.20231129.000028.55430.0.001.json

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

diff --git a/report.20231129.000028.55430.0.001.json b/report.20231129.000028.55430.0.001.json
deleted file mode 100644
index a29d550ae..000000000
--- a/report.20231129.000028.55430.0.001.json
+++ /dev/null
@@ -1,1343 +0,0 @@
-
-{
-  "header": {
-    "reportVersion": 1,
-    "event": "Allocation failed - JavaScript heap out of memory",
-    "trigger": "FatalError",
-    "filename": "report.20231129.000028.55430.0.001.json",
-    "dumpEventTime": "2023-11-29T00:00:28Z",
-    "dumpEventTimeStamp": "1701234028056",
-    "processId": 55430,
-    "cwd": "/Users/sarah/Desktop/dash/Dash-Web",
-    "commandLine": [
-      "/Users/sarah/.nvm/versions/node/v12.16.0/bin/node",
-      "--max-old-space-size=2048",
-      "/Users/sarah/Desktop/dash/Dash-Web/node_modules/ts-node-dev/lib/wrap.js",
-      "/Users/sarah/Desktop/dash/Dash-Web/node_modules/fork-ts-checker-webpack-plugin/lib/service.js"
-    ],
-    "nodejsVersion": "v12.16.0",
-    "wordSize": 64,
-    "arch": "x64",
-    "platform": "darwin",
-    "componentVersions": {
-      "node": "12.16.0",
-      "v8": "7.8.279.23-node.31",
-      "uv": "1.34.0",
-      "zlib": "1.2.11",
-      "brotli": "1.0.7",
-      "ares": "1.15.0",
-      "modules": "72",
-      "nghttp2": "1.40.0",
-      "napi": "5",
-      "llhttp": "2.0.4",
-      "http_parser": "2.9.3",
-      "openssl": "1.1.1d",
-      "cldr": "35.1",
-      "icu": "64.2",
-      "tz": "2019c",
-      "unicode": "12.1"
-    },
-    "release": {
-      "name": "node",
-      "lts": "Erbium",
-      "headersUrl": "https://nodejs.org/download/release/v12.16.0/node-v12.16.0-headers.tar.gz",
-      "sourceUrl": "https://nodejs.org/download/release/v12.16.0/node-v12.16.0.tar.gz"
-    },
-    "osName": "Darwin",
-    "osRelease": "22.6.0",
-    "osVersion": "Darwin Kernel Version 22.6.0: Wed Jul  5 22:21:56 PDT 2023; root:xnu-8796.141.3~6/RELEASE_X86_64",
-    "osMachine": "x86_64",
-    "cpus": [
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 19491620,
-        "nice": 0,
-        "sys": 10530730,
-        "idle": 128029940,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 1041080,
-        "nice": 0,
-        "sys": 832510,
-        "idle": 156711500,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 17230140,
-        "nice": 0,
-        "sys": 7308720,
-        "idle": 133550270,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 1101080,
-        "nice": 0,
-        "sys": 813690,
-        "idle": 156678120,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 14246350,
-        "nice": 0,
-        "sys": 5654800,
-        "idle": 138314740,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 1142930,
-        "nice": 0,
-        "sys": 790770,
-        "idle": 156668310,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 12228700,
-        "nice": 0,
-        "sys": 4533960,
-        "idle": 141551990,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 1165420,
-        "nice": 0,
-        "sys": 773220,
-        "idle": 156669740,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 9805920,
-        "nice": 0,
-        "sys": 3386750,
-        "idle": 145216650,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 1177080,
-        "nice": 0,
-        "sys": 759980,
-        "idle": 156674610,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 8806950,
-        "nice": 0,
-        "sys": 2889820,
-        "idle": 146774530,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 1166250,
-        "nice": 0,
-        "sys": 737790,
-        "idle": 156711830,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 7865920,
-        "nice": 0,
-        "sys": 2438950,
-        "idle": 148205800,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 1155370,
-        "nice": 0,
-        "sys": 719580,
-        "idle": 156743860,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 7478040,
-        "nice": 0,
-        "sys": 2248690,
-        "idle": 148810130,
-        "irq": 0
-      },
-      {
-        "model": "Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz",
-        "speed": 2300,
-        "user": 1131070,
-        "nice": 0,
-        "sys": 695000,
-        "idle": 156797450,
-        "irq": 0
-      }
-    ],
-    "networkInterfaces": [
-      {
-        "name": "lo0",
-        "internal": true,
-        "mac": "00:00:00:00:00:00",
-        "address": "127.0.0.1",
-        "netmask": "255.0.0.0",
-        "family": "IPv4"
-      },
-      {
-        "name": "lo0",
-        "internal": true,
-        "mac": "00:00:00:00:00:00",
-        "address": "::1",
-        "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
-        "family": "IPv6",
-        "scopeid": 0
-      },
-      {
-        "name": "lo0",
-        "internal": true,
-        "mac": "00:00:00:00:00:00",
-        "address": "fe80::1",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 1
-      },
-      {
-        "name": "en5",
-        "internal": false,
-        "mac": "ac:de:48:00:11:22",
-        "address": "fe80::aede:48ff:fe00:1122",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 4
-      },
-      {
-        "name": "en0",
-        "internal": false,
-        "mac": "88:66:5a:29:28:77",
-        "address": "fe80::1467:9dad:c3d1:73a7",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 6
-      },
-      {
-        "name": "en0",
-        "internal": false,
-        "mac": "88:66:5a:29:28:77",
-        "address": "10.38.21.14",
-        "netmask": "255.255.192.0",
-        "family": "IPv4"
-      },
-      {
-        "name": "en0",
-        "internal": false,
-        "mac": "88:66:5a:29:28:77",
-        "address": "fde6:e673:d766:4b72:1481:e813:77a2:da34",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 0
-      },
-      {
-        "name": "en0",
-        "internal": false,
-        "mac": "88:66:5a:29:28:77",
-        "address": "2620:6e:6000:3100:148e:201a:1a33:145d",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 0
-      },
-      {
-        "name": "en0",
-        "internal": false,
-        "mac": "88:66:5a:29:28:77",
-        "address": "2620:6e:6000:3100:5c7f:8d3f:76ba:694b",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 0
-      },
-      {
-        "name": "awdl0",
-        "internal": false,
-        "mac": "12:df:77:23:4c:4e",
-        "address": "fe80::10df:77ff:fe23:4c4e",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 9
-      },
-      {
-        "name": "llw0",
-        "internal": false,
-        "mac": "12:df:77:23:4c:4e",
-        "address": "fe80::10df:77ff:fe23:4c4e",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 12
-      },
-      {
-        "name": "utun0",
-        "internal": false,
-        "mac": "00:00:00:00:00:00",
-        "address": "fe80::6616:29f1:d83d:2f8d",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 14
-      },
-      {
-        "name": "utun1",
-        "internal": false,
-        "mac": "00:00:00:00:00:00",
-        "address": "fe80::b4dc:ec9a:5d3d:18ab",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 15
-      },
-      {
-        "name": "utun2",
-        "internal": false,
-        "mac": "00:00:00:00:00:00",
-        "address": "fe80::ce81:b1c:bd2c:69e",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 16
-      },
-      {
-        "name": "utun3",
-        "internal": false,
-        "mac": "00:00:00:00:00:00",
-        "address": "fe80::bd9f:d9d8:700d:2011",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 17
-      },
-      {
-        "name": "utun4",
-        "internal": false,
-        "mac": "00:00:00:00:00:00",
-        "address": "fe80::9a49:1de7:e220:2bb",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 18
-      },
-      {
-        "name": "utun5",
-        "internal": false,
-        "mac": "00:00:00:00:00:00",
-        "address": "fe80::93fe:38cf:1393:3191",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 19
-      },
-      {
-        "name": "utun6",
-        "internal": false,
-        "mac": "00:00:00:00:00:00",
-        "address": "fe80::781d:651c:69a7:3d42",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 20
-      },
-      {
-        "name": "utun7",
-        "internal": false,
-        "mac": "00:00:00:00:00:00",
-        "address": "fe80::b39:d094:2a6d:e0c5",
-        "netmask": "ffff:ffff:ffff:ffff::",
-        "family": "IPv6",
-        "scopeid": 21
-      }
-    ],
-    "host": "sarahs-mbp-2.devices.brown.edu"
-  },
-  "javascriptStack": {
-    "message": "No stack.",
-    "stack": [
-      "Unavailable."
-    ]
-  },
-  "nativeStack": [
-    {
-      "pc": "0x000000010015c8ca",
-      "symbol": "report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, v8::Local<v8::String>) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    },
-    {
-      "pc": "0x0000000100080f3e",
-      "symbol": "node::OnFatalError(char const*, char const*) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    },
-    {
-      "pc": "0x0000000100185467",
-      "symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    },
-    {
-      "pc": "0x0000000100185403",
-      "symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    },
-    {
-      "pc": "0x000000010030b5f5",
-      "symbol": "v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    },
-    {
-      "pc": "0x000000010030ccc4",
-      "symbol": "v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    },
-    {
-      "pc": "0x0000000100309b37",
-      "symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    },
-    {
-      "pc": "0x0000000100307afd",
-      "symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    },
-    {
-      "pc": "0x00000001003132ba",
-      "symbol": "v8::internal::Heap::AllocateRawWithLightRetry(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    },
-    {
-      "pc": "0x0000000100313341",
-      "symbol": "v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    },
-    {
-      "pc": "0x00000001002e065b",
-      "symbol": "v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    },
-    {
-      "pc": "0x0000000100618a18",
-      "symbol": "v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    },
-    {
-      "pc": "0x0000000100950c19",
-      "symbol": "Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/Users/sarah/.nvm/versions/node/v12.16.0/bin/node]"
-    }
-  ],
-  "javascriptHeap": {
-    "totalMemory": 2154991616,
-    "totalCommittedMemory": 2153815368,
-    "usedMemory": 2136222480,
-    "availableMemory": 51343040,
-    "memoryLimit": 2197815296,
-    "heapSpaces": {
-      "read_only_space": {
-        "memorySize": 262144,
-        "committedMemory": 33088,
-        "capacity": 32808,
-        "used": 32808,
-        "available": 0
-      },
-      "new_space": {
-        "memorySize": 8388608,
-        "committedMemory": 8388528,
-        "capacity": 4189824,
-        "used": 69544,
-        "available": 4120280
-      },
-      "old_space": {
-        "memorySize": 1946566656,
-        "committedMemory": 1946346224,
-        "capacity": 1939183704,
-        "used": 1938974448,
-        "available": 209256
-      },
-      "code_space": {
-        "memorySize": 14323712,
-        "committedMemory": 13779552,
-        "capacity": 12481792,
-        "used": 12481792,
-        "available": 0
-      },
-      "map_space": {
-        "memorySize": 1576960,
-        "committedMemory": 1394440,
-        "capacity": 1280720,
-        "used": 1280720,
-        "available": 0
-      },
-      "large_object_space": {
-        "memorySize": 183824384,
-        "committedMemory": 183824384,
-        "capacity": 183380384,
-        "used": 183380384,
-        "available": 0
-      },
-      "code_large_object_space": {
-        "memorySize": 49152,
-        "committedMemory": 49152,
-        "capacity": 2784,
-        "used": 2784,
-        "available": 0
-      },
-      "new_large_object_space": {
-        "memorySize": 0,
-        "committedMemory": 0,
-        "capacity": 4189824,
-        "used": 0,
-        "available": 4189824
-      }
-    }
-  },
-  "resourceUsage": {
-    "userCpuSeconds": 306.691,
-    "kernelCpuSeconds": 15.3869,
-    "cpuConsumptionPercent": 11.2772,
-    "maxRss": 2231285841920,
-    "pageFaults": {
-      "IORequired": 110,
-      "IONotRequired": 2487718
-    },
-    "fsActivity": {
-      "reads": 0,
-      "writes": 0
-    }
-  },
-  "libuv": [
-  ],
-  "environmentVariables": {
-    "npm_config_save_dev": "",
-    "npm_config_legacy_bundling": "",
-    "npm_config_dry_run": "",
-    "npm_package_dependencies_request": "^2.88.2",
-    "npm_package_dependencies_express_flash": "0.0.2",
-    "npm_package_dependencies__fortawesome_fontawesome_svg_core": "^6.3.0",
-    "NVM_INC": "/Users/sarah/.nvm/versions/node/v12.16.0/include/node",
-    "npm_config_viewer": "man",
-    "npm_config_only": "",
-    "npm_config_commit_hooks": "true",
-    "npm_config_browser": "",
-    "npm_package_gitHead": "5754c1c056ec62e17b5d7badddc4a8fc3637e09f",
-    "npm_package_dependencies_webpack_dev_middleware": "^5.3.1",
-    "npm_package_dependencies_webpack_cli": "^4.10.0",
-    "npm_package_devDependencies_prettier": "^2.7.1",
-    "npm_package_devDependencies_awesome_typescript_loader": "^5.2.1",
-    "npm_package_devDependencies__types_archiver": "^3.1.1",
-    "npm_config_also": "",
-    "npm_package_dependencies_react_jsx_parser": "^1.29.0",
-    "npm_package_dependencies_mongoose": "^5.13.14",
-    "npm_package_dependencies_connect_flash": "^0.1.1",
-    "npm_package_browser_child_process": "false",
-    "npm_config_sign_git_commit": "",
-    "npm_config_rollback": "true",
-    "npm_package_dependencies_material_ui": "^0.20.2",
-    "npm_package_devDependencies__types_sharp": "^0.23.1",
-    "npm_package_devDependencies__types_passport_local": "^1.0.34",
-    "npm_package_devDependencies__types_dotenv": "^6.1.1",
-    "npm_package_devDependencies__types_cookie_parser": "^1.4.2",
-    "TERM_PROGRAM": "Apple_Terminal",
-    "NODE": "/Users/sarah/.nvm/versions/node/v12.16.0/bin/node",
-    "npm_config_usage": "",
-    "npm_config_audit": "true",
-    "npm_package_dependencies_reveal_js": "^4.3.0",
-    "npm_package_dependencies_process": "^0.11.10",
-    "npm_package_dependencies_pdfjs": "^2.4.7",
-    "npm_package_dependencies_html_to_image": "^0.1.3",
-    "npm_package_devDependencies_file_loader": "^3.0.1",
-    "npm_package_devDependencies__types_express_flash": "0.0.0",
-    "npm_package_scripts_monitor": "cross-env MONITORED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node src/server/index.ts",
-    "INIT_CWD": "/Users/sarah/Desktop/dash/Dash-Web",
-    "npm_package_dependencies_rehype_raw": "^6.1.1",
-    "npm_package_dependencies_react_audio_waveform": "0.0.5",
-    "npm_package_dependencies_path_browserify": "^1.0.1",
-    "npm_package_dependencies_nodemailer": "^5.1.1",
-    "npm_package_dependencies_axios": "^0.19.2",
-    "npm_package_devDependencies_typescript": "^4.7.4",
-    "NVM_CD_FLAGS": "-q",
-    "npm_config_globalignorefile": "/Users/sarah/.nvm/versions/node/v12.16.0/etc/npmignore",
-    "npm_package_dependencies_react_grid_layout": "^1.3.4",
-    "npm_package_dependencies_prosemirror_find_replace": "^0.9.0",
-    "npm_package_dependencies_normalize_css": "^8.0.1",
-    "npm_package_devDependencies_mocha": "^5.2.0",
-    "npm_package_devDependencies__types_express_session": "^1.17.5",
-    "SHELL": "/bin/zsh",
-    "TERM": "xterm-256color",
-    "npm_config_shell": "/bin/zsh",
-    "npm_config_maxsockets": "50",
-    "npm_config_init_author_url": "",
-    "npm_package_dependencies_prosemirror_dev_tools": "^3.1.0",
-    "npm_package_dependencies_p_limit": "^2.2.0",
-    "npm_package_dependencies_bson": "^4.6.1",
-    "npm_package_dependencies__types_dom_speech_recognition": "0.0.1",
-    "npm_package_dependencies__emotion_styled": "^11.11.0",
-    "npm_package_devDependencies_style_loader": "^0.23.1",
-    "npm_package_devDependencies__types_react_datepicker": "^3.1.8",
-    "npm_config_shrinkwrap": "true",
-    "npm_config_parseable": "",
-    "npm_config_metrics_registry": "https://registry.npmjs.org/",
-    "npm_package_dependencies_xregexp": "^4.4.1",
-    "npm_package_dependencies_shelljs": "^0.8.5",
-    "npm_package_dependencies_bezier_curve": "^1.0.0",
-    "npm_package_dependencies__mui_icons_material": "^5.11.16",
-    "npm_package_devDependencies_tslint": "^5.20.1",
-    "npm_package_devDependencies__types_react_transition_group": "^4.4.5",
-    "npm_package_scripts_tsc": "tsc",
-    "TMPDIR": "/var/folders/yk/p_39q8jn673c5p8_66mcxm7r0000gn/T/",
-    "npm_config_timing": "",
-    "npm_config_init_license": "ISC",
-    "npm_package_dependencies_socket_io": "^2.5.0",
-    "npm_package_dependencies_probe_image_size": "^4.0.0",
-    "npm_package_dependencies_canvas": "^2.9.3",
-    "npm_package_dependencies__hig_theme_data": "^2.23.1",
-    "npm_package_devDependencies__types_react_select": "^3.1.2",
-    "npm_package_devDependencies__types_prosemirror_model": "^1.16.1",
-    "CONDA_SHLVL": "1",
-    "npm_config_if_present": "",
-    "npm_package_dependencies_typescript_collections": "^1.3.3",
-    "npm_package_dependencies_rimraf": "^3.0.0",
-    "npm_package_dependencies_react_autosuggest": "^9.4.3",
-    "npm_package_dependencies_flexlayout_react": "^0.3.11",
-    "npm_package_dependencies_find_in_files": "^0.5.0",
-    "npm_package_devDependencies__types_chai": "^4.3.0",
-    "CONDA_PROMPT_MODIFIER": "(base) ",
-    "TERM_PROGRAM_VERSION": "447",
-    "npm_package_dependencies_prosemirror_inputrules": "^1.1.3",
-    "npm_package_dependencies_bcrypt_nodejs": "0.0.3",
-    "npm_package_dependencies_async": "^2.6.2",
-    "npm_config_sign_git_tag": "",
-    "npm_config_init_author_email": "",
-    "npm_config_cache_max": "Infinity",
-    "npm_package_dependencies_uuid": "^3.4.0",
-    "npm_package_dependencies_supercluster": "^7.1.4",
-    "npm_package_dependencies_remark_gfm": "^3.0.1",
-    "npm_package_dependencies_connect_mongo": "^2.0.3",
-    "npm_package_dependencies_browser_assert": "^1.2.1",
-    "npm_package_devDependencies_sass_loader": "^7.3.1",
-    "npm_package_scripts_start_release_debug": "cross-env RELEASE=true USE_AZURE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --inspect -- src/server/index.ts",
-    "npm_config_preid": "",
-    "npm_config_long": "",
-    "npm_config_local_address": "",
-    "npm_config_git_tag_version": "true",
-    "npm_config_cert": "",
-    "npm_package_dependencies_js_datepicker": "^4.6.6",
-    "npm_package_devDependencies__types_webpack_hot_middleware": "^2.25.6",
-    "npm_package_devDependencies__types_mongodb": "^3.6.20",
-    "npm_package_devDependencies__types_mocha": "^5.2.6",
-    "TERM_SESSION_ID": "4AACD142-8780-44F4-A4FA-BB53E1BF6081",
-    "npm_config_registry": "https://registry.npmjs.org/",
-    "npm_config_noproxy": "",
-    "npm_config_fetch_retries": "2",
-    "npm_package_dependencies_react_compound_slider": "^2.5.0",
-    "npm_package_dependencies_prosemirror_history": "^1.2.0",
-    "npm_package_devDependencies__types_react_color": "^2.17.6",
-    "npm_package_devDependencies__types_google_maps_react": "^2.0.5",
-    "npm_package_devDependencies__types_color": "^3.0.3",
-    "npm_package_dependencies_react_dom": "^18.2.0",
-    "npm_package_dependencies_passport_local": "^1.0.0",
-    "npm_package_dependencies__octokit_core": "^4.0.4",
-    "npm_package_devDependencies__types_async": "^2.4.1",
-    "npm_package_scripts_debug": "cross-env  NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts",
-    "npm_package_scripts_oldstart": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug -- src/server/index.ts",
-    "npm_config_versions": "",
-    "npm_config_message": "%s",
-    "npm_config_key": "",
-    "npm_package_readmeFilename": "README.md",
-    "npm_package_dependencies_react_refresh_typescript": "^2.0.7",
-    "npm_package_dependencies_react_dropzone": "^14.2.3",
-    "npm_package_dependencies_image_size": "^0.7.5",
-    "npm_package_dependencies_html_to_text": "^5.1.1",
-    "npm_package_dependencies_express_validator": "^5.3.1",
-    "npm_package_devDependencies_eslint_plugin_jsx_a11y": "^6.6.0",
-    "npm_package_node_child_process": "empty",
-    "npm_package_dependencies_react_resizable_rotatable_draggable": "^0.2.0",
-    "npm_package_dependencies_got": "^12.0.1",
-    "npm_package_dependencies__types_d3_color": "^2.0.3",
-    "npm_package_devDependencies_webpack": "^5.69.1",
-    "npm_package_devDependencies__types_nodemailer": "^4.6.6",
-    "npm_package_description": "Install Node.js, then, from the project directory, run",
-    "NVM_DIR": "/Users/sarah/.nvm",
-    "USER": "sarah",
-    "npm_package_dependencies__types_d3_scale": "^3.3.2",
-    "npm_package_devDependencies_dotenv": "^8.6.0",
-    "npm_package_devDependencies__types_react": "^18.0.15",
-    "npm_package_devDependencies__types_prosemirror_transform": "^1.1.5",
-    "npm_package_devDependencies__types_prosemirror_history": "^1.0.3",
-    "npm_package_dependencies_readline": "^1.3.0",
-    "npm_package_dependencies__types_supercluster": "^7.1.0",
-    "npm_package_dependencies__azure_storage_blob": "^12.14.0",
-    "npm_config_globalconfig": "/Users/sarah/.nvm/versions/node/v12.16.0/etc/npmrc",
-    "npm_package_dependencies_depcheck": "^0.9.2",
-    "npm_package_dependencies__types_web": "0.0.53",
-    "CONDA_EXE": "/Users/sarah/miniconda3/bin/conda",
-    "npm_config_prefer_online": "",
-    "npm_config_logs_max": "10",
-    "npm_config_always_auth": "",
-    "npm_package_dependencies_react_icons": "^4.3.1",
-    "npm_package_dependencies_passport_google_oauth20": "^2.0.0",
-    "npm_package_devDependencies_webpack_dev_server": "^3.11.3",
-    "npm_package_devDependencies__types_brotli": "^1.3.1",
-    "npm_package_dependencies_url_loader": "^1.1.2",
-    "npm_package_dependencies_stream_browserify": "^3.0.0",
-    "npm_package_dependencies_prosemirror_transform": "^1.3.4",
-    "npm_package_dependencies_lodash": "^4.17.21",
-    "npm_package_dependencies_i": "^0.3.7",
-    "npm_package_devDependencies_tslint_loader": "^3.6.0",
-    "SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.dBaRxB6a53/Listeners",
-    "npm_package_dependencies_words_to_numbers": "^1.5.1",
-    "npm_package_dependencies_valid_url": "^1.0.9",
-    "npm_package_dependencies_styled_components": "^4.4.1",
-    "npm_package_dependencies_csv_parser": "^3.0.0",
-    "npm_package_dependencies_class_transformer": "^0.2.0",
-    "npm_package_devDependencies_eslint": "^8.36.0",
-    "npm_package_devDependencies__types_prosemirror_inputrules": "^1.0.4",
-    "npm_package_devDependencies__types_express": "^4.17.13",
-    "__CF_USER_TEXT_ENCODING": "0x1F5:0x0:0x0",
-    "npm_execpath": "/Users/sarah/.nvm/versions/node/v12.16.0/lib/node_modules/npm/bin/npm-cli.js",
-    "npm_config_global_style": "",
-    "npm_config_cache_lock_retries": "10",
-    "npm_package_dependencies_wikijs": "^6.3.3",
-    "npm_package_dependencies_bluebird": "^3.7.2",
-    "npm_package_devDependencies__types_react_typist": "^2.0.3",
-    "npm_config_update_notifier": "true",
-    "npm_config_cafile": "",
-    "npm_package_dependencies_util": "^0.12.4",
-    "npm_package_dependencies_raw_loader": "^1.0.0",
-    "npm_package_dependencies_https_browserify": "^1.0.0",
-    "npm_package_dependencies_brotli": "^1.3.3",
-    "npm_package_dependencies__mui_material": "^5.13.1",
-    "npm_package_dependencies__fortawesome_react_fontawesome": "^0.2.0",
-    "npm_package_devDependencies__types_passport_google_oauth20": "^2.0.11",
-    "npm_package_dependencies_cors": "^2.8.5",
-    "npm_package_dependencies_bezier_js": "^4.1.1",
-    "npm_package_dependencies__fortawesome_free_brands_svg_icons": "^6.3.0",
-    "npm_config_heading": "npm",
-    "npm_config_audit_level": "low",
-    "npm_package_dependencies_chrome": "^0.1.0",
-    "npm_package_dependencies__react_three_fiber": "^6.2.3",
-    "npm_package_devDependencies_eslint_plugin_prettier": "^4.2.1",
-    "npm_package_devDependencies_copy_webpack_plugin": "^4.6.0",
-    "npm_package_devDependencies__types_react_measure": "^2.0.8",
-    "npm_package_devDependencies__types_react_dom": "^18.0.6",
-    "npm_package_devDependencies__types_mobile_detect": "^1.3.4",
-    "_CE_CONDA": "",
-    "npm_config_searchlimit": "20",
-    "npm_config_read_only": "",
-    "npm_config_offline": "",
-    "npm_config_fetch_retry_mintimeout": "10000",
-    "npm_package_dependencies_react_typist": "^2.0.5",
-    "npm_package_dependencies_mobx_react_devtools": "^6.1.1",
-    "npm_package_dependencies_md5_file": "^5.0.0",
-    "npm_package_dependencies_forever_agent": "^0.6.1",
-    "npm_package_devDependencies__types_xregexp": "^4.4.0",
-    "npm_package_devDependencies__types_typescript": "^2.0.0",
-    "npm_package_devDependencies__types_request": "^2.48.8",
-    "npm_package_devDependencies__types_prosemirror_commands": "^1.0.4",
-    "npm_config_json": "",
-    "npm_config_access": "",
-    "npm_config_argv": "{\"remain\":[],\"cooked\":[\"start\"],\"original\":[\"start\"]}",
-    "npm_package_dependencies__fortawesome_free_solid_svg_icons": "^6.3.0",
-    "npm_package_devDependencies__types_socket_io": "^2.1.13",
-    "PATH": "/Users/sarah/.nvm/versions/node/v12.16.0/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/sarah/Desktop/dash/Dash-Web/node_modules/.bin:/Users/sarah/.nvm/versions/node/v12.16.0/bin:/Users/sarah/miniconda3/bin:/Users/sarah/miniconda3/condabin:/Users/sarah/.elan/bin:/Library/Frameworks/Python.framework/Versions/3.9/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin",
-    "npm_config_allow_same_version": "",
-    "npm_package_dependencies_webrtc_adapter": "^7.7.1",
-    "npm_package_dependencies_react_reveal": "^1.2.2",
-    "npm_package_dependencies_prosemirror_schema_list": "^1.1.6",
-    "npm_package_dependencies__material_ui_core": "^4.12.3",
-    "npm_package_devDependencies__types_rimraf": "^2.0.5",
-    "npm_package_devDependencies__types_connect_flash": "0.0.34",
-    "npm_config_https_proxy": "",
-    "npm_config_engine_strict": "",
-    "npm_config_description": "true",
-    "npm_package_dependencies_pug": "^2.0.4",
-    "npm_package_dependencies_prosemirror_keymap": "^1.1.5",
-    "npm_package_dependencies_pdfjs_dist": "^2.14.305",
-    "npm_package_dependencies_mobile_detect": "^1.4.5",
-    "npm_package_dependencies_image_size_stream": "^1.1.0",
-    "npm_package_dependencies_golden_layout": "^1.5.9",
-    "npm_package_dependencies_child_process": "^1.0.2",
-    "npm_package_dependencies__types_d3_axis": "^2.1.3",
-    "_": "/Users/sarah/Desktop/dash/Dash-Web/node_modules/.bin/cross-env",
-    "LaunchInstanceID": "16109DB5-CDB2-454A-9613-8F6F534702AC",
-    "npm_config_userconfig": "/Users/sarah/.npmrc",
-    "npm_config_init_module": "/Users/sarah/.npm-init.js",
-    "npm_package_dependencies__react_google_maps_api": "^2.7.0",
-    "CONDA_PREFIX": "/Users/sarah/miniconda3",
-    "__CFBundleIdentifier": "com.apple.Terminal",
-    "npm_config_cidr": "",
-    "npm_package_dependencies_puppeteer": "^3.3.0",
-    "npm_package_dependencies_prosemirror_view": "^1.26.5",
-    "npm_package_dependencies_mongodb": "^3.7.3",
-    "npm_package_dependencies_google_auth_library": "^4.2.4",
-    "npm_package_dependencies_bootstrap": "^4.6.1",
-    "npm_package_devDependencies_eslint_config_airbnb": "^19.0.4",
-    "PWD": "/Users/sarah/desktop/dash/dash-web",
-    "npm_config_user": "501",
-    "npm_config_node_version": "12.16.0",
-    "npm_package_dependencies_node_sass": "^4.14.1",
-    "npm_package_dependencies_howler": "^2.2.3",
-    "npm_package_dependencies_expressjs": "^1.0.1",
-    "npm_package_dependencies_core_js": "^3.28.0",
-    "npm_package_dependencies_browndash_components": "^0.1.36",
-    "npm_package_devDependencies_eslint_plugin_react_hooks": "^4.6.0",
-    "npm_package_devDependencies__types_lodash": "^4.14.179",
-    "JAVA_HOME": "/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home",
-    "npm_lifecycle_event": "start",
-    "npm_package_dependencies_react_table": "^6.11.5",
-    "npm_package_dependencies_react_loading": "^2.0.3",
-    "npm_package_dependencies_mobx": "^5.15.7",
-    "npm_package_dependencies_babel": "^6.23.0",
-    "npm_package_devDependencies_jsdom": "^15.2.1",
-    "npm_package_devDependencies_chai": "^4.3.6",
-    "npm_config_save": "true",
-    "npm_config_ignore_prepublish": "",
-    "npm_config_editor": "vi",
-    "npm_config_auth_type": "legacy",
-    "npm_package_dependencies_npm": "^6.14.18",
-    "npm_package_dependencies_node_stream_zip": "^1.15.0",
-    "npm_package_dependencies_image_data_uri": "^2.0.1",
-    "npm_package_scripts_start_release": "cross-env RELEASE=true USE_AZURE=false NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
-    "npm_package_name": "dash",
-    "LANG": "en_US.UTF-8",
-    "npm_config_tag": "latest",
-    "npm_config_script_shell": "",
-    "npm_package_dependencies_query_string": "^6.14.1",
-    "npm_package_dependencies_mobx_utils": "^5.6.2",
-    "npm_package_dependencies_file_saver": "^2.0.5",
-    "npm_package_dependencies_body_parser": "^1.19.2",
-    "npm_package_dependencies__types_reveal": "^3.3.33",
-    "npm_package_devDependencies_eslint_plugin_import": "^2.26.0",
-    "npm_package_devDependencies__types_prosemirror_view": "^1.23.1",
-    "npm_config_progress": "true",
-    "npm_config_global": "",
-    "npm_config_before": "",
-    "npm_package_dependencies_xoauth2": "^1.2.0",
-    "npm_package_dependencies_standard_http_error": "^2.0.1",
-    "npm_package_dependencies_react_loader_spinner": "^5.3.4",
-    "npm_package_dependencies_http_browserify": "^1.7.0",
-    "npm_package_dependencies__types_d3_selection": "^2.0.1",
-    "npm_package_dependencies__hig_flyout": "^1.3.1",
-    "npm_package_devDependencies_fork_ts_checker_webpack_plugin": "^1.6.0",
-    "npm_package_scripts_build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production",
-    "npm_package_scripts_start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
-    "npm_config_searchstaleness": "900",
-    "npm_config_optional": "true",
-    "npm_config_ham_it_up": "",
-    "npm_package_dependencies_sharp": "^0.23.4",
-    "npm_package_dependencies_rc_switch": "^1.9.2",
-    "npm_package_dependencies_googlephotos": "^0.2.5",
-    "npm_package_dependencies_exifr": "^7.1.3",
-    "npm_package_dependencies__types_google_maps": "^3.2.3",
-    "npm_package_dependencies__types_bezier_js": "^4.1.0",
-    "npm_package_dependencies__ffmpeg_core": "0.10.0",
-    "npm_package_devDependencies_ts_loader": "^5.3.3",
-    "npm_package_devDependencies__types_bcrypt_nodejs": "0.0.30",
-    "XPC_FLAGS": "0x0",
-    "npm_config_save_prod": "",
-    "npm_config_force": "",
-    "npm_config_bin_links": "true",
-    "npm_package_devDependencies__types_youtube": "0.0.39",
-    "npm_config_searchopts": "",
-    "npm_package_dependencies_react_beautiful_dnd": "^13.1.0",
-    "npm_package_dependencies_jszip": "^3.7.1",
-    "npm_package_dependencies_csv_stringify": "^6.3.0",
-    "npm_package_devDependencies__types_react_icons": "^3.0.0",
-    "npm_config_node_gyp": "/Users/sarah/.nvm/versions/node/v12.16.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js",
-    "npm_config_depth": "Infinity",
-    "npm_package_dependencies_google_maps_react": "^2.0.6",
-    "npm_package_dependencies_express_session": "^1.17.2",
-    "npm_package_devDependencies_eslint_plugin_node": "^11.1.0",
-    "npm_package_devDependencies_eslint_config_prettier": "^8.5.0",
-    "npm_package_main": "index.js",
-    "npm_config_sso_poll_frequency": "500",
-    "npm_config_rebuild_bundle": "true",
-    "npm_package_dependencies_chart_js": "^3.8.0",
-    "npm_package_dependencies__emotion_react": "^11.11.0",
-    "npm_package_devDependencies__types_prosemirror_menu": "^1.0.6",
-    "npm_package_devDependencies__types_prosemirror_keymap": "^1.0.4",
-    "npm_package_devDependencies__types_pdfjs_dist": "^2.10.378",
-    "npm_package_devDependencies__types_exif": "^0.6.3",
-    "npm_package_version": "1.0.0",
-    "_CE_M": "",
-    "XPC_SERVICE_NAME": "0",
-    "npm_config_unicode": "true",
-    "npm_package_dependencies_typescript_language_server": "^0.4.0",
-    "npm_package_dependencies_prosemirror_model": "^1.18.1",
-    "npm_package_dependencies__ffmpeg_ffmpeg": "0.10.0",
-    "SHLVL": "2",
-    "HOME": "/Users/sarah",
-    "npm_config_fetch_retry_maxtimeout": "60000",
-    "npm_package_dependencies_request_promise": "^4.2.6",
-    "npm_package_dependencies_react_markdown": "^8.0.3",
-    "npm_package_dependencies__hig_theme_context": "^2.1.3",
-    "npm_package_devDependencies__types_react_autosuggest": "^9.3.14",
-    "npm_package_devDependencies__types_mongoose": "^5.11.97",
-    "npm_package_devDependencies__types_d3": "^7.4.0",
-    "npm_package_devDependencies__types_animejs": "^2.0.2",
-    "npm_package_scripts_test": "mocha -r ts-node/register test/**/*.ts",
-    "npm_config_tag_version_prefix": "v",
-    "npm_config_strict_ssl": "true",
-    "npm_config_sso_type": "oauth",
-    "npm_config_scripts_prepend_node_path": "warn-only",
-    "npm_config_save_prefix": "^",
-    "npm_config_loglevel": "notice",
-    "npm_config_ca": "",
-    "npm_package_dependencies_three": "^0.127.0",
-    "npm_package_dependencies_openai": "^3.2.1",
-    "npm_package_dependencies_mobx_react": "^5.4.4",
-    "npm_package_dependencies_google_translate_api_browser": "^3.0.1",
-    "npm_package_dependencies_cookie_parser": "^1.4.6",
-    "npm_package_dependencies_adm_zip": "^0.4.16",
-    "npm_package_devDependencies_eslint_config_node": "^4.1.0",
-    "npm_config_save_exact": "",
-    "npm_config_group": "20",
-    "npm_config_fetch_retry_factor": "10",
-    "npm_config_dev": "",
-    "npm_package_devDependencies_webpack_hot_middleware": "^2.25.1",
-    "npm_package_devDependencies_cross_env": "^5.2.1",
-    "npm_config_version": "",
-    "npm_config_prefer_offline": "",
-    "npm_config_cache_lock_stale": "60000",
-    "npm_package_devDependencies__types_prosemirror_state": "^1.2.8",
-    "npm_package_devDependencies__types_body_parser": "^1.19.2",
-    "npm_config_otp": "",
-    "npm_config_cache_min": "10",
-    "npm_package_dependencies_react_color": "^2.19.3",
-    "npm_package_dependencies_d3": "^7.6.1",
-    "npm_package_devDependencies_ts_node": "^10.9.1",
-    "npm_package_devDependencies__types_react_grid_layout": "^1.3.2",
-    "npm_config_searchexclude": "",
-    "npm_config_cache": "/Users/sarah/.npm",
-    "npm_package_dependencies_tough_cookie": "^4.0.0",
-    "npm_package_dependencies_googleapis": "^40.0.0",
-    "npm_package_devDependencies__types_valid_url": "^1.0.3",
-    "npm_package_devDependencies__types_passport": "^1.0.9",
-    "npm_package_devDependencies__types_adm_zip": "^0.4.34",
-    "CONDA_PYTHON_EXE": "/Users/sarah/miniconda3/bin/python",
-    "LOGNAME": "sarah",
-    "npm_lifecycle_script": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
-    "npm_config_color": "true",
-    "npm_package_dependencies_solr_node": "^1.2.1",
-    "npm_package_dependencies_react_transition_group": "^4.4.2",
-    "npm_package_dependencies_iink_js": "^1.5.4",
-    "npm_package_dependencies_html_webpack_plugin": "^5.5.0",
-    "npm_config_proxy": "",
-    "npm_config_package_lock": "true",
-    "npm_package_dependencies_prosemirror_state": "^1.4.1",
-    "npm_package_dependencies_nodemon": "^1.19.4",
-    "npm_package_dependencies_function_plot": "^1.22.8",
-    "npm_package_dependencies_equation_editor_react": "github:bobzel/equation-editor-react#useLocally",
-    "npm_package_devDependencies__types_socket_io_parser": "^3.0.0",
-    "CLASSPATH": "/Users/sarah/Downloads/cs15/*:.",
-    "npm_config_package_lock_only": "",
-    "npm_config_fund": "true",
-    "npm_package_dependencies_react": "^18.2.0",
-    "npm_package_dependencies_bingmaps_react": "^1.2.10",
-    "npm_package_devDependencies_scss_loader": "0.0.1",
-    "npm_package_devDependencies__types_cookie_session": "^2.0.44",
-    "npm_config_save_optional": "",
-    "npm_package_dependencies_textarea_caret": "^3.1.0",
-    "npm_package_dependencies_react_measure": "^2.5.2",
-    "npm_package_dependencies_exif": "^0.6.0",
-    "NVM_BIN": "/Users/sarah/.nvm/versions/node/v12.16.0/bin",
-    "CONDA_DEFAULT_ENV": "base",
-    "npm_config_ignore_scripts": "",
-    "npm_config_user_agent": "npm/6.14.7 node/v12.16.0 darwin x64",
-    "npm_package_dependencies_react_resizable": "^1.11.1",
-    "npm_package_dependencies_prosemirror_commands": "^1.2.1",
-    "npm_package_dependencies_memorystream": "^0.3.1",
-    "npm_package_dependencies_formidable": "1.2.1",
-    "npm_package_devDependencies__types_uuid": "^3.4.10",
-    "npm_config_cache_lock_wait": "10000",
-    "npm_package_dependencies_socket_io_client": "^2.5.0",
-    "npm_package_dependencies_recharts": "^2.1.12",
-    "npm_package_dependencies_react_chartjs_2": "^4.3.0",
-    "npm_package_dependencies_fluent_ffmpeg": "^2.1.2",
-    "npm_package_dependencies__types_cors": "^2.8.12",
-    "npm_package_devDependencies__types_node": "^10.17.60",
-    "npm_package_devDependencies__types_file_saver": "^2.0.5",
-    "npm_config_production": "",
-    "npm_package_dependencies_jsonschema": "^1.4.0",
-    "npm_package_dependencies_ffmpeg": "0.0.4",
-    "npm_package_dependencies_cookie_session": "^2.0.0",
-    "npm_package_dependencies_color": "^3.2.1",
-    "npm_package_devDependencies__types_webpack": "^4.41.32",
-    "npm_package_devDependencies__types_request_promise": "^4.1.48",
-    "npm_package_devDependencies__types_prosemirror_schema_list": "^1.0.3",
-    "npm_config_send_metrics": "",
-    "npm_config_save_bundle": "",
-    "npm_package_dependencies_web_request": "^1.0.7",
-    "npm_package_dependencies_react_datepicker": "^3.8.0",
-    "npm_package_dependencies_express": "^4.17.3",
-    "npm_package_dependencies_D": "^1.0.0",
-    "npm_package_dependencies__types_formidable": "1.0.31",
-    "npm_package_devDependencies__types_rc_switch": "^1.9.2",
-    "npm_package_devDependencies__types_prosemirror_dev_tools": "^2.1.0",
-    "npm_package_devDependencies__types_jquery": "^3.5.14",
-    "npm_config_umask": "0022",
-    "npm_config_node_options": "",
-    "npm_config_init_version": "1.0.0",
-    "npm_package_dependencies_https": "^1.0.0",
-    "npm_package_dependencies_array_batcher": "^1.2.3",
-    "npm_package_dependencies__fortawesome_free_regular_svg_icons": "^6.3.0",
-    "npm_package_devDependencies__types_shelljs": "^0.8.11",
-    "npm_package_devDependencies__types_libxmljs": "^0.18.7",
-    "npm_package_devDependencies__types_express_validator": "^3.0.0",
-    "npm_package_devDependencies__types_bluebird": "^3.5.36",
-    "npm_config_init_author_name": "",
-    "npm_config_git": "git",
-    "npm_config_scope": "",
-    "npm_package_dependencies_react_select": "^3.2.0",
-    "npm_package_dependencies_pdf_parse": "^1.1.1",
-    "npm_package_dependencies_colors": "^1.4.0",
-    "npm_package_dependencies_archiver": "^3.1.1",
-    "npm_package_devDependencies_css_loader": "^2.1.1",
-    "npm_package_devDependencies__types_socket_io_client": "^1.4.36",
-    "SECURITYSESSIONID": "186a6",
-    "npm_config_unsafe_perm": "true",
-    "npm_config_tmp": "/var/folders/yk/p_39q8jn673c5p8_66mcxm7r0000gn/T",
-    "npm_config_onload_script": "",
-    "npm_package_dependencies_serializr": "^1.5.4",
-    "npm_package_dependencies_fit_curve": "^0.1.7",
-    "npm_package_dependencies__webscopeio_react_textarea_autocomplete": "^4.9.1",
-    "npm_package_dependencies__types_three": "^0.126.2",
-    "npm_package_devDependencies_ts_node_dev": "^2.0.0",
-    "npm_node_execpath": "/Users/sarah/.nvm/versions/node/v12.16.0/bin/node",
-    "npm_config_prefix": "/Users/sarah/.nvm/versions/node/v12.16.0",
-    "npm_config_link": "",
-    "npm_config_format_package_lock": "true",
-    "npm_package_dependencies_passport": "^0.4.0",
-    "npm_package_devDependencies_eslint_plugin_react": "^7.30.1",
-    "npm_package_devDependencies__types_react_table": "^6.8.9",
-    "npm_package_devDependencies__types_react_reconciler": "^0.26.4",
-    "NODE_OPTIONS": "--max_old_space_size=4096",
-    "TS_NODE_DEV": "true",
-    "_CLIENT_OPENAI_KEY": "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",
-    "VIPSHOME": "/usr/local/Cellar/vips/8.8.1",
-    "TYPESCRIPT_PATH": "/Users/sarah/Desktop/dash/Dash-Web/node_modules/typescript/lib/typescript.js",
-    "TSCONFIG": "/Users/sarah/Desktop/dash/Dash-Web/tsconfig.json",
-    "COMPILER_OPTIONS": "{}",
-    "TSLINT": "true",
-    "CONTEXT": "/Users/sarah/Desktop/dash/Dash-Web",
-    "TSLINTAUTOFIX": "false",
-    "ESLINT": "false",
-    "ESLINT_OPTIONS": "{}",
-    "WATCH": "",
-    "WORK_DIVISION": "1",
-    "MEMORY_LIMIT": "2048",
-    "CHECK_SYNTACTIC_ERRORS": "false",
-    "USE_INCREMENTAL_API": "true",
-    "VUE": "false"
-  },
-  "userLimits": {
-    "core_file_size_blocks": {
-      "soft": 0,
-      "hard": "unlimited"
-    },
-    "data_seg_size_kbytes": {
-      "soft": "unlimited",
-      "hard": "unlimited"
-    },
-    "file_size_blocks": {
-      "soft": "unlimited",
-      "hard": "unlimited"
-    },
-    "max_locked_memory_bytes": {
-      "soft": "unlimited",
-      "hard": "unlimited"
-    },
-    "max_memory_size_kbytes": {
-      "soft": "unlimited",
-      "hard": "unlimited"
-    },
-    "open_files": {
-      "soft": 1048575,
-      "hard": "unlimited"
-    },
-    "stack_size_bytes": {
-      "soft": 8388608,
-      "hard": 67104768
-    },
-    "cpu_time_seconds": {
-      "soft": "unlimited",
-      "hard": "unlimited"
-    },
-    "max_user_processes": {
-      "soft": 2784,
-      "hard": 4176
-    },
-    "virtual_memory_kbytes": {
-      "soft": "unlimited",
-      "hard": "unlimited"
-    }
-  },
-  "sharedObjects": [
-    "/Users/sarah/.nvm/versions/node/v12.16.0/bin/node",
-    "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
-    "/usr/lib/libobjc.A.dylib",
-    "/System/Library/PrivateFrameworks/CoreServicesInternal.framework/Versions/A/CoreServicesInternal",
-    "/usr/lib/liboah.dylib",
-    "/usr/lib/libfakelink.dylib",
-    "/usr/lib/libicucore.A.dylib",
-    "/usr/lib/libSystem.B.dylib",
-    "/System/Library/PrivateFrameworks/SoftLinking.framework/Versions/A/SoftLinking",
-    "/usr/lib/libc++abi.dylib",
-    "/usr/lib/libc++.1.dylib",
-    "/usr/lib/system/libcache.dylib",
-    "/usr/lib/system/libcommonCrypto.dylib",
-    "/usr/lib/system/libcompiler_rt.dylib",
-    "/usr/lib/system/libcopyfile.dylib",
-    "/usr/lib/system/libcorecrypto.dylib",
-    "/usr/lib/system/libdispatch.dylib",
-    "/usr/lib/system/libdyld.dylib",
-    "/usr/lib/system/libkeymgr.dylib",
-    "/usr/lib/system/libmacho.dylib",
-    "/usr/lib/system/libquarantine.dylib",
-    "/usr/lib/system/libremovefile.dylib",
-    "/usr/lib/system/libsystem_asl.dylib",
-    "/usr/lib/system/libsystem_blocks.dylib",
-    "/usr/lib/system/libsystem_c.dylib",
-    "/usr/lib/system/libsystem_collections.dylib",
-    "/usr/lib/system/libsystem_configuration.dylib",
-    "/usr/lib/system/libsystem_containermanager.dylib",
-    "/usr/lib/system/libsystem_coreservices.dylib",
-    "/usr/lib/system/libsystem_darwin.dylib",
-    "/usr/lib/system/libsystem_dnssd.dylib",
-    "/usr/lib/system/libsystem_featureflags.dylib",
-    "/usr/lib/system/libsystem_info.dylib",
-    "/usr/lib/system/libsystem_m.dylib",
-    "/usr/lib/system/libsystem_malloc.dylib",
-    "/usr/lib/system/libsystem_networkextension.dylib",
-    "/usr/lib/system/libsystem_notify.dylib",
-    "/usr/lib/system/libsystem_sandbox.dylib",
-    "/usr/lib/system/libsystem_secinit.dylib",
-    "/usr/lib/system/libsystem_kernel.dylib",
-    "/usr/lib/system/libsystem_platform.dylib",
-    "/usr/lib/system/libsystem_pthread.dylib",
-    "/usr/lib/system/libsystem_symptoms.dylib",
-    "/usr/lib/system/libsystem_trace.dylib",
-    "/usr/lib/system/libunwind.dylib",
-    "/usr/lib/system/libxpc.dylib",
-    "/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit",
-    "/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices",
-    "/usr/lib/libDiagnosticMessagesClient.dylib",
-    "/usr/lib/libenergytrace.dylib",
-    "/usr/lib/libbsm.0.dylib",
-    "/usr/lib/libz.1.dylib",
-    "/usr/lib/system/libkxld.dylib",
-    "/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork",
-    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents",
-    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore",
-    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata",
-    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices",
-    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit",
-    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE",
-    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices",
-    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices",
-    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList",
-    "/System/Library/Frameworks/Security.framework/Versions/A/Security",
-    "/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration",
-    "/usr/lib/libapple_nghttp2.dylib",
-    "/usr/lib/libcompression.dylib",
-    "/usr/lib/libnetwork.dylib",
-    "/usr/lib/libsqlite3.dylib",
-    "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation",
-    "/System/Library/Frameworks/Network.framework/Versions/A/Network",
-    "/usr/lib/libCoreEntitlements.dylib",
-    "/System/Library/PrivateFrameworks/MessageSecurity.framework/Versions/A/MessageSecurity",
-    "/System/Library/PrivateFrameworks/ProtocolBuffer.framework/Versions/A/ProtocolBuffer",
-    "/usr/lib/libMobileGestalt.dylib",
-    "/System/Library/PrivateFrameworks/AppleFSCompression.framework/Versions/A/AppleFSCompression",
-    "/usr/lib/libcoretls.dylib",
-    "/usr/lib/libcoretls_cfhelpers.dylib",
-    "/usr/lib/libpam.2.dylib",
-    "/usr/lib/libxar.1.dylib",
-    "/System/Library/PrivateFrameworks/CoreAutoLayout.framework/Versions/A/CoreAutoLayout",
-    "/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration",
-    "/usr/lib/libarchive.2.dylib",
-    "/usr/lib/libxml2.2.dylib",
-    "/usr/lib/liblangid.dylib",
-    "/System/Library/Frameworks/Combine.framework/Versions/A/Combine",
-    "/usr/lib/swift/libswiftCore.dylib",
-    "/usr/lib/swift/libswiftCoreFoundation.dylib",
-    "/usr/lib/swift/libswiftDarwin.dylib",
-    "/usr/lib/swift/libswiftDispatch.dylib",
-    "/usr/lib/swift/libswiftIOKit.dylib",
-    "/usr/lib/swift/libswiftObjectiveC.dylib",
-    "/usr/lib/swift/libswiftXPC.dylib",
-    "/usr/lib/swift/libswift_Concurrency.dylib",
-    "/usr/lib/swift/libswift_StringProcessing.dylib",
-    "/usr/lib/swift/libswiftos.dylib",
-    "/System/Library/PrivateFrameworks/AppleSystemInfo.framework/Versions/A/AppleSystemInfo",
-    "/System/Library/PrivateFrameworks/IOMobileFramebuffer.framework/Versions/A/IOMobileFramebuffer",
-    "/System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface",
-    "/usr/lib/libpcap.A.dylib",
-    "/usr/lib/libdns_services.dylib",
-    "/usr/lib/liblzma.5.dylib",
-    "/usr/lib/libbz2.1.0.dylib",
-    "/usr/lib/libiconv.2.dylib",
-    "/usr/lib/libcharset.1.dylib",
-    "/usr/lib/swift/libswift_RegexParser.dylib",
-    "/usr/lib/libheimdal-asn1.dylib",
-    "/usr/lib/libCheckFix.dylib",
-    "/System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC",
-    "/System/Library/PrivateFrameworks/CoreNLP.framework/Versions/A/CoreNLP",
-    "/System/Library/PrivateFrameworks/MetadataUtilities.framework/Versions/A/MetadataUtilities",
-    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate",
-    "/usr/lib/libmecab.dylib",
-    "/usr/lib/libCRFSuite.dylib",
-    "/usr/lib/libgermantok.dylib",
-    "/usr/lib/libThaiTokenizer.dylib",
-    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage",
-    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib",
-    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib",
-    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib",
-    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib",
-    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib",
-    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib",
-    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib",
-    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib",
-    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib",
-    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib",
-    "/System/Library/PrivateFrameworks/MIL.framework/Versions/A/MIL",
-    "/System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory",
-    "/System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory",
-    "/System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS",
-    "/System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation",
-    "/usr/lib/libutil.dylib",
-    "/System/Library/PrivateFrameworks/InstalledContentLibrary.framework/Versions/A/InstalledContentLibrary",
-    "/System/Library/PrivateFrameworks/CoreServicesStore.framework/Versions/A/CoreServicesStore",
-    "/usr/lib/libapp_launch_measurement.dylib",
-    "/System/Library/PrivateFrameworks/AppleMobileFileIntegrity.framework/Versions/A/AppleMobileFileIntegrity",
-    "/usr/lib/libmis.dylib",
-    "/System/Library/PrivateFrameworks/MobileSystemServices.framework/Versions/A/MobileSystemServices",
-    "/System/Library/PrivateFrameworks/ConfigProfileHelper.framework/Versions/A/ConfigProfileHelper",
-    "/System/Library/PrivateFrameworks/CoreAnalytics.framework/Versions/A/CoreAnalytics",
-    "/System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce",
-    "/System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling",
-    "/usr/lib/libxslt.1.dylib",
-    "/usr/lib/libcmph.dylib",
-    "/System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji",
-    "/System/Library/PrivateFrameworks/LinguisticData.framework/Versions/A/LinguisticData",
-    "/System/Library/PrivateFrameworks/Lexicon.framework/Versions/A/Lexicon",
-    "/System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Versions/A/BackgroundTaskManagement",
-    "/usr/lib/libTLE.dylib",
-    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices",
-    "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics",
-    "/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText",
-    "/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO",
-    "/System/Library/Frameworks/ColorSync.framework/Versions/A/ColorSync",
-    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS",
-    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices",
-    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore",
-    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD",
-    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSyncLegacy.framework/Versions/A/ColorSyncLegacy",
-    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis",
-    "/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight",
-    "/System/Library/PrivateFrameworks/FontServices.framework/libFontParser.dylib",
-    "/System/Library/PrivateFrameworks/RunningBoardServices.framework/Versions/A/RunningBoardServices",
-    "/System/Library/PrivateFrameworks/IOSurfaceAccelerator.framework/Versions/A/IOSurfaceAccelerator",
-    "/System/Library/PrivateFrameworks/WatchdogClient.framework/Versions/A/WatchdogClient",
-    "/System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay",
-    "/System/Library/Frameworks/CoreMedia.framework/Versions/A/CoreMedia",
-    "/System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator",
-    "/System/Library/Frameworks/Metal.framework/Versions/A/Metal",
-    "/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo",
-    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders",
-    "/System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport",
-    "/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore",
-    "/System/Library/Frameworks/VideoToolbox.framework/Versions/A/VideoToolbox",
-    "/System/Library/PrivateFrameworks/BaseBoard.framework/Versions/A/BaseBoard",
-    "/System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG",
-    "/usr/lib/libexpat.1.dylib",
-    "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib",
-    "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib",
-    "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib",
-    "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib",
-    "/usr/lib/libate.dylib",
-    "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib",
-    "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib",
-    "/System/Library/PrivateFrameworks/GPUWrangler.framework/Versions/A/GPUWrangler",
-    "/System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment",
-    "/System/Library/PrivateFrameworks/DSExternalDisplay.framework/Versions/A/DSExternalDisplay",
-    "/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/31001/Libraries/libllvm-flatbuffers.dylib",
-    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib",
-    "/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/31001/Libraries/libGPUCompilerUtils.dylib",
-    "/System/Library/PrivateFrameworks/CMCaptureCore.framework/Versions/A/CMCaptureCore",
-    "/usr/lib/libspindump.dylib",
-    "/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio",
-    "/System/Library/Frameworks/ExtensionFoundation.framework/Versions/A/ExtensionFoundation",
-    "/System/Library/PrivateFrameworks/CoreTime.framework/Versions/A/CoreTime",
-    "/System/Library/PrivateFrameworks/AppServerSupport.framework/Versions/A/AppServerSupport",
-    "/System/Library/PrivateFrameworks/perfdata.framework/Versions/A/perfdata",
-    "/System/Library/PrivateFrameworks/AudioToolboxCore.framework/Versions/A/AudioToolboxCore",
-    "/System/Library/PrivateFrameworks/caulk.framework/Versions/A/caulk",
-    "/usr/lib/libAudioStatistics.dylib",
-    "/System/Library/PrivateFrameworks/SystemPolicy.framework/Versions/A/SystemPolicy",
-    "/usr/lib/libSMC.dylib",
-    "/System/Library/Frameworks/CoreMIDI.framework/Versions/A/CoreMIDI",
-    "/usr/lib/libAudioToolboxUtility.dylib",
-    "/System/Library/PrivateFrameworks/OSAServicesClient.framework/Versions/A/OSAServicesClient",
-    "/usr/lib/libperfcheck.dylib",
-    "/System/Library/PrivateFrameworks/PlugInKit.framework/Versions/A/PlugInKit",
-    "/System/Library/PrivateFrameworks/AssertionServices.framework/Versions/A/AssertionServices",
-    "/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL",
-    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib",
-    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib",
-    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib",
-    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib",
-    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib",
-    "/usr/lib/libRosetta.dylib",
-    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib",
-    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSCore.framework/Versions/A/MPSCore",
-    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSImage.framework/Versions/A/MPSImage",
-    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNeuralNetwork.framework/Versions/A/MPSNeuralNetwork",
-    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSMatrix.framework/Versions/A/MPSMatrix",
-    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSRayIntersector.framework/Versions/A/MPSRayIntersector",
-    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNDArray.framework/Versions/A/MPSNDArray",
-    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSFunctions.framework/Versions/A/MPSFunctions",
-    "/System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools",
-    "/System/Library/PrivateFrameworks/AggregateDictionary.framework/Versions/A/AggregateDictionary",
-    "/usr/lib/libIOReport.dylib",
-    "/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage",
-    "/System/Library/PrivateFrameworks/PhotosensitivityProcessing.framework/Versions/A/PhotosensitivityProcessing",
-    "/System/Library/Frameworks/OpenCL.framework/Versions/A/OpenCL",
-    "/System/Library/PrivateFrameworks/GraphVisualizer.framework/Versions/A/GraphVisualizer",
-    "/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/FontServices",
-    "/System/Library/Frameworks/UniformTypeIdentifiers.framework/Versions/A/UniformTypeIdentifiers",
-    "/System/Library/PrivateFrameworks/OTSVG.framework/Versions/A/OTSVG",
-    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib",
-    "/System/Library/PrivateFrameworks/FontServices.framework/libhvf.dylib",
-    "/System/Library/PrivateFrameworks/FontServices.framework/libXTFontStaticRegistryData.dylib",
-    "/usr/lib/swift/libswiftMetal.dylib",
-    "/usr/lib/swift/libswiftsimd.dylib",
-    "/System/Library/PrivateFrameworks/VideoToolboxParavirtualizationSupport.framework/Versions/A/VideoToolboxParavirtualizationSupport",
-    "/System/Library/PrivateFrameworks/AppleVA.framework/Versions/A/AppleVA",
-    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATSUI.framework/Versions/A/ATSUI",
-    "/usr/lib/libcups.2.dylib",
-    "/System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos",
-    "/System/Library/Frameworks/GSS.framework/Versions/A/GSS",
-    "/usr/lib/libresolv.9.dylib",
-    "/System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal",
-    "/System/Library/Frameworks/Kerberos.framework/Versions/A/Libraries/libHeimdalProxy.dylib",
-    "/System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth",
-    "/System/Library/Frameworks/AVFAudio.framework/Versions/A/AVFAudio",
-    "/System/Library/PrivateFrameworks/AXCoreUtilities.framework/Versions/A/AXCoreUtilities",
-    "/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox",
-    "/System/Library/PrivateFrameworks/AudioSession.framework/Versions/A/AudioSession",
-    "/System/Library/Frameworks/IOBluetooth.framework/Versions/A/IOBluetooth",
-    "/System/Library/PrivateFrameworks/MediaExperience.framework/Versions/A/MediaExperience",
-    "/System/Library/PrivateFrameworks/AudioSession.framework/libSessionUtility.dylib",
-    "/System/Library/PrivateFrameworks/AudioResourceArbitration.framework/Versions/A/AudioResourceArbitration",
-    "/System/Library/PrivateFrameworks/PowerLog.framework/Versions/A/PowerLog",
-    "/System/Library/Frameworks/CoreData.framework/Versions/A/CoreData",
-    "/System/Library/Frameworks/CoreBluetooth.framework/Versions/A/CoreBluetooth",
-    "/System/Library/Frameworks/AudioUnit.framework/Versions/A/AudioUnit",
-    "/System/Library/PrivateFrameworks/CoreUtils.framework/Versions/A/CoreUtils",
-    "/System/Library/PrivateFrameworks/CoreUtilsExtras.framework/Versions/A/CoreUtilsExtras",
-    "/System/Library/PrivateFrameworks/IO80211.framework/Versions/A/IO80211",
-    "/System/Library/PrivateFrameworks/MobileKeyBag.framework/Versions/A/MobileKeyBag",
-    "/Users/sarah/Desktop/dash/Dash-Web/node_modules/fsevents/build/Release/fse.node"
-  ]
-}
\ No newline at end of file
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 4e3496608..b4ad9c17d 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -33,7 +33,6 @@ export enum DocumentType {
     PRES = 'presentation',
     PRESELEMENT = 'preselement',
     COMPARISON = 'comparison',
-    GROUP = 'group',
     PUSHPIN = 'pushpin',
     MAPROUTE = 'maproute',
     CALENDAR = 'calendar',
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index cf397e85a..ddb6f65b6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -246,8 +246,8 @@ export class DocumentOptions {
     _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)', false);
     _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)', false);
 
-    'acl-Guest'?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions
-    '_acl-Guest'?: string; // public permissions
+    acl_Guest?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions
+    _acl_Guest?: string; // public permissions
     type?: DTYPEt = new DTypeInfo('type of document', true);
     type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection
     _type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection
@@ -535,6 +535,7 @@ export namespace Docs {
                 {
                     layout: { view: FormattedTextBox, dataField: 'text' },
                     options: {
+                        acl: '',
                         _height: 35,
                         _xMargin: 10,
                         _yMargin: 10,
@@ -550,21 +551,21 @@ export namespace Docs {
                 DocumentType.SEARCH,
                 {
                     layout: { view: SearchBox, dataField: defaultDataKey },
-                    options: { _width: 400 },
+                    options: { acl: '', _width: 400 },
                 },
             ],
             [
                 DocumentType.IMG,
                 {
                     layout: { view: ImageBox, dataField: defaultDataKey },
-                    options: { freeform: '', systemIcon: 'BsFileEarmarkImageFill' },
+                    options: { acl: '', freeform: '', systemIcon: 'BsFileEarmarkImageFill' },
                 },
             ],
             [
                 DocumentType.WEB,
                 {
                     layout: { view: WebBox, dataField: defaultDataKey },
-                    options: { _height: 300, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' },
+                    options: { acl: '', _height: 300, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' },
                 },
             ],
             [
@@ -572,6 +573,7 @@ export namespace Docs {
                 {
                     layout: { view: CollectionView, dataField: defaultDataKey },
                     options: {
+                        acl: '',
                         _layout_fitWidth: true,
                         freeform: '',
                         _freeform_panX: 0,
@@ -588,42 +590,42 @@ export namespace Docs {
                 DocumentType.KVP,
                 {
                     layout: { view: KeyValueBox, dataField: defaultDataKey },
-                    options: { _layout_fitWidth: true, _height: 150 },
+                    options: { acl: '', _layout_fitWidth: true, _height: 150 },
                 },
             ],
             [
                 DocumentType.VID,
                 {
                     layout: { view: VideoBox, dataField: defaultDataKey },
-                    options: { _layout_currentTimecode: 0, systemIcon: 'BsFileEarmarkPlayFill' },
+                    options: { acl: '', _layout_currentTimecode: 0, systemIcon: 'BsFileEarmarkPlayFill' },
                 },
             ],
             [
                 DocumentType.AUDIO,
                 {
                     layout: { view: AudioBox, dataField: defaultDataKey },
-                    options: { _height: 100, layout_fitWidth: true, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillVolumeUpFill' },
+                    options: { acl: '', _height: 100, layout_fitWidth: true, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillVolumeUpFill' },
                 },
             ],
             [
                 DocumentType.REC,
                 {
                     layout: { view: VideoBox, dataField: defaultDataKey },
-                    options: { _height: 100, backgroundColor: 'pink', systemIcon: 'BsFillMicFill' },
+                    options: { acl: '', _height: 100, backgroundColor: 'pink', systemIcon: 'BsFillMicFill' },
                 },
             ],
             [
                 DocumentType.PDF,
                 {
                     layout: { view: PDFBox, dataField: defaultDataKey },
-                    options: { _layout_curPage: 1, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, systemIcon: 'BsFileEarmarkPdfFill' },
+                    options: { acl: '', _layout_curPage: 1, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, systemIcon: 'BsFileEarmarkPdfFill' },
                 },
             ],
             [
                 DocumentType.MAP,
                 {
                     layout: { view: MapBox, dataField: defaultDataKey },
-                    options: { map: '', _height: 600, _width: 800, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillPinMapFill' },
+                    options: { acl: '', map: '', _height: 600, _width: 800, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillPinMapFill' },
                 },
             ],
             [
@@ -631,6 +633,7 @@ export namespace Docs {
                 {
                     layout: { view: LinkBox, dataField: 'link' },
                     options: {
+                        acl: '',
                         childDontRegisterViews: true,
                         layout_hideLinkAnchors: true,
                         _height: 1,
@@ -647,88 +650,77 @@ export namespace Docs {
                 {
                     data: new List<Doc>(),
                     layout: { view: EmptyBox, dataField: defaultDataKey },
-                    options: { title: 'Global Script Database' },
+                    options: { acl: '', title: 'Global Script Database' },
                 },
             ],
             [
                 DocumentType.SCRIPTING,
                 {
                     layout: { view: ScriptingBox, dataField: defaultDataKey },
-                    options: { systemIcon: 'BsFileEarmarkCodeFill' },
+                    options: { acl: '', systemIcon: 'BsFileEarmarkCodeFill' },
                 },
             ],
             [
                 DocumentType.LABEL,
                 {
                     layout: { view: LabelBox, dataField: 'title' },
-                    options: { _singleLine: true, layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true },
+                    options: { acl: '', _singleLine: true, layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true },
                 },
             ],
             [
                 DocumentType.EQUATION,
                 {
                     layout: { view: EquationBox, dataField: 'text' },
-                    options: {
-                        fontSize: '14px',
-                        layout_reflowHorizontal: true,
-                        layout_reflowVertical: true,
-                        layout_nativeDimEditable: true,
-                        layout_hideDecorationTitle: true,
-                        systemIcon: 'BsCalculatorFill',
-                    }, // systemIcon: 'BsSuperscript' + BsSubscript
+                    options: { acl: '', fontSize: '14px', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, // systemIcon: 'BsSuperscript' + BsSubscript
                 },
             ],
             [
                 DocumentType.FUNCPLOT,
                 {
                     layout: { view: FunctionPlotBox, dataField: defaultDataKey },
-                    options: {
-                        layout_reflowHorizontal: true,
-                        layout_reflowVertical: true,
-                        layout_nativeDimEditable: true,
-                    },
+                    options: { acl: '', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true },
                 },
             ],
             [
                 DocumentType.BUTTON,
                 {
                     layout: { view: LabelBox, dataField: 'title' },
-                    options: { layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true },
+                    options: { acl: '', layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true },
                 },
             ],
             [
                 DocumentType.PRES,
                 {
                     layout: { view: PresBox, dataField: defaultDataKey },
-                    options: { defaultDoubleClick: 'ignore', hideClickBehaviors: true, layout_hideLinkAnchors: true },
+                    options: { acl: '', defaultDoubleClick: 'ignore', hideClickBehaviors: true, layout_hideLinkAnchors: true },
                 },
             ],
             [
                 DocumentType.FONTICON,
                 {
                     layout: { view: FontIconBox, dataField: 'icon' },
-                    options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideContextMenu: true, layout_hideLinkButton: true, _width: 40, _height: 40 },
+                    options: { acl: '', defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideContextMenu: true, layout_hideLinkButton: true, _width: 40, _height: 40 },
                 },
             ],
             [
                 DocumentType.WEBCAM,
                 {
                     layout: { view: RecordingBox, dataField: defaultDataKey },
-                    options: { systemIcon: 'BsFillCameraVideoFill' },
+                    options: { acl: '', systemIcon: 'BsFillCameraVideoFill' },
                 },
             ],
             [
                 DocumentType.PRESELEMENT,
                 {
                     layout: { view: PresElementBox, dataField: defaultDataKey },
-                    options: { title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' },
+                    options: { acl: '', title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' },
                 },
             ],
             [
                 DocumentType.CONFIG,
                 {
                     layout: { view: CollectionView, dataField: defaultDataKey },
-                    options: { config: '', layout_hideLinkButton: true, layout_unrendered: true },
+                    options: { acl: '', config: '', layout_hideLinkButton: true, layout_unrendered: true },
                 },
             ],
             [
@@ -737,6 +729,7 @@ export namespace Docs {
                     // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
                     layout: { view: InkingStroke, dataField: 'stroke' },
                     options: {
+                        acl: '',
                         systemIcon: 'BsFillPencilFill', //
                         layout_nativeDimEditable: true,
                         layout_reflowVertical: true,
@@ -751,7 +744,7 @@ export namespace Docs {
                 DocumentType.SCREENSHOT,
                 {
                     layout: { view: ScreenshotBox, dataField: defaultDataKey },
-                    options: { layout_nativeDimEditable: true, systemIcon: 'BsCameraFill' },
+                    options: { acl: '', layout_nativeDimEditable: true, systemIcon: 'BsCameraFill' },
                 },
             ],
             [
@@ -759,35 +752,48 @@ export namespace Docs {
                 {
                     data: '',
                     layout: { view: ComparisonBox, dataField: defaultDataKey },
-                    options: { backgroundColor: 'gray', dropAction: dropActionType.move, waitForDoubleClickToClick: 'always', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit' },
+                    options: {
+                        acl: '',
+                        backgroundColor: 'gray',
+                        dropAction: dropActionType.move,
+                        waitForDoubleClickToClick: 'always',
+                        layout_reflowHorizontal: true,
+                        layout_reflowVertical: true,
+                        layout_nativeDimEditable: true,
+                        systemIcon: 'BsLayoutSplit',
+                    },
                 },
             ],
             [
                 DocumentType.GROUPDB,
                 {
                     layout: { view: EmptyBox, dataField: defaultDataKey },
-                    options: { title: 'Global Group Database' },
-                },
-            ],
-            [
-                DocumentType.GROUP,
-                {
-                    layout: { view: EmptyBox, dataField: defaultDataKey },
-                    options: {},
+                    options: { acl: '', title: 'Global Group Database' },
                 },
             ],
             [
                 DocumentType.DATAVIZ,
                 {
                     layout: { view: DataVizBox, dataField: defaultDataKey },
-                    options: { dataViz_title: '', dataViz_line: '', dataViz_pie: '', dataViz_histogram: '', dataViz: 'table', _layout_fitWidth: true, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true },
+                    options: {
+                        acl: '',
+                        dataViz_title: '',
+                        dataViz_line: '',
+                        dataViz_pie: '',
+                        dataViz_histogram: '',
+                        dataViz: 'table',
+                        _layout_fitWidth: true,
+                        layout_reflowHorizontal: true,
+                        layout_reflowVertical: true,
+                        layout_nativeDimEditable: true,
+                    },
                 },
             ],
             [
                 DocumentType.LOADING,
                 {
                     layout: { view: LoadingBox, dataField: '' },
-                    options: { _layout_fitWidth: true, _fitHeight: true, layout_nativeDimEditable: true },
+                    options: { acl: '', _layout_fitWidth: true, _fitHeight: true, layout_nativeDimEditable: true },
                 },
             ],
             [
@@ -795,41 +801,28 @@ export namespace Docs {
                 {
                     data: '',
                     layout: { view: PhysicsSimulationBox, dataField: defaultDataKey, _width: 1000, _height: 800 },
-                    options: {
-                        _height: 100,
-                        mass1: '',
-                        mass2: '',
-                        layout_nativeDimEditable: true,
-                        position: '',
-                        acceleration: '',
-                        pendulum: '',
-                        spring: '',
-                        wedge: '',
-                        simulation: '',
-                        review: '',
-                        systemIcon: 'BsShareFill',
-                    },
+                    options: { acl: '', _height: 100, mass1: '', mass2: '', layout_nativeDimEditable: true, position: '', acceleration: '', pendulum: '', spring: '', wedge: '', simulation: '', review: '', systemIcon: 'BsShareFill' },
                 },
             ],
             [
                 DocumentType.PUSHPIN,
                 {
                     layout: { view: MapPushpinBox, dataField: defaultDataKey },
-                    options: {},
+                    options: { acl: '' },
                 },
             ],
             [
                 DocumentType.MAPROUTE,
                 {
                     layout: { view: CollectionView, dataField: defaultDataKey },
-                    options: {},
+                    options: { acl: '' },
                 },
             ],
             [
                 DocumentType.CALENDAR,
                 {
                     layout: { view: CalendarBox, dataField: defaultDataKey },
-                    options: {},
+                    options: { acl: '' },
                 },
             ],
         ]);
@@ -926,7 +919,7 @@ export namespace Docs {
                 type,
                 isBaseProto: true,
                 _width: 300,
-                'acl-Guest': SharingPermissions.View,
+                acl_Guest: SharingPermissions.View,
                 ...(template.options || {}),
                 layout: layout.view?.LayoutString(layout.dataField),
                 data: template.data,
@@ -971,8 +964,8 @@ export namespace Docs {
             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, '^_');
 
-            // dataProps['acl-Override'] = SharingPermissions.Unset;
-            dataProps['acl-Guest'] = options['acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View);
+            // dataProps.acl_Override = SharingPermissions.Unset;
+            dataProps.acl_Guest = options.acl_Guest ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View);
             dataProps.isSystem = viewProps.isSystem;
             dataProps.isDataDoc = true;
             dataProps.author = ClientUtils.CurrentUserEmail();
@@ -996,7 +989,7 @@ export namespace Docs {
 
             if (!noView) {
                 const viewFirstProps: { [id: string]: any } = { author: ClientUtils.CurrentUserEmail() };
-                viewFirstProps['acl-Guest'] = options['_acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View);
+                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
                 if (placeholderDoc) {
@@ -1004,7 +997,7 @@ export namespace Docs {
                     placeholderDoc._width = options._width !== undefined ? Number(options._width) : undefined;
                     viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true);
                     Array.from(Object.keys(placeholderDoc))
-                        .filter(key => key.startsWith('acl'))
+                        .filter(key => key.startsWith('acl_'))
                         .forEach(key => {
                             dataDoc[key] = viewDoc[key] = placeholderDoc[key];
                         });
@@ -1157,8 +1150,8 @@ export namespace Docs {
             I.rotation = 0;
             I.defaultDoubleClick = 'ignore';
             I.author_date = new DateField();
-            I['acl-Guest'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View;
-            // I['acl-Override'] = SharingPermissions.Unset;
+            I.acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View;
+            // I.acl_Override = SharingPermissions.Unset;
             I[Initializing] = false;
 
             return ink;
@@ -1335,7 +1328,7 @@ export namespace Docs {
             const doc = DockDocument(
                 configs.map(c => c.doc),
                 JSON.stringify(layoutConfig),
-                ClientUtils.CurrentUserEmail() === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options },
+                ClientUtils.CurrentUserEmail() === 'guest' ? options : { acl_Guest: SharingPermissions.View, ...options },
                 id
             );
             configs.forEach(c => {
@@ -1539,8 +1532,8 @@ export namespace DocUtils {
                 source,
                 target,
                 {
-                    'acl-Guest': SharingPermissions.Augment,
-                    '_acl-Guest': SharingPermissions.Augment,
+                    acl_Guest: SharingPermissions.Augment,
+                    _acl_Guest: SharingPermissions.Augment,
                     title: ComputedField.MakeFunction('generateLinkTitle(this)') as any,
                     link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined,
                     link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined,
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index eae5b2d6a..00279e7e1 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -836,7 +836,7 @@ pie title Minerals in my tap water
             linkDocs.author = ClientUtils.CurrentUserEmail();
             linkDocs.isSystem = true;
             linkDocs.data = new List<Doc>([]);
-            linkDocs["acl-Guest"] = SharingPermissions.Augment;
+            linkDocs.acl_Guest = SharingPermissions.Augment;
             doc.myLinkDatabase = new PrefetchProxy(linkDocs);
         }
     }
@@ -857,7 +857,7 @@ pie title Minerals in my tap water
             // childContextMenuScripts: new List<ScriptField>([addToDashboards!,]),
             // childContextMenuLabels: new List<string>(["Add to Dashboards",]),
             // childContextMenuIcons: new List<string>(["user-plus",]),
-            "acl-Guest": SharingPermissions.Augment, "_acl-Guest": SharingPermissions.Augment,
+            acl_Guest: SharingPermissions.Augment, _acl_Guest: SharingPermissions.Augment,
             childDragAction: dropActionType.embed, isSystem: true, childContentPointerEvents: "none", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
             // NOTE: treeView_HideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar
             _layout_showTitle: "title", treeView_HideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, 
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index 891561245..57363663d 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -14,7 +14,7 @@ export class ServerStats extends React.Component<{}> {
 
     @observable _stats: { [key: string]: any } | undefined = undefined;
     // private get linkVisible() {
-    //     return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
+    //     return this.targetDoc ? this.targetDoc['acl_' + PublicKey] !== SharingPermissions.None : false;
     // }
 
     constructor(props: {}) {
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 5b4ac5aff..19a7948e6 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -90,7 +90,7 @@ export class SharingManager extends React.Component<{}> {
     @observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not
 
     // private get linkVisible() {
-    //     return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
+    //     return this.targetDoc ? this.targetDoc['acl_' + PublicKey] !== SharingPermissions.None : false;
     // }
 
     constructor(props: {}) {
@@ -172,10 +172,10 @@ export class SharingManager extends React.Component<{}> {
 
         // the list of users shared with
         const userListContents = users
-            // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
+            // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl_${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
             .filter(({ user }) => docs[0]?.author !== user.email)
             .map(({ user, linkDatabase, sharingDoc, userColor }) => {
-                const userKey = `acl-${normalizeEmail(user.email)}`;
+                const userKey = `acl_${normalizeEmail(user.email)}`;
                 const uniform = docs.every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]);
                 // const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
                 let permissions = targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(targetDoc[userKey]);
@@ -204,7 +204,7 @@ 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(ClientUtils.CurrentUserEmail())}`;
+        const userKey = `acl_${normalizeEmail(ClientUtils.CurrentUserEmail())}`;
         const curUserPermission = StrCast(targetDoc[userKey]);
         // const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name
         userListContents.unshift(
@@ -230,10 +230,10 @@ export class SharingManager extends React.Component<{}> {
         );
 
         // the list of groups shared with
-        const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true));
+        const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl_${normalizeEmail(StrCast(title))}`) : true));
         groupListMap.unshift({ title: 'Guest' }); // , { title: "ALL" });
         const groupListContents = groupListMap.map(group => {
-            const groupKey = `acl-${StrCast(group.title)}`;
+            const groupKey = `acl_${StrCast(group.title)}`;
             const uniform = docs.every(doc => doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey]);
             const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
 
@@ -442,7 +442,7 @@ export class SharingManager extends React.Component<{}> {
     setInternalSharing = undoable((recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => {
         const { user, sharingDoc } = recipient;
         const target = targetDoc || this.targetDoc!;
-        const acl = `acl-${normalizeEmail(user.email)}`;
+        const acl = `acl_${normalizeEmail(user.email)}`;
         const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
         docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
             distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
@@ -459,7 +459,7 @@ export class SharingManager extends React.Component<{}> {
      */
     setInternalGroupSharing = undoable((group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
         const target = targetDoc || this.targetDoc!;
-        const acl = `acl-${normalizeEmail(StrCast(group.title))}`;
+        const acl = `acl_${normalizeEmail(StrCast(group.title))}`;
 
         const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
         docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
@@ -578,7 +578,7 @@ export class SharingManager extends React.Component<{}> {
         } else {
             docs.forEach(doc => {
                 if (GetEffectiveAcl(doc) === AclAdmin) {
-                    distributeAcls(`acl-${shareWith}`, permission, doc, undefined);
+                    distributeAcls(`acl_${shareWith}`, permission, doc, undefined);
                 }
             });
         }
@@ -618,7 +618,7 @@ export class SharingManager extends React.Component<{}> {
     removeGroup = (group: Doc) => {
         if (group.docsShared) {
             DocListCast(group.docsShared).forEach(doc => {
-                const acl = `acl-${StrCast(group.title)}`;
+                const acl = `acl_${StrCast(group.title)}`;
                 distributeAcls(acl, SharingPermissions.None, doc);
 
                 const members: string[] = JSON.parse(StrCast(group.members));
@@ -634,7 +634,7 @@ export class SharingManager extends React.Component<{}> {
     //     if (!targetDoc) {
     //         return;
     //     }
-    //     targetDoc["acl-" + PublicKey] = permission;
+    //     targetDoc['acl_' + PublicKey] = permission;
     // }s
 
     /**
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 25415a4f0..c4855d2e7 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -165,7 +165,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(ClientUtils.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
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 994107c01..ed9fc8dfb 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -222,7 +222,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 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"> 
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 5e1c80f0c..f4ded8367 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -231,7 +231,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @computed get noviceFields() {
         const noviceReqFields = ['author', 'author_date', 'tags', '_layout_curPage'];
-        return this.editableFields(key => key.indexOf('modificationDate') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('acl')), noviceReqFields);
+        return this.editableFields(key => key.indexOf('modificationDate') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('acl_')), noviceReqFields);
     }
 
     @undoBatch
@@ -473,7 +473,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         SharingManager.Instance.users.forEach(eachUser => {
             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) {
+                if (Doc.GetT(seldoc, 'acl_' + normalizeEmail(eachUser.user.email), 'string', true) === '' || Doc.GetT(seldoc, 'acl_' + normalizeEmail(eachUser.user.email), 'string', true) === undefined) {
                     userOnDoc = false;
                 }
             }
@@ -485,7 +485,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         // sorts and then adds each user to the table
         usersAdded.sort(this.sortUsers);
         usersAdded.forEach(userEmail => {
-            const userKey = `acl-${normalizeEmail(userEmail)}`;
+            const userKey = `acl_${normalizeEmail(userEmail)}`;
             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
@@ -494,7 +494,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         // adds current user
         let userEmail = ClientUtils.CurrentUserEmail();
         if (userEmail === 'guest') userEmail = 'Guest';
-        const userKey = `acl-${normalizeEmail(userEmail)}`;
+        const userKey = `acl_${normalizeEmail(userEmail)}`;
         if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail !== target.author) {
             let permission;
             if (this.layoutDocAcls) {
@@ -514,7 +514,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         groupList.sort(this.sortGroups);
         groupList.forEach(group => {
             if (group.title !== 'Guest' && this.selectedDoc) {
-                const groupKey = 'acl-' + normalizeEmail(StrCast(group.title));
+                const groupKey = 'acl_' + normalizeEmail(StrCast(group.title));
                 if (this.selectedDoc[groupKey] !== '' && this.selectedDoc[groupKey] !== undefined) {
                     let permission;
                     if (this.layoutDocAcls) {
@@ -529,7 +529,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         });
 
         // guest permission
-        const guestPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Guest']);
+        const guestPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target)).acl_Guest);
 
         return (
             <div>
@@ -1733,8 +1733,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         }
         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)
+            const type = [DocumentType.AUDIO, DocumentType.VID].find(dt => dt === DocCast(PresBox.Instance.activeItem?.annotationOn)?.type)
+                ? DocCast(PresBox.Instance.activeItem?.annotationOn)?.type
                 : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
             return (
                 <div className="propertiesView" style={{ width: this._props.width }}>
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 51352d3e2..fda320077 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -52,7 +52,7 @@ export class CollectionCarouselView extends CollectionSubView() {
     };
     captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => {
         // first look for properties on the document in the carousel, then fallback to properties on the container
-        const childValue = doc?.['caption-' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined;
+        const childValue = doc?.['caption_' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined;
         return childValue ?? this._props.styleProvider?.(this.layoutDoc, captionProps, property);
     };
     panelHeight = () => this._props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0);
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 6dba9e155..f945a7aa4 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -53,7 +53,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
         makeObservable(this);
         CollectionMenu.Instance = this;
         this._canFade = false; // don't let the inking menu fade away
-        this.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true);
+        this.Pinned = Cast(Doc.UserDoc().menuCollections_pinned, 'boolean', true);
         this.jumpTo(300, 300);
     }
 
@@ -71,7 +71,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
 
     @action
     toggleMenuPin = () => {
-        Doc.UserDoc()['menuCollections-pinned'] = this.Pinned = !this.Pinned;
+        Doc.UserDoc().menuCollections_pinned = this.Pinned = !this.Pinned;
         if (!this.Pinned && this._left < 0) {
             this.jumpTo(300, 300);
         }
@@ -280,8 +280,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     _saveFilterCommand = {
         params: ['target'],
         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']);`,
+        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(() => {
             this.target._childFilters = undefined;
             this.target._searchFilterDocs = undefined;
@@ -289,8 +289,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         initialize: (button: Doc) => {
             const activeDash = Doc.ActiveDashboard;
             if (activeDash) {
-                button['target-childFilters'] = (Doc.MySearcher._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._childFilters || activeDash._childFilters) as any as ObjectField) : undefined;
-                button['target-searchFilterDocs'] = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs as any as ObjectField) : undefined;
+                button.target_childFilters = (Doc.MySearcher._childFilters || activeDash._childFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._childFilters || activeDash._childFilters) as any as ObjectField) : undefined;
+                button.target_searchFilterDocs = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs as any as ObjectField) : undefined;
             }
         },
     };
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a0d84ab28..d6cbe0dab 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -70,7 +70,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
     }
 
     get collectionViewType(): CollectionViewType | undefined {
-        const viewField = StrCast(this.layoutDoc._type_collection);
+        const viewField = StrCast(this.layoutDoc._type_collection) as any as CollectionViewType;
         if (CollectionView._safeMode) {
             switch (viewField) { 
                 case CollectionViewType.Freeform:
@@ -79,7 +79,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
                 default:
             } // prettier-ignore
         }
-        return viewField as any as CollectionViewType;
+        return viewField;
     }
 
     screenToLocalTransform = () => (this._props.renderDepth ? this.ScreenToLocalBoxXf() : this.ScreenToLocalBoxXf().scale(this._props.PanelWidth() / this.bodyPanelWidth()));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 986c1e357..c1a889539 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1664,7 +1664,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             doc.x = scr?.[0];
             doc.y = scr?.[1];
         });
-        this._props.addDocTab(childDocs as any as Doc, OpenWhere.inParentFromScreen);
+        this._props.addDocTab(childDocs, OpenWhere.inParentFromScreen);
     };
 
     @undoBatch
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 303ff4b98..278d30d6a 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,6 +1,5 @@
 /* eslint-disable jsx-a11y/no-static-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 { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable } from 'mobx';
@@ -156,7 +155,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
     @observable _hover = false;
     docView = () => this._props.docView;
     render() {
-        const destinationIcon = Doc.toIcon(this._props.destinationDoc) as any as IconProp;
+        const destinationIcon = Doc.toIcon(this._props.destinationDoc);
 
         const title = StrCast(this._props.destinationDoc.title).length > 18 ? StrCast(this._props.destinationDoc.title).substr(0, 14) + '...' : this._props.destinationDoc.title;
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 6a86af6a7..7b1847ae4 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -160,7 +160,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         const timecode = Math.round(time);
         Object.keys(vals).forEach(val => {
             const findexed = Cast(d[`${val}_indexed`], listSpec('string'), []).slice();
-            findexed[timecode] = vals[val] as any as string;
+            findexed[timecode] = vals[val] || '';
             d[`${val}_indexed`] = new List<string>(findexed);
         });
     }
@@ -169,7 +169,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         const timecode = Math.round(time);
         Object.keys(vals).forEach(val => {
             const findexed = Cast(d[`${val}_indexed`], listSpec('number'), []).slice();
-            findexed[timecode] = vals[val] as any as number;
+            findexed[timecode] = vals[val] || 0;
             d[`${val}_indexed`] = new List<number>(findexed);
         });
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index db28d0a48..6d3d4be06 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -6,11 +6,10 @@ 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 { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
-import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
@@ -341,7 +340,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 alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
         const defaultUrl = new URL(ClientUtils.prepend('/assets/unknown-file-icon-hi.png'));
         const altpaths =
             alts
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index f96dd2b76..a9aa017a1 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -101,7 +101,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
                         </button>
                         <input className="keyValuePair-td-key-check" type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} />
                         <Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === this._props.keyName)?.[1].description ?? ''}>
-                            <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (this._props.keyName.match(/_/g)?.length || 0), color: keyStyle }}>
+                            <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (this._props.keyName.replace(/__/g, '').match(/_/g)?.length || 0), color: keyStyle }}>
                                 {'('.repeat(parenCount)}
                                 {this._props.keyName}
                                 {')'.repeat(parenCount)}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index ed9635bdd..4fe7ec993 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1607,7 +1607,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             </div>
                         </Tooltip>
                     </div>
-                    {[DocumentType.AUDIO, DocumentType.VID].includes(targetType as any as DocumentType) ? null : (
+                    {[DocumentType.AUDIO, DocumentType.VID].find(dt => dt === targetType) ? null : (
                         <>
                             <div className="ribbon-doubleButton">
                                 <div className="presBox-subheading">Slide Duration</div>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index c9aaa5158..9083d6ca3 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -156,9 +156,9 @@ export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol; im
 export function updateCachedAcls(doc: Doc) {
     if (doc) {
         const target = (doc as any)?.__fieldTuples ?? doc;
-        const permissions: { [key: string]: symbol } = !target.author || target.author === ClientUtils.CurrentUserEmail() ? { 'acl-Me': AclAdmin } : {};
+        const permissions: { [key: string]: symbol } = !target.author || target.author === ClientUtils.CurrentUserEmail() ? { acl_Me: AclAdmin } : {};
         Object.keys(target).forEach(key => {
-            key.startsWith('acl') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl);
+            key.startsWith('acl_') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl);
         });
         if (Object.keys(permissions).length || doc[DocAcl]?.length) {
             runInAction(() => {
@@ -382,7 +382,7 @@ export class Doc extends RefField {
                     this[UpdatingFromServer] = true;
                     this[fKey] = value;
                     this[UpdatingFromServer] = false;
-                    if (fKey.startsWith('acl')) {
+                    if (fKey.startsWith('acl_')) {
                         updateCachedAcls(this);
                     }
                     if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) {
@@ -390,7 +390,7 @@ export class Doc extends RefField {
                     }
                 };
                 const writeMode = DocServer.getFieldWriteMode(fKey);
-                if (fKey.startsWith('acl') || writeMode !== DocServer.WriteMode.Playground) {
+                if (fKey.startsWith('acl_') || writeMode !== DocServer.WriteMode.Playground) {
                     delete this[CachedUpdates][fKey];
                     // eslint-disable-next-line no-await-in-loop
                     await fn();
@@ -832,7 +832,7 @@ export namespace Doc {
                             newLayoutDoc.rootDocument = targetDoc;
                             newLayoutDoc.embedContainer = targetDoc;
                             newLayoutDoc.resolvedDataDoc = dataDoc;
-                            newLayoutDoc['acl-Guest'] = SharingPermissions.Edit;
+                            newLayoutDoc.acl_Guest = SharingPermissions.Edit;
                             if (dataDoc[templateField] === undefined && (templateLayoutDoc[templateField] as any)?.length) {
                                 dataDoc[templateField] = ObjectField.MakeCopy(templateLayoutDoc[templateField] as List<Doc>);
                                 // ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"])`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc });
@@ -970,7 +970,7 @@ export namespace Doc {
             delegate.proto = doc;
             delegate.author = ClientUtils.CurrentUserEmail();
             Object.keys(doc)
-                .filter(key => key.startsWith('acl'))
+                .filter(key => key.startsWith('acl_'))
                 .forEach(key => {
                     delegate[key] = doc[key];
                 });
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 72b0ef721..9361430cb 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -107,7 +107,7 @@ const _setterImpl = action((target: any, prop: string | symbol | number, valueIn
             if (value === undefined) 
                  (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $unset: { ['fields.' + prop]: '' } });
             else (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $set:   { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) :value}});
-            if (prop === 'author' || prop.toString().startsWith('acl')) updateCachedAcls(target);
+            if (prop === 'author' || prop.toString().startsWith('acl_')) updateCachedAcls(target);
         } else {
             DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
         }
@@ -184,7 +184,7 @@ export function SetCachedGroups(groups: string[]) {
 }
 function getEffectiveAcl(target: any, user?: string): symbol {
     const targetAcls = target[DocAcl];
-    if (targetAcls?.['acl-Me'] === AclAdmin || GetCachedGroupByName('Admin')) return AclAdmin;
+    if (targetAcls?.acl_Me === AclAdmin || GetCachedGroupByName('Admin')) return AclAdmin;
 
     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) {
@@ -210,7 +210,7 @@ function getEffectiveAcl(target: any, user?: string): symbol {
 
 /**
  * Recursively distributes the access right for a user across the children of a document and its annotations.
- * @param key the key storing the access right (e.g. acl-groupname)
+ * @param key the key storing the access right (e.g. acl_groupname)
  * @param acl the access right being stored (e.g. "Can Edit")
  * @param target the document on which this access right is being set
  * @param visited list of Doc's already distributed to.
@@ -219,7 +219,7 @@ function getEffectiveAcl(target: any, user?: string): symbol {
  */
 // 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())}`;
+    const selfKey = `acl_${normalizeEmail(ClientUtils.CurrentUserEmail())}`;
     if (!target || visited.includes(target) || key === selfKey) return;
     visited.push(target);
 
@@ -268,15 +268,15 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
  * 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'))
+    [...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 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);
+                sharePermission && distributeAcls(key === 'acl_Owner' ? `acl_${normalizeEmail(StrCast(parent.author))}` : key, sharePermission, child, undefined, false, layoutOnly);
             }
         });
 }
@@ -303,7 +303,7 @@ export function setter(target: any, inProp: string | symbol | number, value: any
     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;
+    if (typeof prop === 'string' && prop.startsWith('acl_') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
 
     if (typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('_')) {
         if (!prop.startsWith('__')) prop = prop.substring(1);
diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts
index a288bfeab..bfa6d7bdb 100644
--- a/src/server/authentication/DashUserModel.ts
+++ b/src/server/authentication/DashUserModel.ts
@@ -61,7 +61,7 @@ const userSchema = new mongoose.Schema(
  * Password hash middleware.
  */
 userSchema.pre('save', function save(next) {
-    const user = this as any as DashUserModel;
+    const user = this;
     if (!user.isModified('password')) {
         return next();
     }
@@ -70,7 +70,7 @@ userSchema.pre('save', function save(next) {
             return next(err);
         }
         bcrypt.hash(
-            user.password,
+            user.password ?? '',
             salt,
             () => {},
             (cryptErr: mongoose.Error, hash: string) => {
diff --git a/src/server/authentication/Passport.ts b/src/server/authentication/Passport.ts
index a5222e531..ca9e3058e 100644
--- a/src/server/authentication/Passport.ts
+++ b/src/server/authentication/Passport.ts
@@ -18,11 +18,11 @@ passport.deserializeUser<any, any>((id, done) => {
 passport.use(
     new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => {
         User.findOne({ email: email.toLowerCase() })
-            .then((user: any) => {
+            .then((user: DashUserModel) => {
                 if (!user) {
                     done(undefined, false, { message: 'Invalid email or password' }); // invalid email
                 } else {
-                    (user as any as DashUserModel).comparePassword(password, (error: Error, isMatch: boolean) => {
+                    user.comparePassword(password, (error: Error, isMatch: boolean) => {
                         if (error) return done(error);
                         if (!isMatch) return done(undefined, false, { message: 'Invalid email or password' }); // invalid password
                         // valid authentication HERE
-- 
cgit v1.2.3-70-g09d2


From dd08c20ec6df3fad6ecd6b16c787f10b0c23feb4 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 2 May 2024 00:39:31 -0400
Subject: lots more dependency cycle unwinding.

---
 src/client/documents/DocUtils.ts                   |  36 ++--
 src/client/documents/Documents.ts                  |   4 +-
 src/client/util/BranchingTrailManager.tsx          |   5 +-
 src/client/util/CalendarManager.tsx                |  20 +-
 src/client/util/CaptureManager.tsx                 |   9 +-
 src/client/util/CurrentUserUtils.ts                |  24 +--
 src/client/util/DictationManager.ts                |  44 ++++-
 src/client/util/DocumentManager.ts                 |  85 +++-----
 src/client/util/DragManager.ts                     |  13 --
 src/client/util/GroupManager.tsx                   |   2 +-
 src/client/util/HypothesisUtils.ts                 |  10 +-
 .../util/Import & Export/DirectoryImportBox.tsx    |   1 -
 src/client/util/LinkFollower.ts                    |  31 ++-
 src/client/util/LinkManager.ts                     |   9 +-
 src/client/util/PingManager.ts                     |   4 +-
 src/client/util/ReplayMovements.ts                 |   6 +-
 src/client/util/Scripting.ts                       |   9 +-
 src/client/util/SelectionManager.ts                |  29 +--
 src/client/util/SettingsManager.tsx                |  20 +-
 src/client/util/SharingManager.tsx                 |  12 +-
 src/client/util/SnappingManager.ts                 |  16 +-
 src/client/views/AntimodeMenu.tsx                  |   8 +-
 src/client/views/ComponentDecorations.tsx          |   4 +-
 src/client/views/DocComponent.tsx                  |   2 +
 src/client/views/DocViewUtils.ts                   |  21 ++
 src/client/views/DocumentButtonBar.tsx             |  29 +--
 src/client/views/DocumentDecorations.tsx           | 117 ++++++-----
 src/client/views/FieldsDropdown.tsx                |  22 +--
 src/client/views/FilterPanel.tsx                   |   8 +-
 src/client/views/GestureOverlay.tsx                |   6 +-
 src/client/views/GlobalKeyHandler.ts               |  78 +++++---
 src/client/views/InkControlPtHandles.tsx           |   6 +-
 src/client/views/InkStrokeProperties.ts            |   3 +-
 src/client/views/InkTranscription.tsx              |   1 -
 src/client/views/InkingStroke.tsx                  |   2 +-
 src/client/views/LightboxView.tsx                  |  36 ++--
 src/client/views/Main.tsx                          |  12 +-
 src/client/views/MainView.tsx                      |  85 ++++----
 src/client/views/MarqueeAnnotator.tsx              |   1 -
 src/client/views/OverlayView.tsx                   |   5 +-
 src/client/views/PropertiesButtons.tsx             |  29 ++-
 .../views/PropertiesDocBacklinksSelector.tsx       |   8 +-
 src/client/views/PropertiesDocContextSelector.tsx  |   3 +-
 src/client/views/PropertiesView.tsx                |  50 +++--
 src/client/views/ScriptBox.tsx                     |   8 +-
 src/client/views/ScriptingRepl.tsx                 |  16 +-
 src/client/views/SidebarAnnos.tsx                  |   8 +-
 src/client/views/StyleProp.ts                      |  24 +++
 src/client/views/StyleProvider.tsx                 | 105 ++++------
 .../views/collections/CollectionCarousel3DView.tsx |   5 +-
 .../views/collections/CollectionCarouselView.tsx   |   2 +-
 .../views/collections/CollectionDockingView.tsx    |  18 +-
 src/client/views/collections/CollectionMenu.tsx    |  32 ++-
 .../views/collections/CollectionNoteTakingView.tsx |   2 +-
 .../views/collections/CollectionPileView.tsx       |   4 +-
 .../collections/CollectionStackedTimeline.tsx      |  16 +-
 .../views/collections/CollectionStackingView.tsx   |   2 +-
 src/client/views/collections/CollectionSubView.tsx |  11 +-
 .../views/collections/CollectionTimeView.tsx       |   3 +-
 .../views/collections/CollectionTreeView.tsx       |  17 +-
 .../views/collections/CollectionTreeViewType.ts    |   5 +
 src/client/views/collections/TabDocView.tsx        | 216 +++++++++++----------
 src/client/views/collections/TreeView.tsx          |  15 +-
 .../CollectionFreeFormClusters.ts                  |  31 ++-
 .../CollectionFreeFormInfoUI.tsx                   |  10 +-
 .../CollectionFreeFormPannableContents.tsx         |   9 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  88 ++++-----
 .../collections/collectionFreeForm/MarqueeView.tsx |  15 +-
 .../collectionLinear/CollectionLinearView.tsx      |  10 +-
 .../collectionMulticolumn/MulticolumnResizer.tsx   |   2 +-
 .../collectionMulticolumn/MultirowResizer.tsx      |   2 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  28 +--
 .../collectionSchema/SchemaTableCell.tsx           |   8 +-
 src/client/views/global/globalScripts.ts           |  58 +++---
 src/client/views/linking/LinkMenuGroup.tsx         |   4 +-
 src/client/views/linking/LinkMenuItem.tsx          |  31 ++-
 .../views/newlightbox/ButtonMenu/ButtonMenu.tsx    |   3 +-
 src/client/views/newlightbox/NewLightboxView.tsx   |  27 ++-
 .../components/Recommendation/Recommendation.tsx   |  12 +-
 src/client/views/nodes/AudioBox.tsx                |  25 +--
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  41 ++--
 src/client/views/nodes/ComparisonBox.tsx           |   2 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   3 +-
 .../nodes/DataVizBox/components/LineChart.tsx      |   6 +-
 .../views/nodes/DataVizBox/components/PieChart.tsx |   2 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |   9 +-
 src/client/views/nodes/DocumentContentsView.tsx    |  13 +-
 src/client/views/nodes/DocumentIcon.tsx            |  11 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |  13 --
 src/client/views/nodes/DocumentView.tsx            | 203 +++++++++----------
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  38 ++--
 src/client/views/nodes/FunctionPlotBox.tsx         |   7 +-
 src/client/views/nodes/ImageBox.tsx                |   6 +-
 src/client/views/nodes/KeyValueBox.tsx             |   4 +-
 src/client/views/nodes/LabelBox.tsx                |   2 +-
 src/client/views/nodes/LinkAnchorBox.scss          |  34 ----
 src/client/views/nodes/LinkAnchorBox.tsx           | 118 -----------
 src/client/views/nodes/LinkBox.tsx                 |   5 +-
 src/client/views/nodes/LinkDescriptionPopup.tsx    |   3 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  26 ++-
 src/client/views/nodes/LoadingBox.tsx              |  21 +-
 .../views/nodes/MapBox/DirectionsAnchorMenu.tsx    |   4 +-
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx    |   4 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   6 +-
 src/client/views/nodes/MapBox/MapBox2.tsx          |   6 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |   6 +-
 src/client/views/nodes/PDFBox.tsx                  |   6 +-
 .../views/nodes/RecordingBox/RecordingBox.tsx      |  16 +-
 src/client/views/nodes/ScreenshotBox.tsx           |  11 +-
 src/client/views/nodes/VideoBox.tsx                |  28 ++-
 src/client/views/nodes/WebBox.tsx                  |   5 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |   5 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  45 ++---
 .../formattedText/ProsemirrorExampleTransfer.ts    |   4 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |  17 +-
 .../views/nodes/formattedText/SummaryView.tsx      |   5 +-
 .../views/nodes/generativeFill/GenerativeFill.tsx  |   8 +-
 src/client/views/nodes/trails/PresBox.tsx          |  58 +++---
 src/client/views/nodes/trails/PresElementBox.tsx   |  11 +-
 src/client/views/pdf/AnchorMenu.tsx                |   6 +-
 src/client/views/pdf/Annotation.tsx                |   6 +-
 src/client/views/pdf/PDFViewer.tsx                 |   6 +-
 src/client/views/search/SearchBox.tsx              |  31 ++-
 src/client/views/selectedDoc/SelectedDocView.tsx   |  14 +-
 src/client/views/topbar/TopBar.tsx                 |   8 +-
 src/fields/Doc.ts                                  |  19 +-
 src/fields/ScriptField.ts                          |   1 -
 src/fields/documentSchemas.ts                      |   1 -
 src/mobile/MobileInterface.tsx                     |   9 +-
 src/server/SharedMediaTypes.ts                     |   5 +
 130 files changed, 1254 insertions(+), 1461 deletions(-)
 create mode 100644 src/client/views/DocViewUtils.ts
 create mode 100644 src/client/views/StyleProp.ts
 create mode 100644 src/client/views/collections/CollectionTreeViewType.ts
 delete mode 100644 src/client/views/nodes/LinkAnchorBox.scss
 delete mode 100644 src/client/views/nodes/LinkAnchorBox.tsx

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

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index 0a47e0359..0c9fe0315 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -9,7 +9,7 @@ import { ClientUtils } from '../../ClientUtils';
 import * as JSZipUtils from '../../JSZipUtils';
 import { decycle } from '../../decycler/decycler';
 import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, FieldType, LinkedTo, Opt, SetActiveAudioLinker, StrListCast } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldResult, FieldType, LinkedTo, Opt, StrListCast } from '../../fields/Doc';
 import { DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { InkDataFieldName, InkField } from '../../fields/InkField';
@@ -29,9 +29,7 @@ import { SerializationHelper } from '../util/SerializationHelper';
 import { UndoManager, undoable } from '../util/UndoManager';
 import { ContextMenu } from '../views/ContextMenu';
 import { ContextMenuProps } from '../views/ContextMenuItem';
-import { FieldViewProps } from '../views/nodes/FieldView';
 import { LinkDescriptionPopup } from '../views/nodes/LinkDescriptionPopup';
-import { LoadingBox } from '../views/nodes/LoadingBox';
 import { OpenWhere } from '../views/nodes/OpenWhere';
 import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
 import { DocumentType } from './DocumentTypes';
@@ -50,7 +48,7 @@ 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 allLinks = Doc.Links(doc);
             const matchLink = (val: string, anchor: Doc) => {
                 const linkedToExp = (val ?? '').split('=');
                 if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val;
@@ -131,7 +129,7 @@ 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 : facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length;
+                      const satisfiesExistsFacets = !exists.length ? true : facetKey !== LinkedTo ? d[facetKey] !== undefined : Doc.Links(d).length;
                       const satisfiesUnsetsFacets = !unsets.length ? true : d[facetKey] === undefined;
                       const satisfiesMatchFacets = !matches.length
                           ? true
@@ -170,19 +168,7 @@ export namespace DocUtils {
         return rangeFilteredDocs;
     }
 
-    export const ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = [];
-
-    export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) {
-        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' });
-        });
-    }
-
-    SetActiveAudioLinker(MakeLinkToActiveAudio);
-
-    export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string; link_displayLine?: boolean }, id?: string, showPopup?: number[]) {
+    export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string }, id?: string, showPopup?: number[]) {
         if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
         if (target.doc === Doc.UserDoc()) return undefined;
 
@@ -232,7 +218,6 @@ export namespace DocUtils {
                     title: ComputedField.MakeFunction('generateLinkTitle(this)') as any,
                     link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined,
                     link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined,
-                    link_displayLine: linkSettings.link_displayLine,
                     link_relationship: linkSettings.link_relationship,
                     link_description: linkSettings.link_description,
                     x: ComputedField.MakeFunction(`((this.${a}?.x||0)+(this.${b}?.x||0))/2`) as any,
@@ -256,7 +241,6 @@ export namespace DocUtils {
                 const script = scripts[key];
                 if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) {
                     (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, {
-                        self: Doc.name,
                         this: Doc.name,
                         dragData: Doc.DocDragDataName,
                         value: 'any',
@@ -579,7 +563,7 @@ export namespace DocUtils {
     export function LeavePushpin(doc: Doc, annotationField: string) {
         if (doc.followLinkToggle) return undefined;
         const context = Cast(doc.embedContainer, Doc, null) ?? Cast(doc.annotationOn, Doc, null);
-        const hasContextAnchor = LinkManager.Links(doc).some(l => (l.link_anchor_2 === doc && Cast(l.link_anchor_1, Doc, null)?.annotationOn === context) || (l.link_anchor_1 === doc && Cast(l.link_anchor_2, Doc, null)?.annotationOn === context));
+        const hasContextAnchor = Doc.Links(doc).some(l => (l.link_anchor_2 === doc && Cast(l.link_anchor_1, Doc, null)?.annotationOn === context) || (l.link_anchor_1 === doc && Cast(l.link_anchor_2, Doc, null)?.annotationOn === context));
         if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
             const pushpin = Docs.Create.FontIconDocument({
                 title: '',
@@ -673,7 +657,7 @@ export namespace DocUtils {
                 proto.data_duration = result.duration;
             }
             if (overwriteDoc) {
-                LoadingBox.removeCurrentlyLoading(overwriteDoc);
+                Doc.removeCurrentlyLoading(overwriteDoc);
             }
             generatedDocuments.push(doc);
         }
@@ -717,7 +701,7 @@ export namespace DocUtils {
                 if (overwriteDoc) {
                     overwriteDoc.isLoading = false;
                     overwriteDoc.loadingError = (result as any).message;
-                    LoadingBox.removeCurrentlyLoading(overwriteDoc);
+                    Doc.removeCurrentlyLoading(overwriteDoc);
                 }
             } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype ?? '', result, options, overwriteDoc);
         });
@@ -755,7 +739,7 @@ export namespace DocUtils {
             if ((result as any).message) {
                 if (overwriteDoc) {
                     overwriteDoc.loadingError = (result as any).message;
-                    LoadingBox.removeCurrentlyLoading(overwriteDoc);
+                    Doc.removeCurrentlyLoading(overwriteDoc);
                 }
             } else newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc);
         });
@@ -868,6 +852,10 @@ export function FollowLinkScript() {
     return ScriptField.MakeScript('return followLink(this,altKey)', { altKey: 'boolean' });
 }
 
+export function IsFollowLinkScript(field: FieldResult<FieldType>) {
+    return ScriptCast(field)?.script.originalScript.includes('return followLink(');
+}
+
 ScriptingGlobals.add('Docs', Docs);
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) {
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a3fea1ce4..1c18c0d84 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -19,7 +19,6 @@ import { SharingPermissions } from '../../fields/util';
 import { PointData } from '../../pen-gestures/GestureTypes';
 import { DocServer } from '../DocServer';
 import { dropActionType } from '../util/DropActionTypes';
-import { LinkManager } from '../util/LinkManager';
 import { CollectionViewType, DocumentType } from './DocumentTypes';
 
 class EmptyBox {
@@ -394,7 +393,6 @@ export class DocumentOptions {
     link?: string;
     link_description?: string; // added for links
     link_relationship?: string; // type of relatinoship a link represents
-    link_displayLine?: BOOLt = new BoolInfo('whether a link line should be dipslayed between the two link anchors');
     link_displayArrow?: BOOLt = new BoolInfo("whether to display link's directional arrowhead");
     link_anchor_1?: DOCt = new DocInfo('start anchor of a link');
     link_anchor_2?: DOCt = new DocInfo('end anchor of a link');
@@ -810,7 +808,7 @@ export namespace Docs {
                 'link'
             );
 
-            LinkManager.Instance.addLink(linkDoc);
+            Doc.AddLink(linkDoc);
 
             return linkDoc;
         }
diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx
index 28c00644f..119d103c5 100644
--- a/src/client/util/BranchingTrailManager.tsx
+++ b/src/client/util/BranchingTrailManager.tsx
@@ -6,8 +6,8 @@ import * as React from 'react';
 import { Doc } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { OverlayView } from '../views/OverlayView';
+import { DocumentView } from '../views/nodes/DocumentView';
 import { PresBox } from '../views/nodes/trails';
-import { DocumentManager } from './DocumentManager';
 
 @observer
 export class BranchingTrailManager extends React.Component {
@@ -46,7 +46,6 @@ export class BranchingTrailManager extends React.Component {
         // hi.overlayY = 100;
 
         // Doc.AddToMyOverlay(hi);
-        console.log(DocumentManager._overlayViews);
     };
 
     @action setSlideHistoryStack = action((newArr: String[]) => {
@@ -103,7 +102,7 @@ export class BranchingTrailManager extends React.Component {
         this.setSlideHistoryStack(newStack);
 
         removed.forEach(info => this.containsSet.delete(info.toString()));
-        DocumentManager.Instance.showDocument(targetDoc, { willZoomCentered: true });
+        DocumentView.showDocument(targetDoc, { willZoomCentered: true });
         if (this.slideHistoryStack.length === 0) {
             Doc.UserDoc().isBranchingMode = false;
         }
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 8e3936d34..77cf80151 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -13,15 +13,12 @@ import { Doc, DocListCast } from '../../fields/Doc';
 import { DocData } from '../../fields/DocSymbols';
 import { StrCast } from '../../fields/Types';
 import { Docs } from '../documents/Documents';
-import { DictationOverlay } from '../views/DictationOverlay';
 import { MainViewModal } from '../views/MainViewModal';
 import { ObservableReactComponent } from '../views/ObservableReactComponent';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
 import './CalendarManager.scss';
-import { DocumentManager } from './DocumentManager';
-import { SelectionManager } from './SelectionManager';
-import { SettingsManager } from './SettingsManager';
+import { SnappingManager } from './SnappingManager';
 // import 'react-date-range/dist/styles.css';
 // import 'react-date-range/dist/theme/default.css';
 
@@ -85,11 +82,9 @@ export class CalendarManager extends ObservableReactComponent<{}> {
     };
 
     public open = (target?: DocumentView, targetDoc?: Doc) => {
-        console.log('hi');
         runInAction(() => {
             this.targetDoc = targetDoc || target?.Document;
             this.targetDocView = target;
-            DictationOverlay.Instance.hasActiveModal = true;
             this.isOpen = this.targetDoc !== undefined;
         });
     };
@@ -99,7 +94,6 @@ export class CalendarManager extends ObservableReactComponent<{}> {
         TaskCompletionBox.taskCompleted = false;
         setTimeout(
             action(() => {
-                DictationOverlay.Instance.hasActiveModal = false;
                 this.targetDoc = undefined;
             }),
             500
@@ -137,7 +131,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
 
     // TODO: Make undoable
     private addToCalendar = () => {
-        const docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+        const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
         const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // doc to add to calendar
 
         console.log(targetDoc);
@@ -190,14 +184,14 @@ export class CalendarManager extends ObservableReactComponent<{}> {
 
     private focusOn = (contents: string) => {
         const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
-        const docs = SelectionManager.Views.length > 1 ? SelectionManager.Views.map(docView => docView.Document) : [this.targetDoc];
+        const docs = DocumentView.Selected().length > 1 ? DocumentView.Selected().map(docView => docView.Document) : [this.targetDoc];
         return (
             <span
                 className="focus-span"
                 title={title}
                 onClick={() => {
                     if (this.targetDoc && this.targetDocView && docs.length === 1) {
-                        DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
+                        DocumentView.showDocument(this.targetDoc, { willZoomCentered: true });
                     }
                 }}
                 onPointerEnter={action(() => {
@@ -251,17 +245,17 @@ export class CalendarManager extends ObservableReactComponent<{}> {
 
     @computed
     get calendarInterface() {
-        const docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+        const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
         const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
 
         return (
             <div
                 className="calendar-interface"
                 style={{
-                    background: SettingsManager.userBackgroundColor,
+                    background: SnappingManager.userBackgroundColor,
                     color: StrCast(Doc.UserDoc().userColor),
                 }}>
-                <p className="selected-doc-title" style={{ color: SettingsManager.userColor }}>
+                <p className="selected-doc-title" style={{ color: SnappingManager.userColor }}>
                     <b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}</b>
                 </p>
                 <div className="creation-type-container">
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index a03c80f2a..4fd934774 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -10,8 +10,7 @@ import { DocCast, StrCast } from '../../fields/Types';
 import { LightboxView } from '../views/LightboxView';
 import { MainViewModal } from '../views/MainViewModal';
 import './CaptureManager.scss';
-import { LinkManager } from './LinkManager';
-import { SelectionManager } from './SelectionManager';
+import { DocumentView } from '../views/nodes/DocumentView';
 
 @observer
 export class CaptureManager extends React.Component<{}> {
@@ -56,7 +55,7 @@ export class CaptureManager extends React.Component<{}> {
         const doc = this._document;
         const order: JSX.Element[] = [];
         if (doc) {
-            LinkManager.Links(doc).forEach((l, i) =>
+            Doc.Links(doc).forEach((l, i) =>
                 order.push(
                     <div className="list-item">
                         <div className="number">{i}</div>
@@ -88,8 +87,8 @@ export class CaptureManager extends React.Component<{}> {
                     <div
                         className="cancel"
                         onClick={() => {
-                            const selected = SelectionManager.Views.slice();
-                            SelectionManager.DeselectAll();
+                            const selected = DocumentView.Selected();
+                            DocumentView.DeselectAll();
                             selected.map(dv => dv.props.removeDocument?.(dv.Document));
                             this.close();
                         }}>
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 15c8fec5a..ffc1304bf 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,4 +1,4 @@
-import { observable, reaction, runInAction } from "mobx";
+import { reaction, runInAction } from "mobx";
 import * as rp from 'request-promise';
 import { ClientUtils, OmitKeys } from "../../ClientUtils";
 import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
@@ -19,7 +19,8 @@ import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
 import { Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents";
 import { DashboardView } from "../views/DashboardView";
 import { OverlayView } from "../views/OverlayView";
-import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView";
+import { CollectionTreeView } from "../views/collections/CollectionTreeView";
+import { TreeViewType } from "../views/collections/CollectionTreeViewType";
 import { Colors } from "../views/global/globalEnums";
 import { mediaState } from "../views/nodes/AudioBox";
 import { ButtonType, FontIconBox } from "../views/nodes/FontIconBox/FontIconBox";
@@ -30,8 +31,9 @@ import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
 import { DragManager } from "./DragManager";
 import { dropActionType } from "./DropActionTypes";
 import { MakeTemplate } from "./DropConverter";
-import { LinkManager, UPDATE_SERVER_CACHE } from "./LinkManager";
+import { UPDATE_SERVER_CACHE } from "./LinkManager";
 import { ScriptingGlobals } from "./ScriptingGlobals";
+import { SelectionManager } from "./SelectionManager";
 import { ColorScheme } from "./SettingsManager";
 import { SnappingManager } from "./SnappingManager";
 import { UndoManager } from "./UndoManager";
@@ -937,14 +939,13 @@ pie title Minerals in my tap water
         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)
-        Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs)
-        Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed)
-
-        Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org")
+        SelectionManager.DeselectAll(); // this forces SelectionManager implementation to copy over to DocumentView's API.  This also triggers the LinkManager to be created
+        
+        Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards);
+        Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs);
+        Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed);
 
-        // eslint-disable-next-line no-new
-        new LinkManager();
+        Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org");
 
         DocServer.CacheNeedsUpdate() && setTimeout(UPDATE_SERVER_CACHE, 2500);
         setInterval(UPDATE_SERVER_CACHE, 120000);
@@ -969,12 +970,11 @@ pie title Minerals in my tap water
         });
     }
 
-    @observable public static ServerVersion: string = ';'
     public static async loadCurrentUser() {
         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; });
+                runInAction(() => { SnappingManager.SetServerVersion(result.version); });
                 ClientUtils.SetCurrentUserEmail(result.email);
                 resolvedPorts = result.resolvedPorts as any;
                 DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts?.socket, result.email);
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 97ee628e2..bc9fe813f 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -10,12 +10,13 @@ import { RichTextField } from '../../fields/RichTextField';
 import { listSpec } from '../../fields/Schema';
 import { Cast, CastCtor } from '../../fields/Types';
 import { AudioField, ImageField } from '../../fields/URLField';
+import { AudioAnnoState } from '../../server/SharedMediaTypes';
+import { Networking } from '../Network';
 import { DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
 import { DictationOverlay } from '../views/DictationOverlay';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { OpenWhere } from '../views/nodes/OpenWhere';
-import { SelectionManager } from './SelectionManager';
 import { UndoManager } from './UndoManager';
 
 /**
@@ -244,7 +245,7 @@ export namespace DictationManager {
         export const execute = async (phrase: string) =>
             UndoManager.RunInBatch(async () => {
                 console.log('PHRASE: ' + phrase);
-                const targets = SelectionManager.Views;
+                const targets = DocumentView.Selected();
                 if (!targets || !targets.length) {
                     return undefined;
                 }
@@ -395,4 +396,43 @@ export namespace DictationManager {
             },
         ];
     }
+    export function recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
+        let gumStream: any;
+        let recorder: any;
+        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; }, // prettier-ignore
+                continuous: { indefinite: false },
+            }).then(results => {
+                if (results && [DictationManager.Controls.Infringed].includes(results)) {
+                    DictationManager.Controls.stop();
+                }
+                onEnd?.();
+            });
+
+            gumStream = stream;
+            recorder = new MediaRecorder(stream);
+            recorder.ondataavailable = async (e: any) => {
+                const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
+                if (!(result instanceof Error)) {
+                    const audioField = new AudioField(result.accessPaths.agnostic.client);
+                    const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
+                    if (audioAnnos) audioAnnos.push(audioField);
+                    else dataDoc[field + '_audioAnnotations'] = new List([audioField]);
+                }
+            };
+            recorder.start();
+            const stopFunc = () => {
+                recorder.stop();
+                DictationManager.Controls.stop(/* false */);
+                dataDoc.audioAnnoState = AudioAnnoState.stopped;
+                gumStream.getAudioTracks()[0].stop();
+            };
+            if (onRecording) onRecording(stopFunc);
+            else setTimeout(stopFunc, 5000);
+        });
+    }
 }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 8f0c2b0bf..5bcac7330 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,23 +1,17 @@
 import { Howl } from 'howler';
 import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx';
 import { Doc, Opt } from '../../fields/Doc';
-import { AclAdmin, AclEdit, Animation, DocData } from '../../fields/DocSymbols';
+import { Animation, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { listSpec } from '../../fields/Schema';
 import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { AudioField } from '../../fields/URLField';
-import { GetEffectiveAcl } from '../../fields/util';
 import { CollectionViewType } from '../documents/DocumentTypes';
-import { TabDocView } from '../views/collections/TabDocView';
 import { LightboxView } from '../views/LightboxView';
 import { DocumentView, DocumentViewInternal } from '../views/nodes/DocumentView';
 import { FocusViewOptions } from '../views/nodes/FocusViewOptions';
-import { KeyValueBox } from '../views/nodes/KeyValueBox';
-import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
 import { OpenWhere } from '../views/nodes/OpenWhere';
 import { PresBox } from '../views/nodes/trails';
-import { ScriptingGlobals } from './ScriptingGlobals';
-import { SelectionManager } from './SelectionManager';
 
 type childIterator = { viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean; contextPath: Doc[] };
 export class DocumentManager {
@@ -29,10 +23,9 @@ export class DocumentManager {
     }
 
     // global holds all of the nodes (regardless of which collection they're in)
-    @observable _documentViews = new Set<DocumentView>();
-    @observable.shallow public CurrentlyLoading: Doc[] = [];
+    @observable private _documentViews = new Set<DocumentView>();
     @computed public get DocumentViews() {
-        return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.Contains(view)));
+        return Array.from(this._documentViews).filter(view => (!view.ComponentView?.dontRegisterView?.() && !LightboxView.LightboxDoc) || LightboxView.Contains(view));
     }
     public AddDocumentView(dv: DocumentView) {
         this._documentViews.add(dv);
@@ -44,7 +37,19 @@ export class DocumentManager {
     // private constructor so no other class can create a nodemanager
     private constructor() {
         makeObservable(this);
-        observe(this.CurrentlyLoading, change => {
+
+        DocumentView.allViews = () => this.DocumentViews;
+        DocumentView.addView = this.AddView;
+        DocumentView.removeView = this.RemoveView;
+        DocumentView.showDocument = this.showDocument;
+        DocumentView.showDocumentView = this.showDocumentView;
+        DocumentView.linkCommonAncestor = DocumentManager.LinkCommonAncestor;
+        DocumentView.addViewRenderedCb = this.AddViewRenderedCb;
+        DocumentView.getFirstDocumentView = this.getFirstDocumentView;
+        DocumentView.getDocumentView = this.getDocumentView;
+        DocumentView.getContextPath = DocumentManager.GetContextPath;
+        DocumentView.getLightboxDocumentView = this.getLightboxDocumentView;
+        observe(Doc.CurrentlyLoading, change => {
             // watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become
             switch (change.type as any) {
                 case 'update':
@@ -92,17 +97,12 @@ export class DocumentManager {
 
     @action
     public AddView = (view: DocumentView) => {
-        if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) &&
-            !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {      
-            this.AddDocumentView(view);
-            this.callAddViewFuncs(view);
-        } // prettier-ignore
+        this.AddDocumentView(view);
+        this.callAddViewFuncs(view);
     };
     public RemoveView = action((view: DocumentView) => {
-        if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) && !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
-            this.DeleteDocumentView(view);
-        }
-        SelectionManager.DeselectView(view);
+        this.DeleteDocumentView(view);
+        DocumentView.DeselectView(view);
     });
 
     // gets all views
@@ -203,6 +203,11 @@ export class DocumentManager {
     }
     static _overlayViews = new ObservableSet<DocumentView>();
 
+    /**
+     * Find the nearest common ancestor collection that contains a link's source and target
+     * @param linkDoc
+     * @returns common ancestor DocumentView
+     */
     public static LinkCommonAncestor(linkDoc: Doc) {
         const getAnchor = (which: number) => {
             const anch = DocCast(linkDoc['link_anchor_' + which]);
@@ -245,11 +250,8 @@ export class DocumentManager {
         Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
         const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
         if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
-        const tabView = Array.from(TabDocView._allTabs).find(view => view._document === docContextPath[0]);
-        if (!tabView?._activated && tabView?._document) {
-            options.toggleTarget = false;
-            TabDocView.Activate(tabView?._document);
-        }
+        if (DocumentView.activateTabView(docContextPath[0])) options.toggleTarget = false;
+
         const rootContextView =
             docContextPath.length &&
             (await new Promise<DocumentView>(res => {
@@ -260,7 +262,7 @@ export class DocumentManager {
                     return;
                 }
                 options.didMove = true;
-                (!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+                (!LightboxView.LightboxDoc && docContextPath.some(doc => DocumentView.activateTabView(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
                 this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
             }));
         if (options.openLocation === OpenWhere.lightbox) {
@@ -355,33 +357,4 @@ export class DocumentManager {
         }
     }
 }
-// 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);
-        if (dv && (!containingDoc || dv.containerViewPath?.().lastElement()?.Document === containingDoc)) {
-            DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.Document));
-        } else {
-            const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc));
-            const showDoc = !Doc.IsSystem(container) && !cv ? container : doc;
-            options.toggleTarget = undefined;
-            DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
-                const cvFound = DocumentManager.Instance.getDocumentView(containingDoc);
-                const dvFound = DocumentManager.Instance.getDocumentView(doc, cvFound);
-                dvFound && Doc.linkFollowHighlight(dvFound.Document);
-            });
-        }
-    };
-    if (Doc.IsDataProto(doc) && Doc.GetEmbeddings(doc).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) {
-        doc = Doc.GetEmbeddings(doc).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!;
-    }
-    if (doc.hidden) {
-        doc.hidden = false;
-        options.toggleTarget = false;
-        setTimeout(func);
-    } else func();
-}
-ScriptingGlobals.add(DocFocusOrOpen);
+setTimeout(() => DocumentManager.Instance);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 34f54be2e..fda505420 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -27,8 +27,6 @@ import { ScriptCast } from '../../fields/Types';
 import { Docs } from '../documents/Documents';
 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';
 
@@ -648,14 +646,3 @@ export namespace DragManager {
         document.addEventListener('pointerup', upHandler, true);
     }
 }
-
-// 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.forEach(dv => {
-        dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged;
-    });
-    return undefined;
-});
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index e1a503ac9..5701a22c0 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -77,7 +77,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
      */
     @action
     open = () => {
-        // SelectionManager.DeselectAll();
+        // DocumentView.DeselectAll();
         this.isOpen = true;
         this.populateUsers();
     };
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index dd18b2533..5a6522dc8 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -6,8 +6,6 @@ import { WebField } from '../../fields/URLField';
 import { Docs } from '../documents/Documents';
 import { DocumentLinksButton } from '../views/nodes/DocumentLinksButton';
 import { DocumentView } from '../views/nodes/DocumentView';
-import { DocumentManager } from './DocumentManager';
-import { SelectionManager } from './SelectionManager';
 
 export namespace Hypothesis {
     /**
@@ -25,7 +23,7 @@ export namespace Hypothesis {
      * Search for a WebDocument whose url field matches the given uri, return undefined if not found
      */
     export const findWebDoc = async (uri: string) => {
-        const currentDoc = SelectionManager.Docs.lastElement();
+        const currentDoc = DocumentView.Selected().lastElement()?.Document;
         if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the currently selected doc is the annotation's source, only use Search otherwise
 
         const results: Doc[] = [];
@@ -39,7 +37,7 @@ export namespace Hypothesis {
         //     })
         // );
 
-        const onScreenResults = results.filter(doc => DocumentManager.Instance.getFirstDocumentView(doc));
+        const onScreenResults = results.filter(doc => DocumentView.getFirstDocumentView(doc));
         return onScreenResults.length ? onScreenResults[0] : results.length ? results[0] : undefined; // prioritize results that are currently on the screen
     };
 
@@ -65,7 +63,7 @@ export namespace Hypothesis {
                 DocumentLinksButton.AnnotationId = annotationId;
                 DocumentLinksButton.AnnotationUri = annotationUri;
             });
-            const endLinkView = DocumentManager.Instance.getFirstDocumentView(sourceDoc);
+            const endLinkView = DocumentView.getFirstDocumentView(sourceDoc);
             const rect = document.body.getBoundingClientRect();
             const x = rect.x + rect.width / 2;
             const y = 250;
@@ -182,7 +180,7 @@ export namespace Hypothesis {
                     bubbles: true,
                 })
             );
-            const targetView: Opt<DocumentView> = DocumentManager.Instance.getFirstDocumentView(target);
+            const targetView: Opt<DocumentView> = DocumentView.getFirstDocumentView(target);
             const position = targetView?.screenToViewTransform().inverse().transformPoint(0, 0);
             targetView && position && simulateMouseClick(targetView.ContentDiv!, position[0], position[1], position[0], position[1], false);
         }, 300);
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 398ba3c04..baa0786b7 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -17,7 +17,6 @@
 // import { DocumentType } from '../../documents/DocumentTypes';
 // import { Networking } from '../../Network';
 // import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
-// import { DocumentManager } from '../DocumentManager';
 // import './DirectoryImportBox.scss';
 // import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry';
 // import * as React from 'react';
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index a6f6122ab..9a0edcfec 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,14 +1,12 @@
 import { action, runInAction } from 'mobx';
-import { Doc, DocListCast, FieldResult, FieldType, Opt } from '../../fields/Doc';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
+import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
+import { DocumentView } from '../views/nodes/DocumentView';
 import { FocusViewOptions } from '../views/nodes/FocusViewOptions';
 import { OpenWhere } from '../views/nodes/OpenWhere';
 import { PresBox } from '../views/nodes/trails';
-import { DocumentManager } from './DocumentManager';
-import { LinkManager } from './LinkManager';
 import { ScriptingGlobals } from './ScriptingGlobals';
-import { SelectionManager } from './SelectionManager';
 import { SnappingManager } from './SnappingManager';
 import { UndoManager } from './UndoManager';
 /*
@@ -24,6 +22,9 @@ import { UndoManager } from './UndoManager';
  * - user defined kvps
  */
 export class LinkFollower {
+    public static Init() {
+        DocumentView.FollowLink = LinkFollower.FollowLink;
+    }
     // follows a link - if the target is on screen, it highlights/pans to it.
     // if the target isn't onscreen, then it will open up the target in the lightbox, or in place
     // depending on the followLinkLocation property of the source (or the link itself as a fallback);
@@ -42,9 +43,9 @@ 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 getView = (doc: Doc) => DocumentView.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc));
         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 linkDocs = link ? [link] : Doc.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
         const fwdLinkWithoutTargetView = fwdLinks.find(l => !getView(DocCast(l.link_anchor_2)));
@@ -68,7 +69,7 @@ export class LinkFollower {
                         ? linkDoc.link_anchor_2
                         : linkDoc.link_anchor_1
             ) as Doc;
-            const srcAnchor: Doc = LinkManager.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
+            const srcAnchor: Doc = Doc.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
             if (target) {
                 const doFollow = (canToggle?: boolean) => {
                     const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
@@ -87,14 +88,14 @@ export class LinkFollower {
                         zoomTextSelections: BoolCast(srcAnchor.followLinkZoomText),
                     };
                     if (target.type === DocumentType.PRES) {
-                        const containerDocContext = DocumentManager.GetContextPath(sourceDoc, true); // gather all views that affect layout of sourceDoc so we can revert them after playing the rail
-                        SelectionManager.DeselectAll();
-                        if (!DocumentManager.Instance.AddViewRenderedCb(target, dv => containerDocContext.length && dv.ComponentView?.playTrail?.(containerDocContext))) {
+                        const containerDocContext = DocumentView.getContextPath(sourceDoc, true); // gather all views that affect layout of sourceDoc so we can revert them after playing the rail
+                        DocumentView.DeselectAll();
+                        if (!DocumentView.addViewRenderedCb(target, dv => containerDocContext.length && dv.ComponentView?.playTrail?.(containerDocContext))) {
                             PresBox.OpenPresMinimized(target, [0, 0]);
                         }
                         finished?.();
                     } else {
-                        DocumentManager.Instance.showDocument(target, options, allFinished);
+                        DocumentView.showDocument(target, options, allFinished);
                     }
                 };
                 let movedTarget = false;
@@ -133,10 +134,6 @@ export class LinkFollower {
 
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) {
-    SelectionManager.DeselectAll();
+    DocumentView.DeselectAll();
     return LinkFollower.FollowLink(undefined, doc, altKey) ? undefined : { select: true };
 });
-
-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 dbd13f978..56d5dce4e 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -30,8 +30,8 @@ export class LinkManager {
     @observable.shallow userLinkDBs: Doc[] = [];
     @observable public currentLink: Opt<Doc> = undefined;
     @observable public currentLinkAnchor: Opt<Doc> = undefined;
-    public static get Instance() {
-        return LinkManager._instance;
+    public static get Instance(): LinkManager {
+        return Doc.UserDoc() ? LinkManager._instance ?? new LinkManager() : (undefined as any as LinkManager);
     }
 
     public static Links(doc: Doc | undefined) {
@@ -47,10 +47,13 @@ export class LinkManager {
         this.userLinkDBs.push(linkDb);
     };
     public static AutoKeywords = 'keywords:Usages';
-    constructor() {
+    private constructor() {
         makeObservable(this);
         LinkManager._instance = this;
         Doc.AddLink = this.addLink;
+        Doc.DeleteLink = this.deleteLink;
+        Doc.Links = LinkManager.Links;
+        Doc.getOppositeAnchor = LinkManager.getOppositeAnchor;
         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
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index e5e69c5ac..255e9cee0 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -1,6 +1,6 @@
 import { action, makeObservable, observable, runInAction } from 'mobx';
 import { Networking } from '../Network';
-import { CurrentUserUtils } from './CurrentUserUtils';
+import { SnappingManager } from './SnappingManager';
 
 export class PingManager {
     // create static instance and getter for global use
@@ -32,7 +32,7 @@ export class PingManager {
         try {
             const res = await Networking.PostToServer('/ping', { date: new Date() });
             runInAction(() => {
-                CurrentUserUtils.ServerVersion = res.message;
+                SnappingManager.SetServerVersion(res.message);
             });
             !this.IsBeating && this.setIsBeating(true);
         } catch {
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index dcc6aabab..530fcf211 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -4,8 +4,8 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie
 import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
 import { OpenWhereMod } from '../views/nodes/OpenWhere';
 import { VideoBox } from '../views/nodes/VideoBox';
-import { DocumentManager } from './DocumentManager';
 import { Movement, Presentation } from './TrackMovements';
+import { DocumentView } from '../views/nodes/DocumentView';
 
 export class ReplayMovements {
     private timers: NodeJS.Timeout[] | null;
@@ -106,7 +106,7 @@ export class ReplayMovements {
 
     // returns undefined if the docView isn't open on the screen
     getCollectionFFView = (doc: Doc) => {
-        const isInView = DocumentManager.Instance.getDocumentView(doc);
+        const isInView = DocumentView.getDocumentView(doc);
         return isInView?.ComponentView as CollectionFreeFormView;
     };
 
@@ -118,7 +118,7 @@ export class ReplayMovements {
         }
         // console.log('openTab', docId, doc);
         CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
-        const docView = DocumentManager.Instance.getDocumentView(doc);
+        const docView = DocumentView.getDocumentView(doc);
         // BUG - this returns undefined if the doc is already open
         return docView?.ComponentView as CollectionFreeFormView;
     };
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 8df579e75..6948469cc 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -5,8 +5,8 @@
 // import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts'
 // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
 // eslint-disable-next-line node/no-unpublished-import
-import * as ts from 'typescript';
 import * as typescriptlib from '!!raw-loader!./type_decls.d';
+import * as ts from 'typescript';
 import { Doc, FieldType } from '../../fields/Doc';
 import { RefField } from '../../fields/RefField';
 import { ScriptField } from '../../fields/ScriptField';
@@ -195,7 +195,6 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
     if (found) return found as CompiledScript;
     const { requiredType = '', addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options;
     if (options.params && !options.params.this) options.params.this = Doc.name;
-    if (options.params && !options.params.self) options.params.self = Doc.name;
     if (options.globals) {
         ScriptingGlobals.setScriptingGlobals(options.globals);
     }
@@ -266,9 +265,3 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
     !signature.includes('XXX') && ScriptField._scriptFieldCache.set(script + ':' + signature, result as CompiledScript);
     return result;
 }
-
-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/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 7e8f42de9..0b942116c 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,6 +1,5 @@
 import { action, makeObservable, observable, runInAction } from 'mobx';
 import { Doc, Opt } from '../../fields/Doc';
-import { DocViews } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
 import { listSpec } from '../../fields/Schema';
 import { Cast, DocCast } from '../../fields/Types';
@@ -24,6 +23,13 @@ export class SelectionManager {
     private constructor() {
         SelectionManager._manager = this;
         makeObservable(this);
+        DocumentView.DeselectAll = SelectionManager.DeselectAll;
+        DocumentView.DeselectView = SelectionManager.DeselectView;
+        DocumentView.SelectView = SelectionManager.SelectView;
+        DocumentView.SelectedDocs = SelectionManager.Docs;
+        DocumentView.Selected = SelectionManager.Views;
+        DocumentView.SelectSchemaDoc = SelectionManager.SelectSchemaViewDoc;
+        DocumentView.SelectedSchemaDoc = () => this.SelectedSchemaDocument;
     }
 
     @action
@@ -54,8 +60,10 @@ export class SelectionManager {
     public static DeselectAll = (except?: Doc): void => {
         const found = this.Instance.SelectedViews.find(dv => dv.Document === except);
         runInAction(() => {
-            LinkManager.Instance.currentLink = undefined;
-            LinkManager.Instance.currentLinkAnchor = undefined;
+            if (LinkManager.Instance) {
+                LinkManager.Instance.currentLink = undefined;
+                LinkManager.Instance.currentLinkAnchor = undefined;
+            }
             this.Instance.SelectedSchemaDocument = undefined;
         });
         this.Instance.SelectedViews.forEach(dv => {
@@ -68,19 +76,18 @@ export class SelectionManager {
         if (found) this.SelectView(found, false);
     };
 
-    public static IsSelected = (doc?: Doc) => Array.from(doc?.[DocViews] ?? []).some(dv => dv?.IsSelected);
-    public static get Views() { return this.Instance.SelectedViews; } // prettier-ignore
-    public static get SelectedSchemaDoc() { return this.Instance.SelectedSchemaDocument; } // prettier-ignore
-    public static get Docs() { return this.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore
+    public static Views() { return SelectionManager.Instance.SelectedViews; } // prettier-ignore
+    public static get SelectedSchemaDoc() { return SelectionManager.Instance.SelectedSchemaDocument; } // prettier-ignore
+    public static Docs() { return SelectionManager.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 SelectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
     if (Doc.noviceMode && expertMode) return false;
     if (type === 'tab') {
-        return SelectionManager.Views.lastElement()?._props.renderDepth === 0;
+        return DocumentView.Selected().lastElement()?._props.renderDepth === 0;
     }
-    const selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement());
+    const selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(DocumentView.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
     return selected?.type === type || selected?.type_collection === type || !type;
 });
 // eslint-disable-next-line prefer-arrow-callback
@@ -109,8 +116,6 @@ ScriptingGlobals.add(function 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))
-    );
+    const docs = SelectionManager.Docs().filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
     return docs.length ? new List(docs) : prevValue;
 });
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 8cb97fe50..d3c10f9f4 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
 import { BsGoogle } from 'react-icons/bs';
 import { FaFillDrip, FaPalette } from 'react-icons/fa';
 import { ClientUtils, addStyleSheet, addStyleSheetRule } from '../../ClientUtils';
-import { Doc, Opt } from '../../fields/Doc';
+import { Doc } from '../../fields/Doc';
 import { DashVersion } from '../../fields/DocSymbols';
 import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types';
 import { DocServer } from '../DocServer';
@@ -17,7 +17,7 @@ import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager
 import { MainViewModal } from '../views/MainViewModal';
 import { GroupManager } from './GroupManager';
 import './SettingsManager.scss';
-import { SnappingManager } from './SnappingManager';
+import { SnappingManager, freeformScrollMode } from './SnappingManager';
 import { undoable } from './UndoManager';
 
 export enum ColorScheme {
@@ -28,11 +28,6 @@ export enum ColorScheme {
     Cupcake = 'Cupcake',
 }
 
-export enum freeformScrollMode {
-    Pan = 'pan',
-    Zoom = 'zoom',
-}
-
 @observer
 export class SettingsManager extends React.Component<{}> {
     // eslint-disable-next-line no-use-before-define
@@ -44,17 +39,15 @@ export class SettingsManager extends React.Component<{}> {
     @observable private _curr_password = '';
     @observable private _new_password = '';
     @observable private _new_confirm = '';
-    @observable private _lastPressedSidebarBtn: Opt<Doc> = undefined; // bcz: this is a hack to handle highlighting buttons in the leftpanel menu .. need to find a cleaner approach
     @observable private _activeTab = 'Accounts';
     @observable private _isOpen = false;
 
-    @observable public propertiesWidth: number = 0;
-
     private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true));
 
     public closeMgr = action(() => {
         this._isOpen = false;
     });
+    // eslint-disable-next-line react/no-unused-class-component-methods
     public openMgr = action(() => {
         this._isOpen = true;
     });
@@ -154,9 +147,6 @@ export class SettingsManager extends React.Component<{}> {
         SnappingManager.SettingsStyle = SettingsManager._settingsStyle;
     }
 
-    public get LastPressedBtn() { return this._lastPressedSidebarBtn; } // prettier-ignore
-    public set LastPressedBtn(state:Doc|undefined) { this._lastPressedSidebarBtn = state; } // prettier-ignore
-
     @computed public static get userColor() {
         return StrCast(Doc.UserDoc().userColor);
     }
@@ -287,7 +277,7 @@ export class SettingsManager extends React.Component<{}> {
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                 />
-                <Toggle
+                {/* <Toggle
                     formLabel="Show Link Lines"
                     formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
@@ -297,7 +287,7 @@ export class SettingsManager extends React.Component<{}> {
                     toggleStatus={BoolCast(Doc.UserDoc().showLinkLines)}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
-                />
+                /> */}
                 <Group formLabel="Title Height">
                     <NumberDropdown
                         number={NumCast(Doc.UserDoc().headerHeight, 30)}
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index b0140b73c..31a99896f 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -21,11 +21,9 @@ import { DocServer } from '../DocServer';
 import { MainViewModal } from '../views/MainViewModal';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
-import { DocumentManager } from './DocumentManager';
 import { GroupManager, UserOptions } from './GroupManager';
 import { GroupMemberView } from './GroupMemberView';
 import { SearchUtil } from './SearchUtil';
-import { SelectionManager } from './SelectionManager';
 import './SharingManager.scss';
 import { SnappingManager } from './SnappingManager';
 import { undoable } from './UndoManager';
@@ -152,7 +150,7 @@ export class SharingManager extends React.Component<{}> {
         const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
         const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
 
-        let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+        let docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
 
         if (this.myDocAcls) {
             const newDocs: Doc[] = [];
@@ -442,7 +440,7 @@ export class SharingManager extends React.Component<{}> {
         const { user, sharingDoc } = recipient;
         const target = targetDoc || this.targetDoc!;
         const acl = `acl_${normalizeEmail(user.email)}`;
-        const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
+        const docs = DocumentView.Selected().length < 2 ? [target] : DocumentView.Selected().map(docView => docView.Document);
         docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
             distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
             if (permission !== SharingPermissions.None) {
@@ -460,7 +458,7 @@ export class SharingManager extends React.Component<{}> {
         const target = targetDoc || this.targetDoc!;
         const acl = `acl_${normalizeEmail(StrCast(group.title))}`;
 
-        const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
+        const docs = DocumentView.Selected().length < 2 ? [target] : DocumentView.Selected().map(docView => docView.Document);
         docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
             distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
 
@@ -643,14 +641,14 @@ export class SharingManager extends React.Component<{}> {
 
     private focusOn = (contents: string) => {
         const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
-        const docs = SelectionManager.Views.length > 1 ? SelectionManager.Views.map(docView => docView.props.Document) : [this.targetDoc];
+        const docs = DocumentView.Selected().length > 1 ? DocumentView.Selected().map(docView => docView.props.Document) : [this.targetDoc];
         return (
             <span
                 className="focus-span"
                 title={title}
                 onClick={() => {
                     if (this.targetDoc && this.targetDocView && docs.length === 1) {
-                        DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
+                        DocumentView.showDocument(this.targetDoc, { willZoomCentered: true });
                     }
                 }}
                 onPointerEnter={action(() => {
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 5cd6ecfe1..6789c2ab8 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,5 +1,9 @@
 import { observable, action, runInAction, makeObservable } from 'mobx';
 
+export enum freeformScrollMode {
+    Pan = 'pan',
+    Zoom = 'zoom',
+}
 export class SnappingManager {
     // eslint-disable-next-line no-use-before-define
     private static _manager: SnappingManager;
@@ -19,6 +23,9 @@ export class SnappingManager {
     @observable _vertSnapLines: number[] = [];
     @observable _exploreMode = false;
     @observable _userPanned = false;
+    @observable _serverVersion: string = '';
+    @observable _lastBtnId: string = '';
+    @observable _propertyWid: number = 0;
 
     private constructor() {
         SnappingManager._manager = this;
@@ -45,6 +52,10 @@ export class SnappingManager {
     public static get CanEmbed()       { return this.Instance._canEmbed; } // prettier-ignore
     public static get ExploreMode()    { return this.Instance._exploreMode; } // prettier-ignore
     public static get UserPanned()     { return this.Instance._userPanned; } // prettier-ignore
+    public static get ServerVersion()  { return this.Instance._serverVersion; } // prettier-ignore
+    public static get LastPressedBtn() { return this.Instance._lastBtnId; } // prettier-ignore
+    public static get PropertiesWidth(){ return this.Instance._propertyWid; } // 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
@@ -54,7 +65,10 @@ export class SnappingManager {
     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 TriggerUserPanned = () => runInAction(() => {this.Instance._userPanned = !this.Instance._userPanned}); // prettier-ignore
+    public static TriggerUserPanned = ()              => runInAction(() => {this.Instance._userPanned = !this.Instance._userPanned}); // prettier-ignore
+    public static SetServerVersion  = (version:string) =>runInAction(() => {this.Instance._serverVersion = version}); // prettier-ignore
+    public static SetLastPressedBtn = (id:string)      =>runInAction(() => {this.Instance._lastBtnId = id}); // prettier-ignore
+    public static SetPropertiesWidth= (wid:number)     =>runInAction(() => {this.Instance._propertyWid = wid}); // prettier-ignore
 
     public static userColor: string | undefined;
     public static userVariantColor: string | undefined;
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index b1eb730fa..303672d90 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -1,6 +1,6 @@
 import { action, makeObservable, observable, runInAction } from 'mobx';
 import * as React from 'react';
-import { SettingsManager } from '../util/SettingsManager';
+import { SnappingManager } from '../util/SnappingManager';
 import './AntimodeMenu.scss';
 import { ObservableReactComponent } from './ObservableReactComponent';
 
@@ -157,7 +157,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
                     left: this._left,
                     top: this._top,
                     opacity: this._opacity,
-                    background: SettingsManager.userBackgroundColor,
+                    background: SnappingManager.userBackgroundColor,
                     transitionProperty: this._transitionProperty,
                     transitionDuration: this._transitionDuration,
                     transitionDelay: this._transitionDelay,
@@ -183,7 +183,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
                     height: 'inherit',
                     width: 200,
                     opacity: this._opacity,
-                    background: SettingsManager.userBackgroundColor,
+                    background: SnappingManager.userBackgroundColor,
                     transitionProperty: this._transitionProperty,
                     transitionDuration: this._transitionDuration,
                     transitionDelay: this._transitionDelay,
@@ -206,7 +206,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
                     left: this._left,
                     top: this._top,
                     opacity: this._opacity,
-                    background: SettingsManager.userBackgroundColor,
+                    background: SnappingManager.userBackgroundColor,
                     transitionProperty: this._transitionProperty,
                     transitionDuration: this._transitionDuration,
                     transitionDelay: this._transitionDelay,
diff --git a/src/client/views/ComponentDecorations.tsx b/src/client/views/ComponentDecorations.tsx
index 64b8a8446..929b549e0 100644
--- a/src/client/views/ComponentDecorations.tsx
+++ b/src/client/views/ComponentDecorations.tsx
@@ -1,7 +1,7 @@
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { SelectionManager } from '../util/SelectionManager';
 import './ComponentDecorations.scss';
+import { DocumentView } from './nodes/DocumentView';
 
 @observer
 export class ComponentDecorations extends React.Component<{ boundsTop: number; boundsLeft: number }, { value: string }> {
@@ -9,7 +9,7 @@ export class ComponentDecorations extends React.Component<{ boundsTop: number; b
     static Instance: ComponentDecorations;
 
     render() {
-        const seldoc = SelectionManager.Views.lastElement();
+        const seldoc = DocumentView.Selected().lastElement();
         return seldoc?.ComponentView?.componentUI?.(this.props.boundsLeft, this.props.boundsTop) ?? null;
     }
 }
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index a0e3d2ddd..97ff346e4 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -60,6 +60,8 @@ export interface ViewBoxInterface {
     ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
     search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
+    dontRegisterView?: () => boolean; // KeyValueBox's don't want to register their views
+    isUnstyledView?: () => boolean; // SchemaView and KeyValue are untyled -- not tiles, no opacity
 }
 /**
  * DocComponent returns a React base class used by Doc views with accessors for unpacking the Document,layoutDoc, and dataDoc's
diff --git a/src/client/views/DocViewUtils.ts b/src/client/views/DocViewUtils.ts
new file mode 100644
index 000000000..1f5f29c7e
--- /dev/null
+++ b/src/client/views/DocViewUtils.ts
@@ -0,0 +1,21 @@
+/* eslint-disable prefer-destructuring */
+/* eslint-disable default-param-last */
+/* eslint-disable no-use-before-define */
+import { runInAction } from 'mobx';
+import { Doc, SetActiveAudioLinker } from '../../fields/Doc';
+import { DocUtils } from '../documents/DocUtils';
+import { FieldViewProps } from './nodes/FieldView';
+
+export namespace DocViewUtils {
+    export const ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = [];
+
+    export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) {
+        broadcastEvent && runInAction(() => { Doc.RecordingEvent += 1; }); // prettier-ignore
+        return ActiveRecordings.map(audio => {
+            const sourceDoc = getSourceDoc();
+            return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' });
+        });
+    }
+
+    SetActiveAudioLinker(MakeLinkToActiveAudio);
+}
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index dd744f272..487868169 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -14,23 +14,21 @@ import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '
 import { emptyFunction } from '../../Utils';
 import { Doc } from '../../fields/Doc';
 import { Cast, DocCast } from '../../fields/Types';
-import { DocUtils } from '../documents/DocUtils';
+import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils';
 import { CalendarManager } from '../util/CalendarManager';
+import { DictationManager } from '../util/DictationManager';
 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 './DocumentButtonBar.scss';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { PinProps } from './PinFuncs';
 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 } from './nodes/DocumentView';
+import { DocumentView } from './nodes/DocumentView';
 import { OpenWhere } from './nodes/OpenWhere';
 import { DashFieldView } from './nodes/formattedText/DashFieldView';
 
@@ -206,7 +204,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                                     .views()
                                     .filter(v => v)
                                     .map(dv => dv!.Document);
-                                TabDocView.PinDoc(docs, {
+                                DocumentView.PinDoc(docs, {
                                     pinAudioPlay: true,
                                     pinDocLayout: pinLayoutView,
                                     pinData: { dataview: pinContentView },
@@ -221,7 +219,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
             );
         };
         return !targetDoc ? null : (
-            <Tooltip title={<div className="dash-tooltip">{`Pin Document ${SelectionManager.Views.length > 1 ? 'multiple documents' : ''} to Trail`}</div>}>
+            <Tooltip title={<div className="dash-tooltip">{`Pin Document ${DocumentView.Selected().length > 1 ? 'multiple documents' : ''} to Trail`}</div>}>
                 <div
                     className="documentButtonBar-icon documentButtonBar-pin"
                     onClick={e => {
@@ -229,7 +227,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                             .views()
                             .filter(v => v)
                             .map(dv => dv!.Document);
-                        TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: { dataview: e.altKey }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
+                        DocumentView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: { dataview: e.altKey }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
                         e.stopPropagation();
                     }}>
                     <div className="documentButtonBar-pinTypes">
@@ -299,7 +297,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                         this._props.views().map(
                             view =>
                                 view &&
-                                DocumentViewInternal.recordAudioAnnotation(
+                                DictationManager.recordAudioAnnotation(
                                     view.dataDoc,
                                     view.LayoutFieldKey,
                                     stopFunc => {
@@ -381,7 +379,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
     }
 
     openContextMenu = (e: PointerEvent) => {
-        let child = SelectionManager.Views[0].ContentDiv!.children[0];
+        let child = DocumentView.Selected()[0].ContentDiv!.children[0];
         while (child.children.length) {
             const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
             if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break;
@@ -442,14 +440,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 </div>
                 {this._showLinkPopup ? (
                     <div style={{ position: 'absolute', zIndex: 1000 }}>
-                        <LinkPopup
-                            key="popup"
-                            linkCreated={link => {
-                                link.link_displayLine = !IsFollowLinkScript(this._props.views().lastElement()?.Document.onClick);
-                            }}
-                            linkCreateAnchor={() => this._props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
-                            linkFrom={() => this._props.views().lastElement()?.Document}
-                        />
+                        <LinkPopup key="popup" linkCreated={emptyFunction} linkCreateAnchor={() => this._props.views().lastElement()?.ComponentView?.getAnchor?.(true)} linkFrom={() => this._props.views().lastElement()?.Document} />
                     </div>
                 ) : (
                     <div className="documentButtonBar-button">{this.linkButton}</div>
@@ -457,7 +448,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
 
                 {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">{this.endLinkButton} </div> : null}
                 <div className="documentButtonBar-button">{this.templateButton}</div>
-                {!SelectionManager.Views?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
+                {!DocumentView.Selected().some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
                 <div className="documentButtonBar-button">{this.pinButton}</div>
                 <div className="documentButtonBar-button">{this.recordButton}</div>
                 <div className="documentButtonBar-button">{this.calendarButton}</div>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index ef493fb69..432b02782 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -17,9 +17,7 @@ import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { GetEffectiveAcl } from '../../fields/util';
 import { DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
-import { DocumentManager } from '../util/DocumentManager';
 import { DragManager } from '../util/DragManager';
-import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
@@ -81,14 +79,14 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 let inputting = false;
                 if (this._titleControlString.startsWith('$')) {
                     const titleFieldKey = this._titleControlString.substring(1);
-                    if (SelectionManager.Views[0]?.Document[titleFieldKey] !== this._accumulatedTitle) {
+                    if (DocumentView.Selected()[0]?.Document[titleFieldKey] !== this._accumulatedTitle) {
                         inputting = true;
                     }
                 }
                 const center = {x: (this.Bounds.x+this.Bounds.r)/2, y: (this.Bounds.y+this.Bounds.b)/2};
                 const {x,y} = Utils.rotPt(e.clientX - center.x, 
                                           e.clientY - center.y, 
-                                          NumCast(SelectionManager.Views.lastElement()?.screenToViewTransform().Rotate));
+                                          NumCast(DocumentView.Selected().lastElement()?.screenToViewTransform().Rotate));
                 (this._showNothing = !inputting && !DocumentButtonBar.Instance?._tooltipOpen && !(this.Bounds.x !== Number.MAX_VALUE && //
                     (this.Bounds.x > center.x+x || this.Bounds.r < center.x+x || 
                      this.Bounds.y > center.y+y || this.Bounds.b < center.y+y )));
@@ -110,7 +108,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     @computed get Bounds() {
         return (SnappingManager.IsLinkFollowing || SnappingManager.ExploreMode) ? 
             { x: 0, y: 0, r: 0, b: 0 }
-            : SelectionManager.Views
+            : DocumentView.Selected()
                 .filter(dv => dv._props.renderDepth > 0)
                 .map(dv => dv.getBounds)
                 .reduce((bounds, rect) => !rect ? bounds
@@ -127,7 +125,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             this._titleControlString = this._accumulatedTitle;
         } else if (this._titleControlString.startsWith('$')) {
             if (this._accumulatedTitle.startsWith('-->#')) {
-                SelectionManager.Docs.forEach(doc => {
+                DocumentView.SelectedDocs().forEach(doc => {
                     doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`);
                 });
             }
@@ -135,11 +133,11 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             UndoManager.RunInBatch(
                 () =>
                     titleFieldKey &&
-                    SelectionManager.Views.forEach(d => {
+                    DocumentView.Selected().forEach(dv => {
                         if (titleFieldKey === 'title') {
-                            d.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-');
+                            dv.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-');
                         }
-                        KeyValueBox.SetField(d.Document, titleFieldKey, this._accumulatedTitle);
+                        KeyValueBox.SetField(dv.Document, titleFieldKey, this._accumulatedTitle);
                     }),
                 'edit title'
             );
@@ -154,7 +152,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     };
 
     onContainerDown = (e: React.PointerEvent) => {
-        const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document);
+        const effectiveLayoutAcl = GetEffectiveAcl(DocumentView.Selected()[0].Document);
         if (effectiveLayoutAcl === AclAdmin || effectiveLayoutAcl === AclEdit || effectiveLayoutAcl === AclAugment) {
             setupMoveUpEvents(this, e, moveEv => this.onBackgroundMove(true, moveEv), emptyFunction, emptyFunction);
             e.stopPropagation();
@@ -162,7 +160,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     };
 
     onTitleDown = (e: React.PointerEvent) => {
-        const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document);
+        const effectiveLayoutAcl = GetEffectiveAcl(DocumentView.SelectedDocs()[0]);
         if (effectiveLayoutAcl === AclAdmin || effectiveLayoutAcl === AclEdit || effectiveLayoutAcl === AclAugment) {
             setupMoveUpEvents(
                 this,
@@ -170,7 +168,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 moveEv => this.onBackgroundMove(true, moveEv),
                 emptyFunction,
                 action(() => {
-                    const selected = SelectionManager.Views.length === 1 ? SelectionManager.Docs[0] : undefined;
+                    const selected = DocumentView.SelectedDocs().length === 1 ? DocumentView.SelectedDocs()[0] : undefined;
                     !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString);
                     this._editingTitle = true;
                     this._keyinput.current && setTimeout(this._keyinput.current.focus);
@@ -186,19 +184,16 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     };
     @action
     onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
-        const dragDocView = SelectionManager.Views[0];
+        const dragDocView = DocumentView.Selected()[0];
         const effectiveLayoutAcl = GetEffectiveAcl(dragDocView.Document);
         if (effectiveLayoutAcl !== AclAdmin && effectiveLayoutAcl !== AclEdit && effectiveLayoutAcl !== AclAugment) {
             return false;
         }
         const containers = new Set<Doc | undefined>();
-        SelectionManager.Views.forEach(v => containers.add(DocCast(v.Document.embedContainer)));
+        DocumentView.Selected().forEach(v => containers.add(DocCast(v.Document.embedContainer)));
         if (containers.size > 1) return false;
         const { left, top } = dragDocView.getBounds || { left: 0, top: 0 };
-        const dragData = new DragManager.DocumentDragData(
-            SelectionManager.Views.map(dv => dv.Document),
-            dragDocView._props.dropAction
-        );
+        const dragData = new DragManager.DocumentDragData(DocumentView.SelectedDocs(), dragDocView._props.dropAction);
         dragData.offset = dragDocView.screenToContentsTransform().transformDirection(e.x - left, e.y - top);
         dragData.moveDocument = dragDocView._props.moveDocument;
         dragData.removeDocument = dragDocView._props.removeDocument;
@@ -206,7 +201,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         dragData.canEmbed = dragTitle;
         this._hidden = true;
         DragManager.StartDocumentDrag(
-            SelectionManager.Views.map(dv => dv.ContentDiv!),
+            DocumentView.Selected().map(dv => dv.ContentDiv!),
             dragData,
             e.x,
             e.y,
@@ -223,7 +218,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     _deleteAfterIconify = false;
     _iconifyBatch: UndoManager.Batch | undefined;
     onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
-        const views = SelectionManager.Views.filter(v => v && v._props.renderDepth > 0);
+        const views = DocumentView.Selected().filter(v => v && v._props.renderDepth > 0);
         if (forceDeleteOrIconify === false && this._iconifyBatch) return;
         this._deleteAfterIconify = !!(forceDeleteOrIconify || this._iconifyBatch);
         let iconifyingCount = views.length;
@@ -239,7 +234,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                             iconView._props.removeDocument?.(iconView.Document);
                         }
                     });
-                    views.forEach(SelectionManager.DeselectView);
+                    views.forEach(DocumentView.DeselectView);
                 }
                 this._iconifyBatch?.end();
                 this._iconifyBatch = undefined;
@@ -258,53 +253,49 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     };
 
     onMaximizeDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, () => DragManager.StartWindowDrag?.(e, [SelectionManager.Views.lastElement().Document]) ?? false, emptyFunction, this.onMaximizeClick, false, false);
+        setupMoveUpEvents(this, e, () => DragManager.StartWindowDrag?.(e, [DocumentView.SelectedDocs().lastElement()]) ?? false, emptyFunction, this.onMaximizeClick, false, false);
         e.stopPropagation();
     };
     onMaximizeClick = (e: any): void => {
-        const selectedDocs = SelectionManager.Views;
+        const selectedDocs = DocumentView.SelectedDocs();
         if (selectedDocs.length) {
             if (e.ctrlKey) {
                 // open an embedding in a new tab with Ctrl Key
-                CollectionDockingView.AddSplit(Doc.BestEmbedding(selectedDocs[0].Document), OpenWhereMod.right);
+                CollectionDockingView.AddSplit(Doc.BestEmbedding(selectedDocs[0]), OpenWhereMod.right);
             } else if (e.shiftKey) {
                 // open centered in a new workspace with Shift Key
-                const embedding = Doc.MakeEmbedding(selectedDocs[0].Document);
+                const embedding = Doc.MakeEmbedding(selectedDocs[0]);
                 embedding.embedContainer = undefined;
                 embedding.x = -NumCast(embedding._width) / 2;
                 embedding.y = -NumCast(embedding._height) / 2;
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([embedding], { title: 'Tab for ' + embedding.title }), OpenWhereMod.right);
             } else if (e.altKey) {
                 // open same document in new tab
-                CollectionDockingView.ToggleSplit(selectedDocs[0].Document, OpenWhereMod.right);
+                CollectionDockingView.ToggleSplit(selectedDocs[0], OpenWhereMod.right);
             } else {
-                let openDoc = selectedDocs[0].Document;
+                let openDoc = selectedDocs[0];
                 if (openDoc.layout_fieldKey === 'layout_icon') {
                     openDoc = Doc.GetEmbeddings(openDoc).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc);
                     Doc.deiconifyView(openDoc);
                 }
-                LightboxView.Instance.SetLightboxDoc(
-                    openDoc,
-                    undefined,
-                    selectedDocs.slice(1).map(view => view.Document)
-                );
+                LightboxView.Instance.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1));
             }
         }
-        SelectionManager.DeselectAll();
+        DocumentView.DeselectAll();
     };
 
     onIconifyClick = (): void => {
-        SelectionManager.Views.forEach(dv => dv?.iconify());
-        SelectionManager.DeselectAll();
+        DocumentView.Selected().forEach(dv => dv?.iconify());
+        DocumentView.DeselectAll();
     };
 
-    onSelectContainerDocClick = () => SelectionManager.Views?.[0]?.containerViewPath?.().lastElement()?.select(false);
+    onSelectContainerDocClick = () => DocumentView.Selected()?.[0]?.containerViewPath?.().lastElement()?.select(false);
     /**
      * sets up events when user clicks on the border radius editor
      */
     @action
     onRadiusDown = (e: React.PointerEvent): void => {
-        SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()?.[Id]);
+        SnappingManager.SetIsResizing(DocumentView.SelectedDocs().lastElement()?.[Id]);
         this._isRounding = true;
         this._resizeUndo = UndoManager.StartBatch('DocDecs set radius');
         setupMoveUpEvents(
@@ -314,7 +305,7 @@ 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 = moveEv.clientX < x && moveEv.clientY < y ? 0 : Math.sqrt((moveEv.clientX - x) * (moveEv.clientX - x) + (moveEv.clientY - y) * (moveEv.clientY - y));
-                SelectionManager.Docs.forEach(doc => {
+                DocumentView.SelectedDocs().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`;
@@ -339,7 +330,7 @@ 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
-            () => UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => Doc.toggleLockedPosition(doc)), 'toggleBackground')
+            () => UndoManager.RunInBatch(() => DocumentView.Selected().forEach(dv => Doc.toggleLockedPosition(dv.Document)), 'toggleBackground')
         );
         e.stopPropagation();
     };
@@ -356,7 +347,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     @action
     onRotateCenterDown = (e: React.PointerEvent): void => {
         this._isRotating = true;
-        const seldocview = SelectionManager.Views[0];
+        const seldocview = DocumentView.Selected()[0];
         setupMoveUpEvents(
             this,
             e,
@@ -374,11 +365,11 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         this._isRotating = true;
         const rcScreen = { X: this.rotCenter[0], Y: this.rotCenter[1] };
         const rotateUndo = UndoManager.StartBatch('drag rotation');
-        const selectedInk = SelectionManager.Views.filter(i => i.ComponentView instanceof InkingStroke);
+        const selectedInk = DocumentView.Selected().filter(i => i.ComponentView instanceof InkingStroke);
         const centerPoint = this.rotCenter.slice();
         const infos = new Map<Doc, { unrotatedDocPos: { x: number; y: number }; startRotCtr: { x: number; y: number }; accumRot: number }>();
-        const seldocview = SelectionManager.Views[0];
-        SelectionManager.Views.forEach(dv => {
+        const seldocview = DocumentView.Selected()[0];
+        DocumentView.Selected().forEach(dv => {
             const accumRot = (NumCast(dv.Document._rotation) / 180) * Math.PI;
             const localRotCtr = dv.screenToViewTransform().transformPoint(rcScreen.X, rcScreen.Y);
             const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document.width) / 2, localRotCtr[1] - NumCast(dv.Document.height) / 2];
@@ -387,7 +378,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             infos.set(dv.Document, { unrotatedDocPos, startRotCtr, accumRot });
         });
         const infoRot = (angle: number, isAbs = false) => {
-            SelectionManager.Views.forEach(
+            DocumentView.Selected().forEach(
                 action(dv => {
                     const { unrotatedDocPos, startRotCtr, accumRot } = infos.get(dv.Document)!;
                     const endRotCtr = Utils.rotPt(startRotCtr.x, startRotCtr.y, isAbs ? angle : accumRot + angle);
@@ -437,7 +428,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        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
+        SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[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);
@@ -449,7 +440,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         };
         this._resizeUndo = UndoManager.StartBatch('drag resizing');
         this._snapPt = { x: e.pageX, y: e.pageY };
-        SelectionManager.Views.forEach(docView => CollectionFreeFormView.from(docView)?.dragStarting(false, false));
+        DocumentView.Selected().forEach(docView => CollectionFreeFormView.from(docView)?.dragStarting(false, false));
     };
 
     projectDragToAspect = (e: PointerEvent, docView: DocumentView, fixedAspect: number) => {
@@ -467,7 +458,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]);
     };
     onPointerMove = (e: PointerEvent): boolean => {
-        const first = SelectionManager.Views[0];
+        const first = DocumentView.Selected()[0];
         const effectiveAcl = GetEffectiveAcl(first.Document);
         if (!(effectiveAcl === AclAdmin || effectiveAcl === AclEdit || effectiveAcl === AclAugment)) return false;
         if (!first) return false;
@@ -484,10 +475,10 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         !this._interactionLock && runInAction(async () => {   // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate)
             this._interactionLock = true;
             this._snapPt = thisPt;
-            e.ctrlKey && (SelectionManager.Views.forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()));
-            const hasFixedAspect = SelectionManager.Docs.some(this.hasFixedAspect);
+            e.ctrlKey && (DocumentView.Selected().forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()));
+            const hasFixedAspect = DocumentView.Selected().map(dv => dv.Document).some(this.hasFixedAspect);
             const scaleAspect = {x:scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y};
-            SelectionManager.Views.forEach(docView => 
+            DocumentView.Selected().forEach(docView => 
                 this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey:e.ctrlKey })); // prettier-ignore
             await new Promise<any>(res => { setTimeout(() => { res(this._interactionLock = undefined)})});
         }); // prettier-ignore
@@ -526,7 +517,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const doc = docView.Document;
         if (doc.isGroup) {
             DocListCast(doc.data)
-                .map(member => DocumentManager.Instance.getDocumentView(member, docView)!)
+                .map(member => DocumentView.getDocumentView(member, docView)!)
                 .forEach(member => this.resizeView(member, refPt, scale, opts));
             doc.xPadding = NumCast(doc.xPadding) * scale.x;
             doc.yPadding = NumCast(doc.yPadding) * scale.y;
@@ -607,7 +598,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         this._resizeUndo?.end();
 
         // detect layout_autoHeight gesture and apply
-        SelectionManager.Views.forEach(view => {
+        DocumentView.Selected().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.
@@ -626,18 +617,18 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
 
     @computed
     get selectionTitle(): string {
-        if (SelectionManager.Views.length === 1) {
-            const selected = SelectionManager.Views[0];
+        if (DocumentView.Selected().length === 1) {
+            const selected = DocumentView.Selected()[0];
             if (this._titleControlString.startsWith('$')) {
                 return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as FieldType) || '-unset-';
             }
             return this._accumulatedTitle;
         }
-        return SelectionManager.Views.length > 1 ? '-multiple-' : '-unset-';
+        return DocumentView.Selected().length > 1 ? '-multiple-' : '-unset-';
     }
 
     @computed get rotCenter() {
-        const lastView = SelectionManager.Views.lastElement();
+        const lastView = DocumentView.Selected().lastElement();
         if (lastView) {
             const invXf = lastView.screenToContentsTransform().inverse();
             const seldoc = lastView.layoutDoc;
@@ -649,7 +640,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
 
     render() {
         const { b, r, x, y } = this.Bounds;
-        const seldocview = SelectionManager.Views.lastElement();
+        const seldocview = DocumentView.Selected().lastElement();
         if (SnappingManager.IsDragging || r - x < 1 || x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(r) || isNaN(b) || isNaN(x) || isNaN(y)) {
             setTimeout(
                 action(() => {
@@ -678,7 +669,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             hideDecorations ||
             seldocview._props.hideOpenButton ||
             seldocview.Document.layout_hideOpenButton ||
-            SelectionManager.Views.some(docView => docView.Document._dragOnlyWithinContainer || docView.Document.isGroup || docView.Document.layout_hideOpenButton) ||
+            DocumentView.Selected().some(docView => docView.Document._dragOnlyWithinContainer || docView.Document.isGroup || docView.Document.layout_hideOpenButton) ||
             this._isRounding ||
             this._isRotating;
         const hideDeleteButton =
@@ -688,7 +679,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             this._isRotating ||
             seldocview._props.hideDeleteButton ||
             seldocview.Document.hideDeleteButton ||
-            SelectionManager.Views.some(docView => {
+            DocumentView.Selected().some(docView => {
                 const collectionAcl = docView.containerViewPath?.()?.lastElement() ? GetEffectiveAcl(docView.containerViewPath?.().lastElement().dataDoc) : AclEdit;
                 return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.Document) !== AclAdmin;
             });
@@ -703,7 +694,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 && CollectionFreeFormDocumentView.from(seldocview); // when do we want an object to not rotate?
-        const rotation = SelectionManager.Views.length === 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0;
+        const rotation = DocumentView.Selected().length === 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0;
 
         // Radius constants
         const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
@@ -773,7 +764,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         );
         const centery = hideTitle ? 0 : this._titleHeight;
         const transformOrigin = `${50}% calc(50% + ${centery / 2}px)`;
-        const freeformDoc = SelectionManager.Views.some(v => CollectionFreeFormDocumentView.from(v));
+        const freeformDoc = DocumentView.Selected().some(v => CollectionFreeFormDocumentView.from(v));
         return (
             <div className="documentDecorations" style={{ display: this._showNothing && !freeformDoc ? 'none' : undefined }}>
                 <div
@@ -786,7 +777,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                         transformOrigin,
                         background: SnappingManager.ShiftKey ? undefined : 'yellow',
                         pointerEvents: SnappingManager.ShiftKey || SnappingManager.IsResizing ? 'none' : 'all',
-                        display: SelectionManager.Views.length <= 1 || hideDecorations ? 'none' : undefined,
+                        display: DocumentView.Selected().length <= 1 || hideDecorations ? 'none' : undefined,
                         transform: `rotate(${rotation}deg)`,
                     }}
                     onPointerDown={this.onBackgroundDown}
@@ -843,7 +834,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                     style={{
                                         transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
-                                    <DocumentButtonBar views={() => SelectionManager.Views} />
+                                    <DocumentButtonBar views={() => DocumentView.Selected()} />
                                 </div>
                             )}
                         </div>
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
index 3cb7848c2..0ea0ebd83 100644
--- a/src/client/views/FieldsDropdown.tsx
+++ b/src/client/views/FieldsDropdown.tsx
@@ -13,7 +13,7 @@ import Select from 'react-select';
 import { Doc } from '../../fields/Doc';
 import { DocOptions, FInfo } from '../documents/Documents';
 import { SearchUtil } from '../util/SearchUtil';
-import { SettingsManager } from '../util/SettingsManager';
+import { SnappingManager } from '../util/SnappingManager';
 import './FilterPanel.scss';
 import { ObservableReactComponent } from './ObservableReactComponent';
 
@@ -68,36 +68,36 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
                         ...baseStyles,
                         minHeight: '5px',
                         maxHeight: '30px',
-                        color: SettingsManager.userColor,
-                        backgroundColor: SettingsManager.userBackgroundColor,
+                        color: SnappingManager.userColor,
+                        backgroundColor: SnappingManager.userBackgroundColor,
                         padding: 0,
                         margin: 0,
                     }),
                     singleValue: (baseStyles /* , state */) => ({
                         ...baseStyles,
-                        color: SettingsManager.userColor,
-                        background: SettingsManager.userBackgroundColor,
+                        color: SnappingManager.userColor,
+                        background: SnappingManager.userBackgroundColor,
                     }),
                     placeholder: (baseStyles /* , state */) => ({
                         ...baseStyles,
-                        color: SettingsManager.userColor,
-                        background: SettingsManager.userBackgroundColor,
+                        color: SnappingManager.userColor,
+                        background: SnappingManager.userBackgroundColor,
                     }),
                     input: (baseStyles /* , state */) => ({
                         ...baseStyles,
                         padding: 0,
                         margin: 0,
-                        color: SettingsManager.userColor,
+                        color: SnappingManager.userColor,
                         background: 'transparent',
                     }),
                     option: (baseStyles, state) => ({
                         ...baseStyles,
-                        color: SettingsManager.userColor,
-                        background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor,
+                        color: SnappingManager.userColor,
+                        background: !state.isFocused ? SnappingManager.userBackgroundColor : SnappingManager.userVariantColor,
                     }),
                     menuList: (baseStyles /* , state */) => ({
                         ...baseStyles,
-                        backgroundColor: SettingsManager.userBackgroundColor,
+                        backgroundColor: SnappingManager.userBackgroundColor,
                     }),
                 }}
                 placeholder={typeof this._props.placeholder === 'string' ? this._props.placeholder : this._props.placeholder?.()}
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index ed9fc8dfb..c97edd7f0 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -11,12 +11,12 @@ import { Doc, DocListCast, Field, FieldType, LinkedTo, StrListCast } from '../..
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
-import { DocumentManager } from '../util/DocumentManager';
 import { SearchUtil } from '../util/SearchUtil';
-import { SettingsManager } from '../util/SettingsManager';
+import { SnappingManager } from '../util/SnappingManager';
 import { undoable } from '../util/UndoManager';
 import { FieldsDropdown } from './FieldsDropdown';
 import './FilterPanel.scss';
+import { DocumentView } from './nodes/DocumentView';
 import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components';
 import { ObservableReactComponent } from './ObservableReactComponent';
 
@@ -40,7 +40,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         return this._props.Document;
     }
     @computed get targetDocChildKey() {
-        const targetView = DocumentManager.Instance.getFirstDocumentView(this.Document);
+        const targetView = DocumentView.getFirstDocumentView(this.Document);
         return targetView?.ComponentView?.annotationKey ?? targetView?.ComponentView?.fieldKey ?? 'data';
     }
     @computed get targetDocChildren() {
@@ -299,7 +299,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                                 .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader)
                                 ?.split(Doc.FilterSep)[1]
                         }
-                        style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}
+                        style={{ color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
                         onBlur={undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')}
                         onKeyDown={e => e.key === 'Enter' && undoable(() => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')()}
                     />
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 0d1573ea3..7246f0ba0 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -65,8 +65,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     @observable private _clipboardDoc?: JSX.Element = undefined;
     @observable private _possibilities: JSX.Element[] = [];
 
-    public static DownDocView: DocumentView | undefined;
-
     @computed private get height(): number {
         return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 100, 100);
     }
@@ -136,7 +134,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     }
     @action
     onPointerUp = () => {
-        GestureOverlay.DownDocView = undefined;
+        DocumentView.DownDocView = undefined;
         if (this._points.length > 1) {
             const B = this.svgBounds;
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
@@ -388,7 +386,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     }
 
     get elements() {
-        const selView = GestureOverlay.DownDocView;
+        const selView = DocumentView.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);
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 0f444094a..c73da35bc 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -8,7 +8,6 @@ import { Cast, PromiseValue } from '../../fields/Types';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DragManager } from '../util/DragManager';
 import { GroupManager } from '../util/GroupManager';
-import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
 import { SnappingManager } from '../util/SnappingManager';
@@ -23,6 +22,7 @@ import { CollectionStackedTimeline } from './collections/CollectionStackedTimeli
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
+import { DocumentView } from './nodes/DocumentView';
 import { OpenWhereMod } from './nodes/OpenWhere';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { AnchorMenu } from './pdf/AnchorMenu';
@@ -34,10 +34,12 @@ type KeyControlInfo = {
 };
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo;
 export class KeyManager {
-    public static Instance = new KeyManager();
+    // eslint-disable-next-line no-use-before-define
+    public static Instance: KeyManager;
     private router = new Map<string, KeyHandler>();
 
     constructor() {
+        KeyManager.Instance = this;
         const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;
 
         // SHIFT CONTROL ALT META
@@ -46,6 +48,16 @@ export class KeyManager {
         this.router.set(isMac ? '0100' : '0010', this.alt);
         this.router.set(isMac ? '1001' : '1100', this.ctrl_shift);
         this.router.set('1000', this.shift);
+
+        window.removeEventListener('keydown', KeyManager.Instance.handleModifiers, true);
+        window.addEventListener('keydown', KeyManager.Instance.handleModifiers, true);
+        window.removeEventListener('keyup', KeyManager.Instance.unhandleModifiers);
+        window.addEventListener('keyup', KeyManager.Instance.unhandleModifiers);
+        window.removeEventListener('keydown', KeyManager.Instance.handle);
+        window.addEventListener('keydown', KeyManager.Instance.handle);
+        window.removeEventListener('keyup', KeyManager.Instance.unhandle);
+        window.addEventListener('keyup', KeyManager.Instance.unhandle);
+        window.addEventListener('paste', KeyManager.Instance.paste as any);
     }
 
     public unhandle = action((/* e: KeyboardEvent */) => {
@@ -89,8 +101,8 @@ export class KeyManager {
     private handleGreedy = action((/* keyname: string */) => {});
 
     nudge = (x: number, y: number, label: string) => {
-        const nudgeable = SelectionManager.Views.some(dv => CollectionFreeFormDocumentView.from(dv)?.nudge);
-        nudgeable && UndoManager.RunInBatch(() => SelectionManager.Views.map(dv => CollectionFreeFormDocumentView.from(dv)?.nudge(x, y)), label);
+        const nudgeable = DocumentView.Selected().some(dv => CollectionFreeFormDocumentView.from(dv)?.nudge);
+        nudgeable && UndoManager.RunInBatch(() => DocumentView.Selected().map(dv => CollectionFreeFormDocumentView.from(dv)?.nudge(x, y)), label);
         return { stopPropagation: nudgeable, preventDefault: nudgeable };
     };
 
@@ -98,16 +110,16 @@ export class KeyManager {
         switch (keyname) {
             case 'u':
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
-                    const ungroupings = SelectionManager.Views;
+                    const ungroupings = DocumentView.Selected();
                     undoable(() => () => ungroupings.forEach(dv => { dv.layoutDoc.group = undefined; }), 'ungroup');
-                    SelectionManager.DeselectAll();
+                    DocumentView.DeselectAll();
                 }
                 break;
             case 'g':
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
-                    const selected = SelectionManager.Views;
+                    const selected = DocumentView.Selected();
                     const cv = selected.reduce((col, dv) => (!col || CollectionFreeFormView.from(dv) === col ? CollectionFreeFormView.from(dv) : undefined), undefined as undefined | CollectionFreeFormView);
-                    cv && undoable(() => cv._marqueeViewRef.current?.collection(e, true, SelectionManager.Docs), 'grouping');
+                    cv && undoable(() => cv._marqueeViewRef.current?.collection(e, true, DocumentView.SelectedDocs()), 'grouping');
                 }
                 break;
             case ' ':
@@ -133,7 +145,7 @@ export class KeyManager {
                         doDeselect = !ContextMenu.Instance.closeMenu();
                     }
                     if (doDeselect) {
-                        SelectionManager.DeselectAll();
+                        DocumentView.DeselectAll();
                         LightboxView.Instance.SetLightboxDoc(undefined);
                     }
                     // DictationManager.Controls.stop();
@@ -153,7 +165,7 @@ export class KeyManager {
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
                     if (LightboxView.LightboxDoc) {
                         LightboxView.Instance.SetLightboxDoc(undefined);
-                        SelectionManager.DeselectAll();
+                        DocumentView.DeselectAll();
                     } else if (!window.getSelection()?.toString()) DocumentDecorations.Instance.onCloseClick(true);
                     return { stopPropagation: true, preventDefault: true };
                 }
@@ -179,15 +191,15 @@ export class KeyManager {
             case 'arrowdown':  return this.nudge(0, 10, 'nudge down');
             case 'u' : 
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
-                    UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => {doc.group = undefined}), 'unggroup');
-                    SelectionManager.DeselectAll();
+                    UndoManager.RunInBatch(() => DocumentView.Selected().map(dv => dv.Document).forEach(doc => {doc.group = undefined}), 'unggroup');
+                    DocumentView.DeselectAll();
                 }
                 break;
             case 'g':
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
                     const randomGroup = random(0, 1000);
-                    UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => {doc.group = randomGroup}), 'group');
-                    SelectionManager.DeselectAll();
+                    UndoManager.RunInBatch(() => DocumentView.Selected().forEach(dv => {dv.Document.group = randomGroup}), 'group');
+                    DocumentView.DeselectAll();
                 }
                 break;
             default: 
@@ -206,7 +218,7 @@ export class KeyManager {
         switch (keyname) {
             case 'ƒ':
             case 'f':
-                UndoManager.RunInBatch(() => CollectionFreeFormDocumentView.from(SelectionManager.Views?.[0])?.float(), 'float');
+                UndoManager.RunInBatch(() => CollectionFreeFormDocumentView.from(DocumentView.Selected()?.[0])?.float(), 'float');
                 break;
             default:
         }
@@ -259,8 +271,8 @@ export class KeyManager {
                 }
                 break;
             case 'f':
-                if (SelectionManager.Views.length === 1 && SelectionManager.Views[0].ComponentView?.search) {
-                    SelectionManager.Views[0].ComponentView?.search?.('', false, false);
+                if (DocumentView.Selected().length === 1 && DocumentView.Selected()[0].ComponentView?.search) {
+                    DocumentView.Selected()[0].ComponentView?.search?.('', false, false);
                 } else {
                     const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher);
                     if (searchBtn) {
@@ -279,14 +291,14 @@ export class KeyManager {
                 break;
             case 'y':
                 if (Doc.ActivePage !== 'home') {
-                    SelectionManager.DeselectAll();
+                    DocumentView.DeselectAll();
                     UndoManager.Redo();
                 }
                 stopPropagation = false;
                 break;
             case 'z':
                 if (Doc.ActivePage !== 'home') {
-                    SelectionManager.DeselectAll();
+                    DocumentView.DeselectAll();
                     UndoManager.Undo();
                 }
                 stopPropagation = false;
@@ -302,11 +314,17 @@ export class KeyManager {
                 preventDefault = false;
                 break;
             case 'x':
-                if (SelectionManager.Views.length) {
+                if (DocumentView.Selected().length) {
                     const bds = DocumentDecorations.Instance.Bounds;
-                    const pt = SelectionManager.Views[0].screenToViewTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
-                    const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views.map(dv => dv.Document[Id]).join(':');
-                    SelectionManager.Views.length && navigator.clipboard.writeText(text);
+                    const pt = DocumentView.Selected()[0] //
+                        .screenToViewTransform()
+                        .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
+                    const text = 
+                        `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + //
+                        DocumentView.Selected() 
+                            .map(dv => dv.Document[Id])
+                            .join(':'); // prettier-ignore
+                    DocumentView.Selected().length && navigator.clipboard.writeText(text);
                     DocumentDecorations.Instance.onCloseClick(true);
                     stopPropagation = false;
                     preventDefault = false;
@@ -315,9 +333,15 @@ export class KeyManager {
             case 'c':
                 if ((document.activeElement as any)?.type !== 'text' && !AnchorMenu.Instance.Active && DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) {
                     const bds = DocumentDecorations.Instance.Bounds;
-                    const pt = SelectionManager.Views[0].screenToViewTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
-                    const text = `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views.map(dv => dv.Document[Id]).join(':');
-                    SelectionManager.Views.length && navigator.clipboard.writeText(text);
+                    const pt = DocumentView.Selected()[0]
+                        .screenToViewTransform()
+                        .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); // prettier-ignore
+                    const text =
+                        `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` +
+                        DocumentView.SelectedDocs()
+                            .map(doc => doc[Id])
+                            .join(':'); // prettier-ignore
+                    DocumentView.Selected().length && navigator.clipboard.writeText(text);
                     stopPropagation = false;
                 }
                 preventDefault = false;
@@ -336,7 +360,7 @@ export class KeyManager {
         if (!plain) return;
         const clone = plain.startsWith('__DashCloneId(');
         const docids = plain.split(':'); // hack!  docids[0] is the top left of the selection rectangle
-        const addDocument = SelectionManager.Views.lastElement()?.ComponentView?.addDocument;
+        const addDocument = DocumentView.Selected().lastElement()?.ComponentView?.addDocument;
         if (addDocument && (plain.startsWith('__DashDocId(') || clone)) {
             Doc.Paste(docids.slice(1), clone, addDocument);
         }
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index b4fe44733..a49923ffb 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -7,7 +7,6 @@ import { List } from '../../fields/List';
 import { listSpec } from '../../fields/Schema';
 import { Cast } from '../../fields/Types';
 import { returnFalse, setupMoveUpEvents } from '../../ClientUtils';
-import { SelectionManager } from '../util/SelectionManager';
 import { UndoManager } from '../util/UndoManager';
 import { Colors } from './global/globalEnums';
 import { InkingStroke } from './InkingStroke';
@@ -15,6 +14,7 @@ import { InkStrokeProperties } from './InkStrokeProperties';
 import { SnappingManager } from '../util/SnappingManager';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { PointData } from '../../pen-gestures/GestureTypes';
+import { DocumentView } from './nodes/DocumentView';
 
 export interface InkControlProps {
     inkDoc: Doc;
@@ -224,8 +224,8 @@ export class InkEndPtHandles extends ObservableReactComponent<InkEndProps> {
                 const v1n = { X: v1.X / v1len, Y: v1.Y / v1len };
                 const v2n = { X: v2.X / v2len, Y: v2.Y / v2len };
                 const angle = Math.acos(v1n.X * v2n.X + v1n.Y * v2n.Y) * Math.sign(v1.X * v2.Y - v2.X * v1.Y);
-                InkStrokeProperties.Instance.stretchInk(SelectionManager.Views, scaling, p2, v1n, moveEv.shiftKey);
-                InkStrokeProperties.Instance.rotateInk(SelectionManager.Views, angle, pt2()); // bcz: call pt2() func here because pt2 will have changed from previous stretchInk call
+                InkStrokeProperties.Instance.stretchInk(DocumentView.Selected(), scaling, p2, v1n, moveEv.shiftKey);
+                InkStrokeProperties.Instance.rotateInk(DocumentView.Selected(), angle, pt2()); // bcz: call pt2() func here because pt2 will have changed from previous stretchInk call
                 return false;
             }),
             action(() => {
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 109a9cad4..3920ecc2a 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -9,7 +9,6 @@ import { Cast, NumCast } from '../../fields/Types';
 import { PointData } from '../../pen-gestures/GestureTypes';
 import { Point } from '../../pen-gestures/ndollar';
 import { DocumentType } from '../documents/DocumentTypes';
-import { DocumentManager } from '../util/DocumentManager';
 import { undoBatch } from '../util/UndoManager';
 import { FitOneCurve } from '../util/bezierFit';
 import { InkingStroke } from './InkingStroke';
@@ -386,7 +385,7 @@ export class InkStrokeProperties {
         containingCollection?.childDocs
             .filter(doc => doc.type === DocumentType.INK)
             .forEach(doc => {
-                const testInkView = DocumentManager.Instance.getDocumentView(doc, containingDocView);
+                const testInkView = DocumentView.getDocumentView(doc, containingDocView);
                 const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.Document ? this.excludeSelfSnapSegs(ink, controlIndex) : []);
                 if (snapped && snapped.distance < snapData.distance) {
                     const snappedInkPt = doc === inkView.Document ? snapped.nearestPt : inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 7e2b334af..1ed8de1be 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -6,7 +6,6 @@
 // import { Cast, DateCast, NumCast } from '../../fields/Types';
 // import { aggregateBounds } from '../../Utils';
 // import { DocumentType } from '../documents/DocumentTypes';
-// import { DocumentManager } from '../util/DocumentManager';
 // import { CollectionFreeFormView } from './collections/collectionFreeForm';
 // import { InkingStroke } from './InkingStroke';
 // import './InkTranscription.scss';
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 884512539..62fc73c78 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -44,7 +44,7 @@ import { InkTangentHandles } from './InkTangentHandles';
 import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { PinDocView, PinProps } from './PinFuncs';
-import { StyleProp } from './StyleProvider';
+import { StyleProp } from './StyleProp';
 
 const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 020525ef8..12d899388 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -13,10 +13,6 @@ import { CreateLinkToActiveAudio, Doc, DocListCast, FieldResult, Opt } from '../
 import { Id } from '../../fields/FieldSymbols';
 import { InkTool } from '../../fields/InkField';
 import { Cast, NumCast, toList } from '../../fields/Types';
-import { DocumentManager } from '../util/DocumentManager';
-import { LinkManager } from '../util/LinkManager';
-import { SelectionManager } from '../util/SelectionManager';
-import { SettingsManager } from '../util/SettingsManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
 import { GestureOverlay } from './GestureOverlay';
@@ -24,8 +20,6 @@ import './LightboxView.scss';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
 import { CollectionDockingView } from './collections/CollectionDockingView';
-import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
-import { TabDocView } from './collections/TabDocView';
 import { DocumentView } from './nodes/DocumentView';
 import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
 
@@ -90,7 +84,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                 });
             const l = CreateLinkToActiveAudio(() => doc).lastElement();
             l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
-            CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
+            DocumentView.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
             this._history.push({ doc, target });
         } else {
             this._future = [];
@@ -98,14 +92,14 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
             Doc.ActiveTool = InkTool.None;
             SnappingManager.SetExploreMode(false);
         }
-        SelectionManager.DeselectAll();
+        DocumentView.DeselectAll();
         if (future) {
             this._future.push(
                 ...(this._doc ? [this._doc] : []),
                 ...future
                     .slice()
                     .sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow))
-                    .sort((a, b) => LinkManager.Links(a).length - LinkManager.Links(b).length)
+                    .sort((a, b) => Doc.Links(a).length - Doc.Links(b).length)
             );
         }
         this._doc = doc;
@@ -134,11 +128,11 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
         const lightDoc = this._doc;
         if (!lightDoc) return;
         const target = (this._docTarget = this._future.pop());
-        const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+        const targetDocView = target && DocumentView.getLightboxDocumentView(target);
         if (targetDocView && target) {
             const l = CreateLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
             l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
-            DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
+            DocumentView.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 => {
@@ -157,10 +151,10 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
             return;
         }
         const { doc, target } = this._history.lastElement();
-        const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
+        const docView = DocumentView.getLightboxDocumentView(target || doc);
         if (docView) {
             this._docTarget = target;
-            target && DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
+            target && DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
         } else {
             this.SetLightboxDoc(doc, target);
         }
@@ -178,8 +172,8 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
         if (this._docTarget) {
             const fieldKey = Doc.LayoutFieldKey(this._docTarget);
             const contents = [...DocListCast(this._docTarget[fieldKey]), ...DocListCast(this._docTarget[fieldKey + '_annotations'])];
-            const links = LinkManager.Links(this._docTarget)
-                .map(link => LinkManager.getOppositeAnchor(link, this._docTarget!)!)
+            const links = Doc.Links(this._docTarget)
+                .map(link => Doc.getOppositeAnchor(link, this._docTarget!)!)
                 .filter(doc => doc);
             this.SetLightboxDoc(this._docTarget, undefined, contents.length ? contents : links);
         }
@@ -220,7 +214,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
             <div
                 className="lightboxView-navBtn"
                 title={color}
-                style={{ top, color: SettingsManager.userColor, background: undefined }}
+                style={{ top, color: SnappingManager.userColor, background: undefined }}
                 onClick={e => {
                     e.stopPropagation();
                     click();
@@ -237,8 +231,8 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
             <div className={classname}>
                 <Toggle
                     tooltip={tooltip}
-                    color={SettingsManager.userColor}
-                    background={toggleBackground ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
+                    color={SnappingManager.userColor}
+                    background={toggleBackground ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
                     toggleType={ToggleType.BUTTON}
                     type={Type.TERT}
                     icon={<FontAwesomeIcon icon={toggleBackground ? icon : (icon2 as IconProp) || icon} size="sm" />}
@@ -252,7 +246,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
         return !this._doc ? null : (
             <div
                 className="lightboxView-frame"
-                style={{ background: SettingsManager.userBackgroundColor }}
+                style={{ background: SnappingManager.userBackgroundColor }}
                 onPointerDown={e => {
                     downx = e.clientX;
                     downy = e.clientY;
@@ -266,7 +260,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                         width: this.lightboxWidth(),
                         height: this.lightboxHeight(),
                         clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`,
-                        background: SettingsManager.userBackgroundColor,
+                        background: SnappingManager.userBackgroundColor,
                     }}>
                     <GestureOverlay isActive>
                         <DocumentView
@@ -292,7 +286,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                             removeDocument={undefined}
                             whenChildContentsActiveChanged={emptyFunction}
                             addDocTab={this.AddDocTab}
-                            pinToPres={TabDocView.PinDoc}
+                            pinToPres={DocumentView.PinDoc}
                             focus={emptyFunction}
                         />
                     </GestureOverlay>
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 01f3b032e..b6dfed687 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -8,14 +8,17 @@ import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
 import { AssignAllExtensions } from '../../extensions/Extensions';
 import { FieldLoader } from '../../fields/FieldLoader';
+import { BranchingTrailManager } from '../util/BranchingTrailManager';
 import { CurrentUserUtils } from '../util/CurrentUserUtils';
 import { PingManager } from '../util/PingManager';
 import { ReplayMovements } from '../util/ReplayMovements';
 import { TrackMovements } from '../util/TrackMovements';
+import { KeyManager } from './GlobalKeyHandler';
+import { MainView } from './MainView';
 import { CollectionView } from './collections/CollectionView';
+import { CollectionFreeFormInfoUI } from './collections/collectionFreeForm/CollectionFreeFormInfoUI';
 import './global/globalScripts';
-import { MainView } from './MainView';
-import { BranchingTrailManager } from '../util/BranchingTrailManager';
+import { LinkFollower } from '../util/LinkFollower';
 
 dotenv.config();
 
@@ -58,6 +61,11 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
         new ReplayMovements();
         new BranchingTrailManager({});
         new PingManager();
+        new KeyManager();
+
+        // iniitialize plugin apis
+        CollectionFreeFormInfoUI.Init();
+        LinkFollower.Init();
         root.render(<MainView />);
     }, 0);
 })();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index aec553baa..2c7d3d32a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -12,7 +12,8 @@ import '../../../node_modules/browndash-components/dist/styles/global.min.css';
 import { ClientUtils, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
 import { Doc, DocListCast, GetDocFromUrl, Opt } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
+import { DocData, DocViews } from '../../fields/DocSymbols';
+import { Id } from '../../fields/FieldSymbols';
 import { DocCast, StrCast, toList } from '../../fields/Types';
 import { DocServer } from '../DocServer';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
@@ -29,7 +30,6 @@ 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';
 import { ServerStats } from '../util/ServerStats';
 import { SettingsManager } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
@@ -42,7 +42,6 @@ import { DashboardView } from './DashboardView';
 import { DictationOverlay } from './DictationOverlay';
 import { DocumentDecorations } from './DocumentDecorations';
 import { GestureOverlay } from './GestureOverlay';
-import { KeyManager } from './GlobalKeyHandler';
 import { LightboxView } from './LightboxView';
 import './MainView.scss';
 import { ObservableReactComponent } from './ObservableReactComponent';
@@ -160,7 +159,7 @@ export class MainView extends ObservableReactComponent<{}> {
     leftMenuHeight = () => this._dashUIHeight;
     leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
     leftMenuFlyoutHeight = () => this._dashUIHeight;
-    propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SettingsManager.Instance?.propertiesWidth || 0));
+    propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SnappingManager.PropertiesWidth || 0));
     propertiesHeight = () => this._dashUIHeight;
     mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth();
     mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
@@ -169,7 +168,7 @@ export class MainView extends ObservableReactComponent<{}> {
         // Utils.TraceConsoleLog();
         reaction(
             // when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection
-            () => SelectionManager.Views.slice(),
+            () => DocumentView.Selected().slice(),
             views => views.length > 1 && (document.activeElement as any)?.blur !== undefined && (document.activeElement as any)!.blur()
         );
         const scriptTag = document.createElement('script');
@@ -231,37 +230,29 @@ export class MainView extends ObservableReactComponent<{}> {
         tag.src = 'https://www.youtube.com/iframe_api';
         const firstScriptTag = document.getElementsByTagName('script')[0];
         firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
-        window.removeEventListener('keydown', KeyManager.Instance.handleModifiers, true);
-        window.addEventListener('keydown', KeyManager.Instance.handleModifiers, true);
-        window.removeEventListener('keyup', KeyManager.Instance.unhandleModifiers);
-        window.addEventListener('keyup', KeyManager.Instance.unhandleModifiers);
-        window.removeEventListener('keydown', KeyManager.Instance.handle);
-        window.addEventListener('keydown', KeyManager.Instance.handle);
-        window.removeEventListener('keyup', KeyManager.Instance.unhandle);
-        window.addEventListener('keyup', KeyManager.Instance.unhandle);
-        window.addEventListener('paste', KeyManager.Instance.paste as any);
         document.addEventListener('dash', (e: any) => {
             // event used by chrome plugin to tell Dash which document to focus on
             const id = GetDocFromUrl(e.detail);
-            DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.showDocument(doc, { willPan: false }) : null));
+            DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentView.showDocument(doc, { willPan: false }) : null));
         });
         document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener);
         this.initEventListeners();
     }
 
     componentWillUnMount() {
-        window.removeEventListener('keyup', KeyManager.Instance.unhandle);
-        window.removeEventListener('keydown', KeyManager.Instance.handle);
-        window.removeEventListener('pointerdown', this.globalPointerDown, true);
-        window.removeEventListener('pointermove', this.globalPointerMove, true);
-        window.removeEventListener('pointerup', this.globalPointerClick, true);
-        window.removeEventListener('paste', KeyManager.Instance.paste as any);
-        document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener);
+        // window.removeEventListener('keyup', KeyManager.Instance.unhandle);
+        // window.removeEventListener('keydown', KeyManager.Instance.handle);
+        // window.removeEventListener('pointerdown', this.globalPointerDown, true);
+        // window.removeEventListener('pointermove', this.globalPointerMove, true);
+        // window.removeEventListener('pointerup', this.globalPointerClick, true);
+        // window.removeEventListener('paste', KeyManager.Instance.paste as any);
+        // document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener);
     }
 
     constructor(props: any) {
         super(props);
         makeObservable(this);
+        CollectionDockingView.setTabJSXComponent(TabDocView);
         DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl;
         MainView.Instance = this;
         DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any);
@@ -592,7 +583,7 @@ export class MainView extends ObservableReactComponent<{}> {
                 if (!e.cancelBubble) {
                     const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join();
                     if (pathstr?.includes('libraryFlyout')) {
-                        SelectionManager.DeselectAll();
+                        DocumentView.DeselectAll();
                     }
                 }
             },
@@ -719,14 +710,14 @@ export class MainView extends ObservableReactComponent<{}> {
             this,
             e,
             action(() => {
-                SettingsManager.Instance.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX);
-                return !SettingsManager.Instance.propertiesWidth;
+                SnappingManager.SetPropertiesWidth(Math.max(0, this._dashUIWidth - e.clientX));
+                return !SnappingManager.PropertiesWidth;
             }),
             action(() => {
-                SettingsManager.Instance.propertiesWidth < 5 && (SettingsManager.Instance.propertiesWidth = 0);
+                SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
             }),
             action(() => {
-                SettingsManager.Instance.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0;
+                SnappingManager.SetPropertiesWidth(this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0);
             }),
             false
         );
@@ -776,7 +767,7 @@ export class MainView extends ObservableReactComponent<{}> {
                         Document={this._sidebarContent.proto || this._sidebarContent}
                         addDocument={undefined}
                         addDocTab={DocumentViewInternal.addDocTabFunc}
-                        pinToPres={TabDocView.PinDoc}
+                        pinToPres={DocumentView.PinDoc}
                         containerViewPath={returnEmptyDoclist}
                         styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider}
                         removeDocument={returnFalse}
@@ -799,7 +790,7 @@ export class MainView extends ObservableReactComponent<{}> {
 
     @computed get leftMenuPanel() {
         return (
-            <div key="menu" className="mainView-leftMenuPanel" style={{ background: SettingsManager.userBackgroundColor, display: LightboxView.LightboxDoc ? 'none' : undefined }}>
+            <div key="menu" className="mainView-leftMenuPanel" style={{ background: SnappingManager.userBackgroundColor, display: LightboxView.LightboxDoc ? 'none' : undefined }}>
                 <DocumentView
                     Document={Doc.MyLeftSidebarMenu}
                     addDocument={undefined}
@@ -852,9 +843,9 @@ export class MainView extends ObservableReactComponent<{}> {
                     {this.flyout}
                     <div
                         className="mainView-libraryHandle"
-                        style={{ background: SettingsManager.userBackgroundColor, left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }}
+                        style={{ background: SnappingManager.userBackgroundColor, left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? 'none' : undefined }}
                         onPointerDown={this.onFlyoutPointerDown}>
-                        <FontAwesomeIcon icon="chevron-left" color={SettingsManager.userColor} style={{ opacity: '50%' }} size="sm" />
+                        <FontAwesomeIcon icon="chevron-left" color={SnappingManager.userColor} style={{ opacity: '50%' }} size="sm" />
                     </div>
                     <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
                         {this.dockingContent}
@@ -864,11 +855,11 @@ export class MainView extends ObservableReactComponent<{}> {
                                 className={`mainView-propertiesDragger${this.propertiesWidth() < 10 ? '-minified' : ''}`}
                                 key="props"
                                 onPointerDown={this.onPropertiesPointerDown}
-                                style={{ background: SettingsManager.userVariantColor, right: this.propertiesWidth() - 1 }}>
-                                <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={SettingsManager.userColor} size="sm" />
+                                style={{ background: SnappingManager.userVariantColor, right: this.propertiesWidth() - 1 }}>
+                                <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? 'chevron-left' : 'chevron-right'} color={SnappingManager.userColor} size="sm" />
                             </div>
                         )}
-                        <div className="properties-container" style={{ width: this.propertiesWidth(), color: SettingsManager.userColor }}>
+                        <div className="properties-container" style={{ width: this.propertiesWidth(), color: SnappingManager.userColor }}>
                             <div style={{ display: this.propertiesWidth() < 10 ? 'none' : undefined }}>
                                 <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />
                             </div>
@@ -911,11 +902,11 @@ export class MainView extends ObservableReactComponent<{}> {
         // setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5)));
 
         this._sidebarContent.proto = DocCast(button.target);
-        SettingsManager.Instance.LastPressedBtn = button;
+        SnappingManager.SetLastPressedBtn(button[Id]);
     });
 
     closeFlyout = action(() => {
-        SettingsManager.Instance.LastPressedBtn = undefined;
+        SnappingManager.SetLastPressedBtn('');
         this._panelContent = 'none';
         this._sidebarContent.proto = undefined;
         this._leftMenuFlyoutWidth = 0;
@@ -933,7 +924,7 @@ export class MainView extends ObservableReactComponent<{}> {
 
     @computed get docButtons() {
         return !Doc.MyDockedBtns ? null : (
-            <div className="mainView-docButtons" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }} ref={this._docBtnRef}>
+            <div className="mainView-docButtons" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }} ref={this._docBtnRef}>
                 <CollectionLinearView
                     Document={Doc.MyDockedBtns}
                     docViewPath={returnEmptyDocViewList}
@@ -964,8 +955,8 @@ export class MainView extends ObservableReactComponent<{}> {
         );
     }
     @computed get snapLines() {
-        const dragged = DragManager.docsBeingDragged.lastElement() ?? SelectionManager.Docs.lastElement();
-        const dragPar = dragged ? CollectionFreeFormView.from(DocumentManager.Instance.getDocumentView(dragged)) : undefined;
+        const dragged = DragManager.docsBeingDragged.lastElement() ?? DocumentView.SelectedDocs().lastElement();
+        const dragPar = dragged ? CollectionFreeFormView.from(Array.from(dragged[DocViews]).lastElement()) : undefined;
         return !dragPar?.layoutDoc.freeform_snapLines ? null : (
             <div className="mainView-snapLines">
                 <svg style={{ width: '100%', height: '100%' }}>
@@ -1010,14 +1001,22 @@ export class MainView extends ObservableReactComponent<{}> {
         );
     }
 
+    togglePropertiesFlyout = () => {
+        if (MainView.Instance.propertiesWidth() > 0) {
+            SnappingManager.SetPropertiesWidth(0);
+        } else {
+            SnappingManager.SetPropertiesWidth(300);
+        }
+    };
+
     lightboxMaxBorder = [200, 50];
     render() {
         return (
             <div
                 className="mainView-container"
                 style={{
-                    color: SettingsManager.userColor,
-                    background: SettingsManager.userBackgroundColor,
+                    color: SnappingManager.userColor,
+                    background: SnappingManager.userBackgroundColor,
                 }}
                 onScroll={() =>
                     (ele => {
@@ -1067,7 +1066,7 @@ export class MainView extends ObservableReactComponent<{}> {
                         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}/>
+                                            <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} togglePropertiesFlyout={this.togglePropertiesFlyout} toggleTopBar={this.toggleTopBar} topBarHeight={this.headerBarHeightFunc}/>
                                         </div>
                                         {this.mainDashboardArea}
                                     </> );
@@ -1086,7 +1085,7 @@ export class MainView extends ObservableReactComponent<{}> {
                 {/* <InkTranscription /> */}
                 {this.snapLines}
                 <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={this.lightboxMaxBorder} />
-                <OverlayView />
+                {LightboxView.LightboxDoc ? null : <OverlayView />}
                 <GPTPopup key="gptpopup" />
                 <SchemaCSVPopUp key="schemacsvpopup" />
                 <GenerativeFill imageEditorOpen={ImageEditor.Open} imageEditorSource={ImageEditor.Source} imageRootDoc={ImageEditor.RootDoc} addDoc={ImageEditor.AddDoc} />
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 0f13de9e5..a917a7d57 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -227,7 +227,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
                               const linkDocData = dragEx.linkDocument[DocData];
                               linkDocData.link_relationship = 'cropped image';
                               linkDocData.title = 'crop: ' + this.props.Document.title;
-                              linkDocData.link_displayLine = false;
                           }
                       },
                   });
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index e17da2680..a7907a565 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -13,7 +13,6 @@ import { DocumentType } from '../documents/DocumentTypes';
 import { DragManager } from '../util/DragManager';
 import { dropActionType } from '../util/DropActionTypes';
 import { Transform } from '../util/Transform';
-import { LightboxView } from './LightboxView';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import './OverlayView.scss';
 import { DefaultStyleProvider } from './StyleProvider';
@@ -94,7 +93,7 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
     };
 
     render() {
-        return LightboxView.LightboxDoc ? null : (
+        return (
             <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'}
@@ -181,7 +180,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
     docScreenToLocalXf = computedFn((doc: Doc) => () => new Transform(-NumCast(doc.overlayX), -NumCast(doc.overlayY), 1));
 
     @computed get overlayDocs() {
-        return Doc.MyOverlayDocs.filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES).map(d => {
+        return Doc.MyOverlayDocs.map(d => {
             let [offsetx, offsety] = [0, 0];
             const dref = React.createRef<HTMLDivElement>();
             const onPointerMove = action((e: PointerEvent, down: number[]) => {
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index cbbf48c75..edf6df2b9 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -20,11 +20,8 @@ import { DocData } from '../../fields/DocSymbols';
 import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, ScriptCast } from '../../fields/Types';
 import { ImageField } from '../../fields/URLField';
-import { DocUtils } from '../documents/DocUtils';
+import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
-import { IsFollowLinkScript } from '../util/LinkFollower';
-import { LinkManager } from '../util/LinkManager';
-import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
 import { undoBatch, undoable } from '../util/UndoManager';
 import { InkingStroke } from './InkingStroke';
@@ -40,13 +37,13 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @observable public static Instance: PropertiesButtons;
 
     @computed get selectedDoc() {
-        return SelectionManager.SelectedSchemaDoc || SelectionManager.Views.lastElement()?.Document;
+        return DocumentView.SelectedSchemaDoc() || DocumentView.Selected().lastElement()?.Document;
     }
     @computed get selectedLayoutDoc() {
-        return SelectionManager.SelectedSchemaDoc || SelectionManager.Views.lastElement()?.layoutDoc;
+        return DocumentView.SelectedSchemaDoc() || DocumentView.Selected().lastElement()?.layoutDoc;
     }
     @computed get selectedTabView() {
-        return !SelectionManager.SelectedSchemaDoc && SelectionManager.Views.lastElement()?.topMost;
+        return !DocumentView.SelectedSchemaDoc() && DocumentView.Selected().lastElement()?.topMost;
     }
 
     @computed get titleButton() {
@@ -164,7 +161,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     //         on => `${on ? 'Set' : 'Remove'} lightbox flag`,
     //         on => 'window-restore',
     //         onClick => {
-    //             SelectionManager.Views.forEach(dv => {
+    //             DocumentView.Selected().forEach(dv => {
     //                 const containerDoc = dv.Document;
     //                 //containerDoc.followAllLinks =
     //                 // containerDoc.noShadow =
@@ -320,14 +317,14 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @undoBatch
     handlePerspectiveChange = (e: any) => {
         this.selectedDoc && (this.selectedDoc._type_collection = e.target.value);
-        SelectionManager.Views.forEach(docView => {
+        DocumentView.Selected().forEach(docView => {
             docView.layoutDoc._type_collection = e.target.value;
         });
     };
     @computed get onClickVal() {
         const linkButton = IsFollowLinkScript(this.selectedDoc.onClick);
         const followLoc = this.selectedDoc._followLinkLocation;
-        const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
+        const linkedToLightboxView = () => Doc.Links(this.selectedDoc).some(link => Doc.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
 
         if (followLoc === OpenWhere.lightbox && !linkedToLightboxView()) return 'linkInPlace';
         if (linkButton && followLoc === OpenWhere.addRight) return 'linkOnRight';
@@ -381,7 +378,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @undoBatch
     @action
     handleOptionChange = (onClick: string) => {
-        SelectionManager.Views.forEach(docView => {
+        DocumentView.Selected().forEach(docView => {
             const linkButton = IsFollowLinkScript(docView.Document.onClick);
             docView.noOnClick();
             switch (onClick) {
@@ -417,7 +414,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             const click = () => this.handleOptionChange(value[0]);
             const linkButton = IsFollowLinkScript(this.selectedDoc.onClick);
             const followLoc = this.selectedDoc._followLinkLocation;
-            const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
+            const linkedToLightboxView = () => Doc.Links(this.selectedDoc).some(link => Doc.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
 
             let active = false;
             // prettier-ignore
@@ -451,7 +448,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     }
     @undoBatch
     editOnClickScript = () => {
-        if (SelectionManager.Views.length) SelectionManager.Views.forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, undefined, 'onClick'));
+        if (DocumentView.Selected().length) DocumentView.Selected().forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, undefined, 'onClick'));
         else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick');
     };
 
@@ -472,8 +469,8 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 fillWidth
                 toggleType={ToggleType.BUTTON}
                 onClick={undoable(() => {
-                    if (SelectionManager.Views.length > 1) {
-                        SelectionManager.Views.forEach(dv => (onClick ?? onPropToggle)(dv, dv.Document, property));
+                    if (DocumentView.Selected().length > 1) {
+                        DocumentView.Selected().forEach(dv => (onClick ?? onPropToggle)(dv, dv.Document, property));
                     } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
                 }, property)}
             />
@@ -482,7 +479,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
 
     render() {
         const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)];
-        const isText = SelectionManager.Views.lastElement()?.ComponentView instanceof FormattedTextBox;
+        const isText = DocumentView.Selected().lastElement()?.ComponentView instanceof FormattedTextBox;
         const isInk = this.selectedDoc?.layout_isSvg;
         const isImage = layoutField instanceof ImageField;
         const isMap = this.selectedDoc?.type === DocumentType.MAP;
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index a806995f2..edb55f341 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -7,12 +7,12 @@ import { Doc } from '../../fields/Doc';
 import { Cast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
 import { LinkManager } from '../util/LinkManager';
-import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
 import './PropertiesDocBacklinksSelector.scss';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { LinkMenu } from './linking/LinkMenu';
 import { OpenWhere } from './nodes/OpenWhere';
+import { DocumentView } from './nodes/DocumentView';
 
 type PropertiesDocBacklinksSelectorProps = {
     Document: Doc;
@@ -25,7 +25,7 @@ type PropertiesDocBacklinksSelectorProps = {
 export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDocBacklinksSelectorProps> {
     getOnClick = action((link: Doc) => {
         const linkAnchor = this.props.Document;
-        const other = LinkManager.getOppositeAnchor(link, linkAnchor);
+        const other = Doc.getOppositeAnchor(link, linkAnchor);
         const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? Cast(other.annotationOn, Doc, null) : other;
         LinkManager.Instance.currentLink = link;
         if (otherdoc) {
@@ -36,10 +36,10 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
     });
 
     render() {
-        return !SelectionManager.Views.length ? null : (
+        return !DocumentView.Selected().length ? null : (
             <div className="propertiesDocBacklinksSelector" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
                 {this.props.hideTitle ? null : <p key="contexts">Contexts:</p>}
-                <LinkMenu docView={SelectionManager.Views.lastElement()} clearLinkEditor={undefined} itemHandler={this.getOnClick} style={{ left: 0, top: 0 }} />
+                <LinkMenu docView={DocumentView.Selected().lastElement()} clearLinkEditor={undefined} itemHandler={this.getOnClick} style={{ left: 0, top: 0 }} />
             </div>
         );
     }
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 722b1f90a..7465c727a 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -7,11 +7,10 @@ import * as React from 'react';
 import { Doc } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { Cast, StrCast } from '../../fields/Types';
-import { DocFocusOrOpen } from '../util/DocumentManager';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import './PropertiesDocContextSelector.scss';
 import { CollectionDockingView } from './collections/CollectionDockingView';
-import { DocumentView } from './nodes/DocumentView';
+import { DocFocusOrOpen, DocumentView } from './nodes/DocumentView';
 import { OpenWhere } from './nodes/OpenWhere';
 
 type PropertiesDocContextSelectorProps = {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index ff5dcd1b8..7c16e0ddb 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -23,10 +23,8 @@ import { ComputedField } from '../../fields/ScriptField';
 import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { GetEffectiveAcl, SharingPermissions, normalizeEmail } from '../../fields/util';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
-import { DocumentManager } from '../util/DocumentManager';
 import { GroupManager } from '../util/GroupManager';
 import { LinkManager } from '../util/LinkManager';
-import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
 import { Transform } from '../util/Transform';
@@ -40,7 +38,7 @@ import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector
 import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
 import { PropertiesSection } from './PropertiesSection';
 import './PropertiesView.scss';
-import { DefaultStyleProvider } from './StyleProvider';
+import { DefaultStyleProvider, SetFilterOpener as SetPropertiesFilterOpener } from './StyleProvider';
 import { DocumentView } from './nodes/DocumentView';
 import { StyleProviderFuncType } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
@@ -66,6 +64,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         super(props);
         makeObservable(this);
         PropertiesView.Instance = this;
+        SetPropertiesFilterOpener(
+            action(() => {
+                this.CloseAll();
+                this.openFilters = true;
+            })
+        );
     }
 
     @computed get MAX_EMBED_HEIGHT() {
@@ -73,7 +77,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
 
     @computed get selectedDoc() {
-        return SelectionManager.SelectedSchemaDoc || this.selectedDocumentView?.Document || Doc.ActiveDashboard;
+        return DocumentView.SelectedSchemaDoc() || this.selectedDocumentView?.Document || Doc.ActiveDashboard;
     }
 
     @computed get selectedLink() {
@@ -81,12 +85,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
 
     @computed get selectedLayoutDoc() {
-        return SelectionManager.SelectedSchemaDoc || this.selectedDocumentView?.layoutDoc || Doc.ActiveDashboard;
+        return DocumentView.SelectedSchemaDoc() || this.selectedDocumentView?.layoutDoc || Doc.ActiveDashboard;
     }
     @computed get selectedDocumentView() {
-        if (SelectionManager.Views.length) return SelectionManager.Views[0];
-        if (PresBox.Instance?.selectedArray.size) return DocumentManager.Instance.getDocumentView(PresBox.Instance.Document);
-        return undefined;
+        return DocumentView.Selected().lastElement();
     }
     @computed get isPres(): boolean {
         return this.selectedDoc?.type === DocumentType.PRES;
@@ -182,7 +184,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         const rows: JSX.Element[] = [];
         if (this.dataDoc && this.selectedDoc) {
             const ids = new Set<string>(reqdKeys);
-            const docs: Doc[] = SelectionManager.Views.length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views.map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
+            const docs: Doc[] =
+                DocumentView.Selected().length < 2 //
+                    ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc]
+                    : DocumentView.Selected().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
             docs.forEach(doc =>
                 Object.keys(doc)
                     .filter(filter)
@@ -237,7 +242,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @undoBatch
     setKeyValue = (value: string) => {
-        const docs = SelectionManager.Views.length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc!] : SelectionManager.Views.map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
+        const docs =
+            DocumentView.Selected().length < 2 && this.selectedDoc
+                ? [this.layoutFields
+                        ? Doc.Layout(this.selectedDoc) //
+                        : this.dataDoc!]
+                : DocumentView.Selected().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); // prettier-ignore
         docs.forEach(doc => {
             if (value.indexOf(':') !== -1) {
                 const newVal = value[0].toUpperCase() + value.substring(1, value.length);
@@ -295,13 +305,13 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc;
         let counter = 0;
 
-        LinkManager.Links(selAnchor).forEach(() => counter++);
+        Doc.Links(selAnchor).forEach(() => counter++);
 
         return counter;
     }
 
     @computed get layoutPreview() {
-        if (SelectionManager.Views.length > 1) {
+        if (DocumentView.Selected().length > 1) {
             return '-- multiple selected --';
         }
         if (this.selectedDoc) {
@@ -348,7 +358,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
      */
     @undoBatch
     changePermissions = (e: any, user: string) => {
-        const docs = SelectionManager.Views.length < 2 ? [this.selectedDoc] : SelectionManager.Views.map(dv => (this.layoutDocAcls ? dv.layoutDoc : dv.dataDoc));
+        const docs = DocumentView.Selected().length < 2 ? [this.selectedDoc] : DocumentView.Selected().map(dv => (this.layoutDocAcls ? dv.layoutDoc : dv.dataDoc));
         SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs, this.layoutDocAcls);
     };
 
@@ -462,7 +472,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
      */
     @computed get sharingTable() {
         // all selected docs
-        const docs = SelectionManager.Views.length < 2 && this.selectedDoc ? [this.selectedDoc] : SelectionManager.Views.map(docView => docView.Document);
+        const docs = DocumentView.Selected().length < 2 && this.selectedDoc ? [this.selectedDoc] : DocumentView.SelectedDocs();
         const target = docs[0];
 
         const showAdmin = GetEffectiveAcl(target) === AclAdmin;
@@ -580,7 +590,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @computed get editableTitle() {
         const titles = new Set<string>();
-        SelectionManager.Views.forEach(dv => titles.add(StrCast(dv.Document.title)));
+        DocumentView.Selected().forEach(dv => titles.add(StrCast(dv.Document.title)));
         const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title);
         return (
             <div>
@@ -631,8 +641,8 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @undoBatch
     setTitle = (value: string | number) => {
-        if (SelectionManager.Views.length > 1) {
-            SelectionManager.Views.map(dv => Doc.SetInPlace(dv.Document, 'title', value, true));
+        if (DocumentView.Selected().length > 1) {
+            DocumentView.Selected().map(dv => Doc.SetInPlace(dv.Document, 'title', value, true));
         } else if (this.dataDoc) {
             if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, 'title', value, true);
             else KeyValueBox.SetField(this.dataDoc, 'title', value as string, true);
@@ -1365,14 +1375,14 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @computed get destinationAnchor() {
         const ldoc = this.selectedLink;
         const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor;
-        if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch;
+        if (ldoc && lanch) return Doc.getOppositeAnchor(ldoc, lanch) ?? lanch;
         return ldoc ? DocCast(ldoc.link_anchor_2) : ldoc;
     }
 
     @computed get sourceAnchor() {
         const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor;
 
-        return selAnchor ?? (this.selectedLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(this.selectedLink, this.destinationAnchor) : this.selectedLink);
+        return selAnchor ?? (this.selectedLink && this.destinationAnchor ? Doc.getOppositeAnchor(this.selectedLink, this.destinationAnchor) : this.selectedLink);
     }
 
     toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc, value: any = true, ovalue: any = false, cb: (val: any) => any = val => val) => {
@@ -1435,7 +1445,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3));
         const targZoom = this.sourceAnchor?.followLinkZoom;
         const indent = 30;
-        const hasSelectedAnchor = this.selectedLink !== this.selectedDoc && LinkManager.Links(this.sourceAnchor).includes(this.selectedLink!);
+        const hasSelectedAnchor = this.selectedLink !== this.selectedDoc && Doc.Links(this.sourceAnchor).includes(this.selectedLink!);
 
         return (
             <>
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 8de359e07..9c36e6d26 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -122,14 +122,14 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
                     if (!text) {
                         doc[DocData][fieldKey] = undefined;
                     } else {
-                        const script = CompileScript(text, {
+                        const compScript = CompileScript(text, {
                             params: { this: Doc.name, ...contextParams },
                             typecheck: false,
                             editable: true,
                             transformer: DocumentIconContainer.getTransformer(),
                         });
-                        if (!script.compiled) {
-                            onError(script.errors.map(error => error.messageText).join('\n'));
+                        if (!compScript.compiled) {
+                            onError(compScript.errors.map(error => error.messageText).join('\n'));
                             return;
                         }
 
@@ -143,7 +143,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
                         div.innerHTML = 'button';
                         params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, () => {}, clientX, clientY);
 
-                        doc[DocData][fieldKey] = new ScriptField(script);
+                        doc[DocData][fieldKey] = new ScriptField(compScript);
                         overlayDisposer();
                     }
                 }}
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index ba2e22b3b..1a2eb460f 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -5,15 +5,15 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { DocumentManager } from '../util/DocumentManager';
 import { CompileScript, Transformer, ts } from '../util/Scripting';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { SettingsManager } from '../util/SettingsManager';
+import { SnappingManager } from '../util/SnappingManager';
 import { undoable } from '../util/UndoManager';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { OverlayView } from './OverlayView';
 import './ScriptingRepl.scss';
 import { DocumentIconContainer } from './nodes/DocumentIcon';
+import { DocumentView } from './nodes/DocumentView';
 
 interface replValueProps {
     scrollToBottom: () => void;
@@ -169,7 +169,7 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
             case 'Enter': {
                 e.stopPropagation();
                 const docGlobals: { [name: string]: any } = {};
-                DocumentManager.Instance.DocumentViews.forEach((dv, i) => {
+                DocumentView.allViews().forEach((dv, i) => {
                     docGlobals[`d${i}`] = dv.Document;
                 });
                 const globals = ScriptingGlobals.makeMutableGlobalsCopy(docGlobals);
@@ -262,13 +262,13 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
     render() {
         return (
             <div className="scriptingRepl-outerContainer">
-                <div className="scriptingRepl-commandsContainer" style={{ background: SettingsManager.userBackgroundColor }} ref={this.commandsRef}>
+                <div className="scriptingRepl-commandsContainer" style={{ background: SnappingManager.userBackgroundColor }} ref={this.commandsRef}>
                     {this.commands.map(({ command, result }, i) => (
-                        <div className="scriptingRepl-resultContainer" style={{ background: SettingsManager.userBackgroundColor }} key={i}>
-                            <div className="scriptingRepl-commandString" style={{ background: SettingsManager.userBackgroundColor }}>
+                        <div className="scriptingRepl-resultContainer" style={{ background: SnappingManager.userBackgroundColor }} key={i}>
+                            <div className="scriptingRepl-commandString" style={{ background: SnappingManager.userBackgroundColor }}>
                                 {command || <br />}
                             </div>
-                            <div className="scriptingRepl-commandResult" style={{ background: SettingsManager.userBackgroundColor }}>
+                            <div className="scriptingRepl-commandResult" style={{ background: SnappingManager.userBackgroundColor }}>
                                 <ScriptingValueDisplay scrollToBottom={this.maybeScrollToBottom} value={result} />
                             </div>
                         </div>
@@ -276,7 +276,7 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
                 </div>
                 <input
                     className="scriptingRepl-commandInput"
-                    style={{ background: SettingsManager.userBackgroundColor }} //
+                    style={{ background: SnappingManager.userBackgroundColor }} //
                     onFocus={this.onFocus}
                     onBlur={this.onBlur}
                     value={this.commandString}
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 3648e613d..9b70f1ca7 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -14,12 +14,11 @@ import { DocCast, NumCast, StrCast } from '../../fields/Types';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
 import { DocUtils } from '../documents/DocUtils';
-import { LinkManager } from '../util/LinkManager';
 import { SearchUtil } from '../util/SearchUtil';
 import { Transform } from '../util/Transform';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import './SidebarAnnos.scss';
-import { StyleProp } from './StyleProvider';
+import { StyleProp } from './StyleProp';
 import { CollectionStackingView } from './collections/CollectionStackingView';
 import { FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@@ -87,7 +86,6 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
         Doc.SetSelectOnLoad(target);
         FormattedTextBox.DontSelectInitialText = true;
         const link = DocUtils.MakeLink(anchor, target, { link_relationship: 'inline comment:comment on' });
-        link && (link.link_displayLine = false);
 
         const taggedContent = this.childFilters()
             .filter(data => data.split(':')[0])
@@ -187,8 +185,8 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
     layout_showTitle = () => 'title';
     setHeightCallback = (height: number) => this._props.setHeight?.(height + this.filtersHeight());
     sortByLinkAnchorY = (a: Doc, b: Doc) => {
-        const ay = LinkManager.Links(a).length && DocCast(LinkManager.Links(a)[0].link_anchor_1).y;
-        const by = LinkManager.Links(b).length && DocCast(LinkManager.Links(b)[0].link_anchor_1).y;
+        const ay = Doc.Links(a).length && DocCast(Doc.Links(a)[0].link_anchor_1).y;
+        const by = Doc.Links(b).length && DocCast(Doc.Links(b)[0].link_anchor_1).y;
         return NumCast(ay) - NumCast(by);
     };
     render() {
diff --git a/src/client/views/StyleProp.ts b/src/client/views/StyleProp.ts
new file mode 100644
index 000000000..dd5b98cfe
--- /dev/null
+++ b/src/client/views/StyleProp.ts
@@ -0,0 +1,24 @@
+export enum StyleProp {
+    TreeViewIcon = 'treeView_Icon',
+    TreeViewSortings = 'treeView_Sortings', // options for how to sort tree view items
+    DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view
+    Opacity = 'opacity', // opacity of the document view
+    BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views
+    BorderRounding = 'borderRounding', // border radius of the document view
+    Color = 'color', // foreground color of Document view items
+    BackgroundColor = 'backgroundColor', // background color of a document view
+    FillColor = 'fillColor', // fill color of an ink stroke or shape
+    WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
+    PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified
+    Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background
+    HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that
+    ShowCaption = 'layout_showCaption',
+    TitleHeight = 'titleHeight', // Height of Title area
+    ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix)
+    BorderPath = 'customBorder', // border path for document view
+    FontColor = 'fontColor', // color o tet
+    FontSize = 'fontSize', // size of text font
+    FontFamily = 'fontFamily', // font family of text
+    FontWeight = 'fontWeight', // font weight of text
+    Highlighting = 'highlighting', // border highlighting
+}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 6d4f69e6e..3ecfc8ba3 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -13,53 +13,20 @@ 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 { Id } from '../../fields/FieldSymbols';
+import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
+import { AudioAnnoState } from '../../server/SharedMediaTypes';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
-import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager';
-import { IsFollowLinkScript } from '../util/LinkFollower';
-import { LinkManager } from '../util/LinkManager';
-import { SettingsManager } from '../util/SettingsManager';
+import { IsFollowLinkScript } from '../documents/DocUtils';
 import { SnappingManager } from '../util/SnappingManager';
 import { undoBatch, UndoManager } from '../util/UndoManager';
-import { CollectionSchemaView } from './collections/collectionSchema/CollectionSchemaView';
 import { TreeSort } from './collections/TreeSort';
 import { Colors } from './global/globalEnums';
-import { DocumentViewProps } from './nodes/DocumentView';
+import { DocFocusOrOpen, DocumentView, DocumentViewProps } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
-import { KeyValueBox } from './nodes/KeyValueBox';
-import { PropertiesView } from './PropertiesView';
+import { StyleProp } from './StyleProp';
 import './StyleProvider.scss';
-import { ScriptField } from '../../fields/ScriptField';
-
-export enum StyleProp {
-    TreeViewIcon = 'treeView_Icon',
-    TreeViewSortings = 'treeView_Sortings', // options for how to sort tree view items
-    DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view
-    Opacity = 'opacity', // opacity of the document view
-    BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views
-    BorderRounding = 'borderRounding', // border radius of the document view
-    Color = 'color', // foreground color of Document view items
-    BackgroundColor = 'backgroundColor', // background color of a document view
-    FillColor = 'fillColor', // fill color of an ink stroke or shape
-    WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
-    PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified
-    Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background
-    HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that
-    ShowCaption = 'layout_showCaption',
-    TitleHeight = 'titleHeight', // Height of Title area
-    ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix)
-    BorderPath = 'customBorder', // border path for document view
-    FontColor = 'fontColor', // color o tet
-    FontSize = 'fontSize', // size of text font
-    FontFamily = 'fontFamily', // font family of text
-    FontWeight = 'fontWeight', // font weight of text
-    Highlighting = 'highlighting', // border highlighting
-}
-
-export enum AudioAnnoState {
-    stopped = 'stopped',
-    playing = 'playing',
-}
 
 function toggleLockedPosition(doc: Doc) {
     UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
@@ -80,7 +47,7 @@ export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: nu
     const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
     const replacer = (match: any, expr: string) =>
         // bcz: this executes a script to convert a property expression string:  { script }  into a value
-        ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? '';
+        ScriptField.MakeFunction(expr, { this: Doc.name, scale: 'number' })?.script.run({ this: doc, scale }).result?.toString() ?? '';
     divKeys.forEach((prop: string) => {
         const p = (props as any)[prop];
         typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
@@ -98,6 +65,11 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
     } ${ph * inset}`;
 }
 
+let _filterOpener: () => void;
+export function SetFilterOpener(func: () => void) {
+    _filterOpener = func;
+}
+
 // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
 //
 export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & DocumentViewProps>, property: string): any {
@@ -124,7 +96,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         childFiltersByRanges,
         renderDepth,
         docViewPath,
-        DocumentView,
+        DocumentView: docView,
         LayoutTemplateString,
         disableBrushing,
         NativeDimScaling,
@@ -177,21 +149,20 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             return  undefined;
         case StyleProp.DocContents:  return undefined;
         case StyleProp.WidgetColor:  return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
-        case StyleProp.Opacity:      return LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
+        case StyleProp.Opacity:      return docView?.().ComponentView?.isUnstyledView?.() ? 1 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
         case StyleProp.FontColor:    return StrCast(doc?.[fieldKey + 'fontColor'],  StrCast(Doc.UserDoc().fontColor, color()));
         case StyleProp.FontSize:     return StrCast(doc?.[fieldKey + 'fontSize'],   StrCast(Doc.UserDoc().fontSize));
         case StyleProp.FontFamily:   return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
         case StyleProp.FontWeight:   return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight));
         case StyleProp.FillColor:    return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
         case StyleProp.ShowCaption:  return hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
-        case StyleProp.TitleHeight:  return Math.min(4,(DocumentView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
+        case StyleProp.TitleHeight:  return Math.min(4,(docView?.().screenToViewTransform().Scale ?? 1)) * NumCast(Doc.UserDoc().headerHeight,30);
         case StyleProp.ShowTitle:
             return (
                 (doc &&
-                    !(DocumentView?.().ComponentView instanceof CollectionSchemaView) &&
+                    !docView?.().ComponentView?.isUnstyledView?.() &&
                     !LayoutTemplateString &&
                     !doc.presentation_targetDoc &&
-                    !LayoutTemplateString?.includes(KeyValueBox.name) &&
                     layout_showTitle?.() !== '' &&
                     StrCast(
                         doc._layout_showTitle,
@@ -205,9 +176,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 ''
             );
         case StyleProp.Color: {
-            if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userBackgroundColor;
-            if (Doc.IsSystem(doc!)) return SettingsManager.userColor;
-            if (doc?.type === DocumentType.FONTICON) return SettingsManager.userColor;
+            if (SnappingManager.LastPressedBtn === doc?.[Id]) return SnappingManager.userBackgroundColor;
+            if (Doc.IsSystem(doc!)) return SnappingManager.userColor;
+            if (doc?.type === DocumentType.FONTICON) return SnappingManager.userColor;
             const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
             if (docColor) return docColor;
             const backColor = backgroundCol();
@@ -243,7 +214,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 ? titleHeight()
                 : 0;
         case StyleProp.BackgroundColor: {
-            if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item
+            if (SnappingManager.LastPressedBtn === doc?.[Id]) return SnappingManager.userColor; // hack to indicate active menu panel item
             const dataKey = doc ? Doc.LayoutFieldKey(doc) : "";
             const usePath = StrCast(doc?.[dataKey + "_usePath"]);
             const alternate = usePath.includes(":hover") ? ( doc?.isHovering ? '_' + usePath.replace(":hover","") : "") : usePath ? "_" +usePath:usePath;
@@ -268,7 +239,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 case DocumentType.VID:         docColor = docColor || (Colors.LIGHT_GRAY); break;
                 case DocumentType.COL:
                     docColor = docColor || (doc && Doc.IsSystem(doc)
-                            ? SettingsManager.userBackgroundColor
+                            ? SnappingManager.userBackgroundColor
                             : doc?.annotationOn
                             ? '#00000010' // faint interior for collections on PDFs, images, etc
                             : doc?.isGroup
@@ -288,7 +259,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         case StyleProp.BoxShadow: {
             if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow)
             if (doc.layout_boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
-            if (IsFollowLinkScript(doc?.onClick) && LinkManager.Links(doc).length && !layoutDoc?.layout_isSvg) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
+            if (IsFollowLinkScript(doc?.onClick) && Doc.Links(doc).length && !layoutDoc?.layout_isSvg) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
             switch (doc?.type) {
                 case DocumentType.COL:
                     return StrCast(
@@ -320,8 +291,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             }
         }
         case StyleProp.PointerEvents:
-            if (StrCast(doc?.pointerEvents) && !LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
-            if (LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
+            if (docView?.().ComponentView?.dontRegisterView?.()) return 'all';
+            if (StrCast(doc?.pointerEvents)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
             if (SnappingManager.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
             if (pointerEvents?.() === 'none') return 'none';
             if (opacity() === 0) return 'none';
@@ -336,12 +307,12 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                         </div>
                     );
             const paint = () => !doc?.onPaint ? null : (
-                        <div className={`styleProvider-paint${DocumentView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
+                        <div className={`styleProvider-paint${docView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
                             <FontAwesomeIcon icon='pen' size="lg" />
                         </div>
                     );
             const filter = () => {
-                const dashView = untracked(() => DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard));
+                const dashView = untracked(() => DocumentView.getDocumentView(Doc.ActiveDashboard));
                 const showFilterIcon =
                     StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
                         ? 'green' // #18c718bd' //'hasFilter'
@@ -357,17 +328,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                             // eslint-disable-next-line react/no-unstable-nested-components
                             iconProvider={() => <div className='styleProvider-filterShift'><FaFilter/></div>}
                             closeOnSelect
-                            setSelectedVal={
-                                action((dv) => {
-                                    (dv as any).select(false);
-                                    (SettingsManager.Instance.propertiesWidth = 250);
-                                    setTimeout(action(() => {
-                                        if (PropertiesView.Instance) {
-                                            PropertiesView.Instance.CloseAll();
-                                            PropertiesView.Instance.openFilters = true;
-                                        }
-                                    }));
-                                })
+                            setSelectedVal={((dv: DocumentView) => {
+                                    dv.select(false);
+                                    SnappingManager.SetPropertiesWidth(250);
+                                    _filterOpener?.();
+                                }) as any // Dropdown assumes values are strings or numbers.. 
                             }
                             size={Size.XSMALL}
                             width={15}
@@ -375,14 +340,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                             title={showFilterIcon === 'green' ? 
                                 "This view is filtered.  Click to view/change filters":
                                 "this view inherits filters from one of its parents"}
-                            color={SettingsManager.userColor}
+                            color={SnappingManager.userColor}
                             background={showFilterIcon}
                             items={[ ...(dashView ? [dashView]: []), ...(docViewPath?.()??[])]
                                     .filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length)
                                     .map(dv => ({
                                 text: StrCast(dv?.Document.title),
                                 val: dv as any,
-                                style: {color:SettingsManager.userColor, background:SettingsManager.userBackgroundColor},
+                                style: {color:SnappingManager.userColor, background:SnappingManager.userBackgroundColor},
                             } as IListItemProps)) }
                         />
                     </div>
@@ -395,7 +360,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: 'blue' };
                 return (
                     <Tooltip title={<div>{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}</div>}>
-                        <div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.playAnnotation()}>
+                        <div className="styleProvider-audio" onPointerDown={() => DocumentView.getFirstDocumentView(doc)?.playAnnotation()}>
                             <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon='file-audio' size="sm" />
                         </div>
                     </Tooltip>
@@ -415,7 +380,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
 }
 
 export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp, offIcon: IconProp, clickFunc?: () => void) {
-    const color = SettingsManager.userColor;
+    const color = SnappingManager.userColor;
     return (
         <IconButton
             size={Size.XSMALL}
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index d8232c132..7617f2a52 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -11,8 +11,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
-import { SelectionManager } from '../../util/SelectionManager';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
 import { DocumentView } from '../nodes/DocumentView';
 import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import './CollectionCarousel3DView.scss';
@@ -101,7 +100,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
     }
 
     changeSlide = (direction: number) => {
-        SelectionManager.DeselectAll();
+        DocumentView.DeselectAll();
         this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.carouselItems.length) % this.carouselItems.length;
     };
 
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index fda320077..9d3657995 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -11,7 +11,7 @@ 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 { StyleProp } from '../StyleProp';
 import { DocumentView } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index b98aceb16..fc9e2e39b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -16,27 +16,33 @@ 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 { SnappingManager } from '../../util/SnappingManager';
 import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
 import { DashboardView } from '../DashboardView';
 import { LightboxView } from '../LightboxView';
+import { DocumentView } from '../nodes/DocumentView';
 import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere';
 import { OverlayView } from '../OverlayView';
 import { ScriptingRepl } from '../ScriptingRepl';
 import { UndoStack } from '../UndoStack';
 import './CollectionDockingView.scss';
 import { CollectionSubView } from './CollectionSubView';
-import { TabDocView } from './TabDocView';
 
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
 export class CollectionDockingView extends CollectionSubView() {
+    static tabClass: JSX.Element | null = null;
+    /**
+     * Configure golden layout to render its documents using the specified React component
+     * @param ele - typically would be set to TabDocView
+     */
+    static setTabJSXComponent(ele: any) {
+        this.tabClass = ele;
+    }
     // eslint-disable-next-line no-use-before-define
     @observable public static Instance: CollectionDockingView | undefined = undefined;
 
@@ -295,7 +301,7 @@ export class CollectionDockingView extends CollectionSubView() {
             glay.on('tabCreated', this.tabCreated);
             glay.on('tabDestroyed', this.tabDestroyed);
             glay.on('stackCreated', this.stackCreated);
-            glay.registerComponent('DocumentFrameRenderer', TabDocView);
+            glay.registerComponent('DocumentFrameRenderer', CollectionDockingView.tabClass);
             glay.container = this._containerRef.current;
             glay.init();
             glay.root.layoutManager.on('itemDropped', this.tabItemDropped);
@@ -431,8 +437,8 @@ export class CollectionDockingView extends CollectionSubView() {
                 } else {
                     const tabTarget = (e.target as HTMLElement)?.parentElement?.className.includes('lm_tab') ? (e.target as HTMLElement).parentElement : (e.target as HTMLElement);
                     const map = Array.from(this.tabMap).find(tab => tab.element[0] === tabTarget);
-                    if (map?.DashDoc && DocumentManager.Instance.getFirstDocumentView(map.DashDoc)) {
-                        SelectionManager.SelectView(DocumentManager.Instance.getFirstDocumentView(map.DashDoc), false);
+                    if (map?.DashDoc && DocumentView.getFirstDocumentView(map.DashDoc)) {
+                        DocumentView.SelectView(DocumentView.getFirstDocumentView(map.DashDoc), false);
                     }
                 }
             }
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index f945a7aa4..e53071584 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -21,13 +21,11 @@ import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 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 { undoBatch } from '../../util/UndoManager';
 import { AntimodeMenu } from '../AntimodeMenu';
 import { EditableView } from '../EditableView';
-import { MainView } from '../MainView';
 import { DefaultStyleProvider } from '../StyleProvider';
 import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
 import './CollectionMenu.scss';
@@ -38,6 +36,7 @@ interface CollectionMenuProps {
     panelWidth: () => number;
     toggleTopBar: () => void;
     topBarHeight: () => number;
+    togglePropertiesFlyout: () => void;
 }
 
 @observer
@@ -59,7 +58,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
 
     componentDidMount() {
         reaction(
-            () => SelectionManager.Views.lastElement(),
+            () => DocumentView.Selected().lastElement(),
             view => view && this.SetSelection(view)
         );
     }
@@ -77,15 +76,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
         }
     };
 
-    @action
-    toggleProperties = () => {
-        if (MainView.Instance.propertiesWidth() > 0) {
-            SettingsManager.Instance.propertiesWidth = 0;
-        } else {
-            SettingsManager.Instance.propertiesWidth = 300;
-        }
-    };
-
     buttonBarXf = () => {
         if (!this._docBtnRef.current) return Transform.Identity();
         const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._docBtnRef.current);
@@ -128,15 +118,15 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
     render() {
         const headerIcon = this.props.topBarHeight() > 0 ? 'angle-double-up' : 'angle-double-down';
         const headerTitle = this.props.topBarHeight() > 0 ? 'Close Header Bar' : 'Open Header Bar';
-        const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
-        const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
+        const propIcon = SnappingManager.PropertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+        const propTitle = SnappingManager.PropertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
 
         const hardCodedButtons = (
             <div className="hardCodedButtons">
                 <Toggle
                     toggleType={ToggleType.BUTTON}
                     type={Type.PRIM}
-                    color={SettingsManager.userColor}
+                    color={SnappingManager.userColor}
                     onClick={this.props.toggleTopBar}
                     toggleStatus={this.props.topBarHeight() > 0}
                     icon={<FontAwesomeIcon icon={headerIcon} size="lg" />}
@@ -145,9 +135,9 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                 <Toggle
                     toggleType={ToggleType.BUTTON}
                     type={Type.PRIM}
-                    color={SettingsManager.userColor}
-                    onClick={this.toggleProperties}
-                    toggleStatus={SettingsManager.Instance.propertiesWidth > 0}
+                    color={SnappingManager.userColor}
+                    onClick={this._props.togglePropertiesFlyout}
+                    toggleStatus={SnappingManager.PropertiesWidth > 0}
                     icon={<FontAwesomeIcon icon={propIcon} size="lg" />}
                     tooltip={propTitle}
                 />
@@ -159,7 +149,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
             <div
                 className="collectionMenu-container"
                 style={{
-                    background: SettingsManager.userBackgroundColor,
+                    background: SnappingManager.userBackgroundColor,
                     // borderColor: SettingsManager.userColor
                 }}>
                 {this.contMenuButtons}
@@ -245,7 +235,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     _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']);",
+        script: "this.target._freeform_panX = this.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(() => {
             this.target._freeform_panX = 0;
             this.target._freeform_panY = 0;
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index e940a1aef..3f9eed1d6 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -27,7 +27,7 @@ import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocum
 import { DocumentView } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FocusViewOptions } from '../nodes/FocusViewOptions';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
 import './CollectionNoteTakingView.scss';
 import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn';
 import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider';
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index e02570d3e..5b3f625db 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -10,13 +10,13 @@ import { NumCast, StrCast, toList } from '../../../fields/Types';
 import { emptyFunction } from '../../../Utils';
 import { DocUtils } from '../../documents/DocUtils';
 import { dropActionType } from '../../util/DropActionTypes';
-import { SelectionManager } from '../../util/SelectionManager';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { OpenWhere } from '../nodes/OpenWhere';
 import { computePassLayout, computeStarburstLayout } from './collectionFreeForm';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import './CollectionPileView.scss';
 import { CollectionSubView } from './CollectionSubView';
+import { DocumentView } from '../nodes/DocumentView';
 
 @observer
 export class CollectionPileView extends CollectionSubView() {
@@ -150,7 +150,7 @@ export class CollectionPileView extends CollectionSubView() {
     @undoBatch
     onClick = (e: React.MouseEvent) => {
         if (e.button === 0) {
-            SelectionManager.DeselectAll();
+            DocumentView.DeselectAll();
             this.toggleStarburst();
             e.stopPropagation();
         }
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 48d24c910..f9b123bb6 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -19,11 +19,8 @@ import { ImageField } from '../../../fields/URLField';
 import { emptyFunction, formatTime } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
-import { FollowLinkScript } from '../../documents/DocUtils';
-import { DocumentManager } from '../../util/DocumentManager';
+import { FollowLinkScript, IsFollowLinkScript } from '../../documents/DocUtils';
 import { DragManager } from '../../util/DragManager';
-import { IsFollowLinkScript, LinkFollower } from '../../util/LinkFollower';
-import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
@@ -87,7 +84,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     private _timeline: HTMLDivElement | null = null; // ref to actual timeline div
     private _timelineWrapper: HTMLDivElement | null = null; // ref to timeline wrapper div for zooming and scrolling
     private _markerStart: number = 0;
-    @observable public static CurrentlyPlaying: DocumentView[] = [];
     @observable _selectingRegion = false;
     @observable _markerEnd: number | undefined = undefined;
     @observable _trimming: number = TrimScope.None;
@@ -182,7 +178,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> =>
         new Promise<Opt<DocumentView>>(res => {
             if (doc.hidden) options.didMove = !(doc.hidden = false);
-            const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
+            const findDoc = (finish: (dv: DocumentView) => void) => DocumentView.addViewRenderedCb(doc, dv => finish(dv));
             findDoc(dv => res(dv));
         });
 
@@ -296,7 +292,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                     }
                     if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) {
                         const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
-                        setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false));
+                        setTimeout(() => DocumentView.getDocumentView(anchor)?.select(false));
                     }
                     (!isClick || !wasSelecting) && (this._markerEnd = undefined);
                     this._timelineWrapper && (this._timelineWrapper.style.cursor = '');
@@ -481,7 +477,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     @action
     clickAnchor = (anchorDoc: Doc, clientX: number) => {
         if (IsFollowLinkScript(anchorDoc.onClick)) {
-            LinkFollower.FollowLink(undefined, anchorDoc, false);
+            DocumentView.FollowLink(undefined, anchorDoc, false);
         }
         const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
         const endTime = this.anchorEnd(anchorDoc);
@@ -761,12 +757,12 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
                     // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
                     /* (isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc)) */
                     !this._props.layoutDoc.dontAutoFollowLinks &&
-                    LinkManager.Links(this._props.mark).length &&
+                    Doc.Links(this._props.mark).length &&
                     time > NumCast(this._props.mark[this._props.startTag]) &&
                     time < NumCast(this._props.mark[this._props.endTag]) &&
                     this._lastTimecode < NumCast(this._props.mark[this._props.startTag]) - 1e-5
                 ) {
-                    LinkFollower.FollowLink(undefined, this._props.mark, false);
+                    DocumentView.FollowLink(undefined, this._props.mark, false);
                 }
                 this._lastTimecode = time;
             }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 8ae0f2832..3f12a281e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -31,7 +31,7 @@ import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocum
 import { DocumentView } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FocusViewOptions } from '../nodes/FocusViewOptions';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
 import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
 import './CollectionStackingView.scss';
 import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 7c08aedb1..a4708bed5 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -21,12 +21,11 @@ import { Docs, DocumentOptions } from '../../documents/Documents';
 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 { DocumentView } from '../nodes/DocumentView';
 
 export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> {
     isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
@@ -377,7 +376,7 @@ export function CollectionSubView<X>() {
                             }
                         });
                     } else {
-                        const srcWeb = SelectionManager.Views.lastElement();
+                        const srcWeb = DocumentView.Selected().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;
@@ -386,7 +385,7 @@ export function CollectionSubView<X>() {
                         Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text;
                         addDocument(htmlDoc);
                         if (srcWeb) {
-                            const iframe = SelectionManager.Views[0].ContentDiv?.getElementsByTagName('iframe')?.[0];
+                            const iframe = DocumentView.Selected()[0].ContentDiv?.getElementsByTagName('iframe')?.[0];
                             const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any;
                             if (focusNode) {
                                 const anchor = srcWeb?.ComponentView?.getAnchor?.(true);
@@ -496,13 +495,13 @@ export function CollectionSubView<X>() {
             if (typeof files === 'string') {
                 const loading = Docs.Create.LoadingDocument(files, options);
                 generatedDocuments.push(loading);
-                LoadingBox.addCurrentlyLoading(loading);
+                Doc.addCurrentlyLoading(loading);
                 DocUtils.uploadYoutubeVideoLoading(files, {}, loading);
             } else {
                 generatedDocuments.push(
                     ...files.map(file => {
                         const loading = Docs.Create.LoadingDocument(file, options);
-                        LoadingBox.addCurrentlyLoading(loading);
+                        Doc.addCurrentlyLoading(loading);
                         DocUtils.uploadFileToDoc(file, {}, loading);
                         return loading;
                     })
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index b3760d4af..0369e4a2a 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -12,7 +12,6 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { Docs } from '../../documents/Documents';
-import { DocumentManager } from '../../util/DocumentManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
@@ -257,7 +256,7 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou
         action(() => {
             const filterVals = bounds.payload as string[];
             filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, 'check'));
-            const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
+            const pivotView = DocumentView.getDocumentView(pivotDoc);
             if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
                 if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
                     // eslint-disable-next-line prefer-destructuring
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d015e73ad..c1247f5b0 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -14,11 +14,9 @@ import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, Utils } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocUtils } from '../../documents/DocUtils';
-import { DocumentManager } from '../../util/DocumentManager';
 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';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
@@ -27,10 +25,11 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { DocumentView } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
 import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionSubView } from './CollectionSubView';
 import './CollectionTreeView.scss';
+import { TreeViewType } from './CollectionTreeViewType';
 import { TreeView } from './TreeView';
 
 const _global = (window /* browser */ || global) /* node */ as any;
@@ -49,12 +48,6 @@ export type collectionTreeViewProps = {
     hierarchyIndex?: number[];
 };
 
-export enum TreeViewType {
-    outline = 'outline',
-    fileSystem = 'fileSystem',
-    default = 'default',
-}
-
 @observer
 export class CollectionTreeView extends CollectionSubView<Partial<collectionTreeViewProps>>() {
     public static AddTreeFunc = 'addTreeFolder(this.embedContainer)';
@@ -183,7 +176,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         const targetDataDoc = this.Document[DocData];
         const value = DocListCast(targetDataDoc[this._props.fieldKey]);
         const result = value.filter(v => !docs.includes(v));
-        if (docs.some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
+        if (docs.some(doc => DocumentView.Selected().some(dv => Doc.AreProtosEqual(dv.Document, doc)))) DocumentView.DeselectAll();
         if (result.length !== value.length) {
             if (docIn instanceof Doc) {
                 const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(docIn);
@@ -191,7 +184,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 this._props.removeDocument?.(docIn);
                 if (ind > 0 && prev) {
                     Doc.SetSelectOnLoad(prev);
-                    DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false);
+                    DocumentView.getDocumentView(prev, this.DocumentView?.())?.select(false);
                 }
                 return true;
             }
@@ -468,7 +461,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                                 minHeight: '100%',
                             }}
                             onWheel={e => e.stopPropagation()}
-                            onClick={() => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())}
+                            onClick={() => (!this.layoutDoc.forceActive ? this._props.select(false) : DocumentView.DeselectAll())}
                             onDrop={this.onTreeDrop}>
                             <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul>
                         </div>
diff --git a/src/client/views/collections/CollectionTreeViewType.ts b/src/client/views/collections/CollectionTreeViewType.ts
new file mode 100644
index 000000000..53b88160e
--- /dev/null
+++ b/src/client/views/collections/CollectionTreeViewType.ts
@@ -0,0 +1,5 @@
+export enum TreeViewType {
+    outline = 'outline',
+    fileSystem = 'fileSystem',
+    default = 'default',
+}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 2d900bb73..2d8b2564d 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -9,7 +9,7 @@ import * as ReactDOM from 'react-dom/client';
 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 { DocData, DocViews } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { FieldId } from '../../../fields/RefField';
@@ -18,10 +18,8 @@ import { Cast, DocCast, NumCast, StrCast, toList } from '../../../fields/Types';
 import { DocServer } from '../../DocServer';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
-import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { dropActionType } from '../../util/DropActionTypes';
-import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { UndoManager, undoable } from '../../util/UndoManager';
@@ -29,7 +27,8 @@ import { DashboardView } from '../DashboardView';
 import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { PinDocView, PinProps } from '../PinFuncs';
-import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
+import { DefaultStyleProvider } from '../StyleProvider';
 import { Colors } from '../global/globalEnums';
 import { DocumentView, returnEmptyDocViewList } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
@@ -194,9 +193,101 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     _mainCont: HTMLDivElement | null = null;
     _tabReaction: IReactionDisposer | undefined;
 
+    /**
+     * Adds a document to the presentation view
+     * */
+    @action
+    public static PinDoc(docIn: Doc | Doc[], pinProps: PinProps) {
+        const docs = toList(docIn);
+
+        const batch = UndoManager.StartBatch('Pin doc to pres trail');
+        const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);
+
+        if (!Doc.ActivePresentation) {
+            Doc.AddDocToList(Doc.MyTrails, 'data', curPres);
+            Doc.ActivePresentation = curPres;
+        }
+
+        docs.forEach(doc => {
+            // Edge Case 1: Cannot pin document to itself
+            if (doc === curPres) {
+                alert('Cannot pin presentation document to itself');
+                return;
+            }
+            const anchorDoc = DocumentView.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps);
+            const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Docs.Create.ConfigDocument({});
+            const targDoc = (pinDoc.presentation_targetDoc = anchorDoc ?? doc);
+            pinDoc.title = doc.title + ' - Slide';
+            pinDoc.data = targDoc.type === DocumentType.PRES ? ComputedField.MakeFunction('copyField(this.presentation_targetDoc.data') : new List<Doc>(); // the children of the embedding's layout are the presentation slide children.  the embedding's data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
+            pinDoc.presentation_movement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom;
+            pinDoc.presentation_duration = pinDoc.presentation_duration ?? 1000;
+            pinDoc.presentation_groupWithUp = false;
+            Doc.SetContainer(pinDoc, curPres);
+            // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements.  That way the PresBox could configure all of its children at render time
+            pinDoc.treeView = ''; // not really needed, but makes key value pane look better
+            pinDoc.treeView_RenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
+            pinDoc.treeView_HeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling.
+            pinDoc.treeView_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
+            pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
+            pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
+            const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null);
+
+            if (pinProps.pinViewport) PinDocView(pinDoc, pinProps, anchorDoc ?? doc);
+            if (!pinProps?.audioRange && duration !== undefined) {
+                pinDoc.presentation_mediaStart = 'manual';
+                pinDoc.presentation_mediaStop = 'manual';
+            }
+            if (pinProps?.activeFrame !== undefined) {
+                pinDoc.config_activeFrame = pinProps?.activeFrame;
+                pinDoc.title = doc.title + ' (move)';
+                pinDoc.presentation_movement = PresMovement.Pan;
+            }
+            if (pinProps?.currentFrame !== undefined) {
+                pinDoc.config_currentFrame = pinProps?.currentFrame;
+                pinDoc.title = doc.title + ' (move)';
+                pinDoc.presentation_movement = PresMovement.Pan;
+            }
+            if (pinDoc.stroke_isInkMask) {
+                pinDoc.presentation_hideAfter = true;
+                pinDoc.presentation_hideBefore = true;
+                pinDoc.presentation_movement = PresMovement.None;
+            }
+            if (curPres.expandBoolean) pinDoc.presentation_expandInlineButton = true;
+            Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement());
+            PresBox.Instance?.clearSelectedArray();
+            pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); // Update selected array
+        });
+        if (
+            // open the presentation trail if it's not already opened
+            !Array.from(CollectionDockingView.Instance?.tabMap ?? [])
+                .map(d => d.DashDoc)
+                .includes(curPres)
+        ) {
+            if (Doc.IsInMyOverlay(curPres)) Doc.RemFromMyOverlay(curPres);
+            CollectionDockingView.AddSplit(curPres, OpenWhereMod.right);
+            setTimeout(() => DocumentView.showDocument(docs.lastElement(), { willPan: true }), 100); // keeps the pinned doc in view since the sidebar shifts things
+        }
+        setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
+    }
+
+    static Activate = (tabDoc: Doc) => {
+        const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(findTab => findTab.DashDoc === tabDoc && !findTab.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)
+        return tab !== undefined;
+    };
+    // static ActivateTabView(doc: Doc) {
+    //     const tabView = Array.from(TabDocView._allTabs).find(view => view._document === doc);
+    //     if (!tabView?._activated && tabView?._document) {
+    //         TabDocView.Activate(tabView?._document);
+    //         return tabView;
+    //     }
+    //     return undefined;
+    // }
     constructor(props: any) {
         super(props);
         makeObservable(this);
+        DocumentView.activateTabView = TabDocView.Activate;
+        DocumentView.PinDoc = TabDocView.PinDoc;
     }
 
     @observable _activated: boolean = false;
@@ -205,8 +296,14 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     @observable _hovering = false;
     @observable _isActive: boolean = false;
     @observable _isAnyChildContentActive = false;
+    public static IsSelected = (doc?: Doc) => {
+        if (Array.from(doc?.[DocViews] ?? []).some(dv => dv?.IsSelected)) {
+            return true;
+        }
+        return false;
+    };
     @computed get _isUserActivated() {
-        return SelectionManager.IsSelected(this._document) || this._isAnyChildContentActive;
+        return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive;
     }
     get _isContentActive() {
         return this._isUserActivated || this._hovering;
@@ -279,12 +376,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                         returnFalse,
                         action(clickEv => {
                             if (this.view) {
-                                SelectionManager.SelectView(this.view, false);
+                                DocumentView.SelectView(this.view, false);
                                 const child = getChild();
                                 simulateMouseClick(child, clickEv.clientX, clickEv.clientY + 30, clickEv.screenX, clickEv.screenY + 30);
                             } else {
                                 this._activated = true;
-                                setTimeout(() => this.view && SelectionManager.SelectView(this.view, false));
+                                setTimeout(() => this.view && DocumentView.SelectView(this.view, false));
                             }
                         })
                     );
@@ -350,7 +447,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
             // select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected
             titleEle.onpointerdown = action((e: any) => {
                 if (e.target.className !== 'lm_iconWrap') {
-                    if (this.view) SelectionManager.SelectView(this.view, false);
+                    if (this.view) DocumentView.SelectView(this.view, false);
                     else this._activated = true;
                     if (Date.now() - titleEle.lastClick < 1000) titleEle.select();
                     titleEle.lastClick = Date.now();
@@ -358,7 +455,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                 }
             });
             tab._disposers.selectionDisposer = reaction(
-                () => SelectionManager.IsSelected(this._document),
+                () => TabDocView.IsSelected(this._document),
                 action(selected => {
                     if (selected) this._activated = true;
                     if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
@@ -382,89 +479,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                 .off('click') // unbind the current click handler
                 .click(() => {
                     Object.values(tab._disposers).forEach((disposer: any) => disposer?.());
-                    SelectionManager.DeselectAll();
+                    DocumentView.DeselectAll();
                     UndoManager.RunInBatch(() => tab.contentItem.remove(), 'delete tab');
                 });
         }
     };
 
-    /**
-     * Adds a document to the presentation view
-     * */
-    @action
-    public static PinDoc(docIn: Doc | Doc[], pinProps: PinProps) {
-        const docs = toList(docIn);
-
-        const batch = UndoManager.StartBatch('Pin doc to pres trail');
-        const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);
-
-        if (!Doc.ActivePresentation) {
-            Doc.AddDocToList(Doc.MyTrails, 'data', curPres);
-            Doc.ActivePresentation = curPres;
-        }
-
-        docs.forEach(doc => {
-            // Edge Case 1: Cannot pin document to itself
-            if (doc === curPres) {
-                alert('Cannot pin presentation document to itself');
-                return;
-            }
-            const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps);
-            const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Docs.Create.ConfigDocument({});
-            const targDoc = (pinDoc.presentation_targetDoc = anchorDoc ?? doc);
-            pinDoc.title = doc.title + ' - Slide';
-            pinDoc.data = targDoc.type === DocumentType.PRES ? ComputedField.MakeFunction('copyField(this.presentation_targetDoc.data') : new List<Doc>(); // the children of the embedding's layout are the presentation slide children.  the embedding's data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
-            pinDoc.presentation_movement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom;
-            pinDoc.presentation_duration = pinDoc.presentation_duration ?? 1000;
-            pinDoc.presentation_groupWithUp = false;
-            Doc.SetContainer(pinDoc, curPres);
-            // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements.  That way the PresBox could configure all of its children at render time
-            pinDoc.treeView = ''; // not really needed, but makes key value pane look better
-            pinDoc.treeView_RenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
-            pinDoc.treeView_HeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling.
-            pinDoc.treeView_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
-            pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
-            pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
-            const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null);
-
-            if (pinProps.pinViewport) PinDocView(pinDoc, pinProps, anchorDoc ?? doc);
-            if (!pinProps?.audioRange && duration !== undefined) {
-                pinDoc.presentation_mediaStart = 'manual';
-                pinDoc.presentation_mediaStop = 'manual';
-            }
-            if (pinProps?.activeFrame !== undefined) {
-                pinDoc.config_activeFrame = pinProps?.activeFrame;
-                pinDoc.title = doc.title + ' (move)';
-                pinDoc.presentation_movement = PresMovement.Pan;
-            }
-            if (pinProps?.currentFrame !== undefined) {
-                pinDoc.config_currentFrame = pinProps?.currentFrame;
-                pinDoc.title = doc.title + ' (move)';
-                pinDoc.presentation_movement = PresMovement.Pan;
-            }
-            if (pinDoc.stroke_isInkMask) {
-                pinDoc.presentation_hideAfter = true;
-                pinDoc.presentation_hideBefore = true;
-                pinDoc.presentation_movement = PresMovement.None;
-            }
-            if (curPres.expandBoolean) pinDoc.presentation_expandInlineButton = true;
-            Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement());
-            PresBox.Instance?.clearSelectedArray();
-            pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); // Update selected array
-        });
-        if (
-            // open the presentation trail if it's not already opened
-            !Array.from(CollectionDockingView.Instance?.tabMap ?? [])
-                .map(d => d.DashDoc)
-                .includes(curPres)
-        ) {
-            if (Doc.IsInMyOverlay(curPres)) Doc.RemFromMyOverlay(curPres);
-            CollectionDockingView.AddSplit(curPres, OpenWhereMod.right);
-            setTimeout(() => DocumentManager.Instance.showDocument(docs.lastElement(), { willPan: true }), 100); // keeps the pinned doc in view since the sidebar shifts things
-        }
-        setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
-    }
-
     componentDidMount() {
         new _global.ResizeObserver(
             action((entries: any) => {
@@ -484,12 +504,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     }
     componentDidUpdate(prevProps: Readonly<TabDocViewProps>) {
         super.componentDidUpdate(prevProps);
-        this._view && DocumentManager.Instance.AddView(this._view);
+        this._view && DocumentView.addView(this._view);
     }
 
     componentWillUnmount() {
         this._tabReaction?.();
-        this._view && DocumentManager.Instance.RemoveView(this._view);
+        this._view && DocumentView.removeView(this._view);
         runInAction(() => TabDocView._allTabs.delete(this));
 
         this._props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
@@ -503,7 +523,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     private onActiveContentItemChanged(contentItem: any) {
         if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) {
             this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab;
-            if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => SelectionManager.SelectView(this._view, false));
+            if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => DocumentView.SelectView(this._view, false));
             !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
         }
     }
@@ -517,7 +537,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     //  lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox
     addDocTab = (docsIn: Doc | Doc[], location: OpenWhere) => {
         const docs = toList(docsIn);
-        SelectionManager.DeselectAll();
+        DocumentView.DeselectAll();
         const whereFields = location.split(':');
         const keyValue = whereFields.includes(OpenWhereMod.keyvalue);
         const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
@@ -527,7 +547,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         switch (whereFields[0]) {
             case undefined:
             case OpenWhere.lightbox:   if (this.layoutDoc?._isLightbox) {
-                                            const lightboxView = !docs[0].annotationOn && DocCast(docs[0].embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(docs[0].embedContainer)) : undefined;
+                                            const lightboxView = !docs[0].annotationOn && DocCast(docs[0].embedContainer) ? DocumentView.getFirstDocumentView(DocCast(docs[0].embedContainer)) : undefined;
                                             const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.Document)];
                                             if (lightboxView && (!data || data instanceof List)) {
                                                 lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>(docs);
@@ -543,7 +563,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     };
     remDocTab = (doc: Doc | Doc[]) => {
         if (doc === this._document) {
-            SelectionManager.DeselectAll();
+            DocumentView.DeselectAll();
             CollectionDockingView.CloseSplit(this._document);
             return true;
         }
@@ -551,11 +571,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     };
 
     getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
-    static Activate = (tabDoc: Doc) => {
-        const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(findTab => findTab.DashDoc === tabDoc && !findTab.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)
-        return tab !== undefined;
-    };
+
     @action
     focusFunc = () => {
         if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
@@ -586,7 +602,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                 <DocumentView
                     key={this._document[Id]}
                     ref={action((r: DocumentView) => {
-                        this._lastView && DocumentManager.Instance.RemoveView(this._lastView);
+                        this._lastView && DocumentView.removeView(this._lastView);
                         this._view = r;
                         this._lastView = this._view;
                     })}
@@ -635,7 +651,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                     this._mainCont = ref;
                     if (this._mainCont) {
                         if (this._lastTab) {
-                            this._view && DocumentManager.Instance.RemoveView(this._view);
+                            this._view && DocumentView.removeView(this._view);
                         }
                         this._lastTab = this.tab;
                         (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 2f5af0564..969b98d91 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -21,24 +21,23 @@ import { TraceMobx } from '../../../fields/util';
 import { DocUtils } from '../../documents/DocUtils';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
-import { DocumentManager } from '../../util/DocumentManager';
 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';
 import { Transform } from '../../util/Transform';
 import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
 import { EditableView } from '../EditableView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
 import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
 import { FieldViewProps, StyleProviderFuncType } from '../nodes/FieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { OpenWhere } from '../nodes/OpenWhere';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
+import { CollectionTreeView } from './CollectionTreeView';
+import { TreeViewType } from './CollectionTreeViewType';
 import { CollectionView } from './CollectionView';
 import { TreeSort } from './TreeSort';
 import './TreeView.scss';
@@ -208,7 +207,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         const ind = DocListCast(this.dataDoc[key]).indexOf(docs.lastElement());
 
         const res = docs.reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
-        res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView.DocumentView?.())?.select(false);
+        res && ind > 0 && DocumentView.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView.DocumentView?.())?.select(false);
         return res;
     };
 
@@ -379,7 +378,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         const bulletData = bullet[DocData];
         bulletData.title = ComputedField.MakeFunction('this.text?.Text');
         bulletData.data = new List<Doc>([]);
-        DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
+        DocumentView.addViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
 
         return bullet;
     }
@@ -823,7 +822,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
 
     @computed get validExpandViewTypes() {
         const annos = () => (DocListCast(this.Document[this.fieldKey + '_annotations']).length && !this.treeView.dashboardMode ? 'annotations' : '');
-        const links = () => (LinkManager.Links(this.Document).length && !this.treeView.dashboardMode ? 'links' : '');
+        const links = () => (Doc.Links(this.Document).length && !this.treeView.dashboardMode ? 'links' : '');
         const data = () => (this.childDocs || this.treeView.dashboardMode ? this.fieldKey : '');
         const embeddings = () => (this.treeView.dashboardMode ? '' : 'embeddings');
         const fields = () => (Doc.noviceMode ? '' : 'fields');
@@ -1210,7 +1209,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) && DocumentView.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">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
index 6fc295e27..26a52cd2a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
@@ -1,17 +1,16 @@
 import { action, observable } from 'mobx';
+import { CollectionFreeFormView } from '.';
+import { intersectRect } from '../../../../Utils';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { intersectRect } from '../../../../Utils';
 import { DocumentType } from '../../../documents/DocumentTypes';
-import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { dropActionType } from '../../../util/DropActionTypes';
-import { SelectionManager } from '../../../util/SelectionManager';
+import { StyleProp } from '../../StyleProp';
 import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
-import { StyleProp } from '../../StyleProvider';
 import './CollectionFreeFormView.scss';
-import { CollectionFreeFormView } from '.';
 
 export class CollectionFreeFormClusters {
     private _view: CollectionFreeFormView;
@@ -44,7 +43,7 @@ export class CollectionFreeFormClusters {
         const h = NumCast(doc1Layout._height) + clusterDistance;
         return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
     }
-    pickCluster(probe: number[]) {
+    handlePointerDown(probe: number[]) {
         this._hitCluster = this.childLayoutPairs
             .map(pair => pair.layout)
             .reduce((cluster, cd) => {
@@ -62,13 +61,13 @@ export class CollectionFreeFormClusters {
         return this._hitCluster;
     }
 
-    tryDragCluster(e: PointerEvent) {
+    tryToDrag(e: PointerEvent) {
         const cluster = this._hitCluster;
         if (cluster !== -1) {
             const ptsParent = e;
             if (ptsParent) {
                 const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
-                const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.DocumentView)!);
+                const clusterDocs = eles.map(ele => DocumentView.getDocumentView(ele, this.DocumentView)!);
                 const { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 };
                 const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? dropActionType.embed : undefined);
                 de.moveDocument = this.viewMoveDocument;
@@ -87,7 +86,7 @@ export class CollectionFreeFormClusters {
         return false;
     }
 
-    initClusters() {
+    initLayout() {
         if (this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length) {
             return this.updateClusters(true);
         }
@@ -97,11 +96,11 @@ export class CollectionFreeFormClusters {
     updateClusters(useClusters: boolean) {
         this.Document._freeform_useClusters = useClusters;
         this._clusterSets.length = 0;
-        this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
+        this.childLayoutPairs.map(pair => pair.layout).map(c => this.addDocument(c));
     }
 
     @action
-    updateClusterDocs(docs: Doc[]) {
+    addDocuments(docs: Doc[]) {
         const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
         if (this.Document._freeform_useClusters) {
             const docFirst = docs[0];
@@ -143,12 +142,12 @@ export class CollectionFreeFormClusters {
                     this._clusterSets[(doc.layout_cluster = NumCast(docFirst.layout_cluster))].push(doc);
                 });
             }
-            childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.layout_cluster === i) && this.updateCluster(child));
+            childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.layout_cluster === i) && this.addDocument(child));
         }
     }
 
     @action
-    updateCluster = (doc: Doc) => {
+    addDocument = (doc: Doc) => {
         const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
         if (this.Document._freeform_useClusters) {
             this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
@@ -188,7 +187,7 @@ export class CollectionFreeFormClusters {
                         const cluster = NumCast(doc?.layout_cluster);
                         if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
                             if (this._clusterSets.length <= cluster) {
-                                setTimeout(() => doc && this.updateCluster(doc));
+                                setTimeout(() => doc && this.addDocument(doc));
                             } else {
                                 // choose a cluster color from a palette
                                 const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
@@ -212,9 +211,9 @@ export class CollectionFreeFormClusters {
         return styleProp;
     };
 
-    trySelectCluster = (addToSel: boolean) => {
+    tryToSelect = (addToSel: boolean) => {
         if (addToSel && this._hitCluster !== -1) {
-            !addToSel && SelectionManager.DeselectAll();
+            !addToSel && DocumentView.DeselectAll();
             const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
             this.selectDocuments(eles);
             return true;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 65a529d62..5d8373fc7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -4,13 +4,13 @@ import * as React from 'react';
 import { Doc, DocListCast, FieldType, FieldResult } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
 import { StrCast } from '../../../../fields/Types';
-import { LinkManager } from '../../../util/LinkManager';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
 import { TopBar } from '../../topbar/TopBar';
 import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState';
 import './CollectionFreeFormView.scss';
 import { DocData } from '../../../../fields/DocSymbols';
+import { CollectionFreeFormView } from './CollectionFreeFormView';
 
 export interface CollectionFreeFormInfoUIProps {
     Document: Doc;
@@ -21,6 +21,12 @@ export interface CollectionFreeFormInfoUIProps {
 
 @observer
 export class CollectionFreeFormInfoUI extends ObservableReactComponent<CollectionFreeFormInfoUIProps> {
+    public static Init() {
+        CollectionFreeFormView.SetInfoUICreator((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => (
+            //
+            <CollectionFreeFormInfoUI Document={doc} LayoutDoc={layout} childDocs={childDocs} close={close} />
+        ));
+    }
     _firstDocPos = { x: 0, y: 0 };
 
     constructor(props: any) {
@@ -63,7 +69,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
         const linkStart = () => DocumentLinksButton.StartLink;
         const linkUnstart = () => !DocumentLinksButton.StartLink;
 
-        const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length;
+        const numDocLinks = () => Doc.Links(firstDoc())?.length;
         const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView;
 
         const activeTool = () => Doc.ActiveTool;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index 90977d955..65a2fe0aa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -3,9 +3,8 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { ScriptField } from '../../../../fields/ScriptField';
-import { PresBox } from '../../nodes/trails/PresBox';
-import './CollectionFreeFormView.scss';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
+import './CollectionFreeFormView.scss';
 
 export interface CollectionFreeFormPannableContentsProps {
     Document: Doc;
@@ -20,12 +19,16 @@ export interface CollectionFreeFormPannableContentsProps {
 
 @observer
 export class CollectionFreeFormPannableContents extends ObservableReactComponent<CollectionFreeFormPannableContentsProps> {
+    static _overlayPlugin: ((fform: Doc) => React.JSX.Element) | null = null;
+    public static SetOverlayPlugin(plugin: ((fform: Doc) => React.JSX.Element) | null) {
+        CollectionFreeFormPannableContents._overlayPlugin = plugin;
+    }
     constructor(props: CollectionFreeFormPannableContentsProps) {
         super(props);
         makeObservable(this);
     }
     @computed get presPaths() {
-        return this._props.showPresPaths() ? PresBox.Instance.pathLines(this._props.Document) : null;
+        return this._props.showPresPaths() ? CollectionFreeFormPannableContents._overlayPlugin?.(this._props.Document) : null;
     }
     // rectangle highlight used when following trail/link to a region of a collection that isn't a document
     showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) =>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f55d5a23f..6d901119e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -26,15 +26,12 @@ import { aggregateBounds, emptyFunction, intersectRect, Utils } from '../../../.
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DocUtils } from '../../../documents/DocUtils';
-import { DocumentManager } from '../../../util/DocumentManager';
 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';
-import { SelectionManager } from '../../../util/SelectionManager';
-import { freeformScrollMode } from '../../../util/SettingsManager';
-import { SnappingManager } from '../../../util/SnappingManager';
+import { freeformScrollMode, SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
 import { Timeline } from '../../animationtimeline/Timeline';
@@ -50,12 +47,11 @@ import { FocusViewOptions } from '../../nodes/FocusViewOptions';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { OpenWhere } from '../../nodes/OpenWhere';
 import { PinDocView, PinProps } from '../../PinFuncs';
-import { StyleProp } from '../../StyleProvider';
+import { StyleProp } from '../../StyleProp';
 import { CollectionSubView } from '../CollectionSubView';
-import { TreeViewType } from '../CollectionTreeView';
+import { TreeViewType } from '../CollectionTreeViewType';
 import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid';
 import { CollectionFreeFormClusters } from './CollectionFreeFormClusters';
-import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI';
 import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents';
 import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
@@ -87,8 +83,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     } // this makes mobx trace() statements more descriptive
     public unprocessedDocs: Doc[] = [];
     public static collectionsWithUnprocessedInk = new Set<CollectionFreeFormView>();
-    public static from(dv?: DocumentView) {
-        return CollectionFreeFormDocumentView.from(dv)?.CollectionFreeFormView;
+    public static from(dv?: DocumentView): CollectionFreeFormView | undefined {
+        const parent = CollectionFreeFormDocumentView.from(dv)?._props.parent;
+        return parent instanceof CollectionFreeFormView ? parent : undefined;
     }
 
     _oldWheel: any;
@@ -227,25 +224,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
         return DocumentView.SetViewTransition(docs, 'all', duration, timer, undefined, true);
     }
-    public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) {
-        const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, timer, undefined, true);
-        const timecode = Math.round(time);
-        docs.forEach(doc => {
-            CollectionFreeFormDocumentView.animFields.forEach(val => {
-                const findexed = Cast(doc[`${val.key}_indexed`], listSpec('number'), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
-            });
-            CollectionFreeFormDocumentView.animStringFields.forEach(val => {
-                const findexed = Cast(doc[`${val}_indexed`], listSpec('string'), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as any as string);
-            });
-            CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => {
-                const findexed = Cast(doc[`${val}_indexed`], listSpec(InkField), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as any);
-            });
-        });
-        return newTimer;
-    }
     changeKeyFrame = (back = false) => {
         const currentFrame = Cast(this.Document._currentFrame, 'number', null);
         if (currentFrame === undefined) {
@@ -256,7 +234,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], 1000);
             this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
         } else {
-            this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], currentFrame || 0);
+            this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], currentFrame || 0);
             this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
             this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame));
         }
@@ -292,8 +270,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.addDocument(newDoc);
     };
     selectDocuments = (docs: Doc[]) => {
-        SelectionManager.DeselectAll();
-        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
+        DocumentView.DeselectAll();
+        docs.map(doc => DocumentView.getDocumentView(doc, this.DocumentView?.())).forEach(dv => dv && DocumentView.SelectView(dv, true));
     };
     addDocument = (newBox: Doc | Doc[]) => {
         let retVal = false;
@@ -301,7 +279,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             retVal = this._props.addDocument?.(newBox) || false;
             if (retVal) {
                 this.bringToFront(newBox);
-                this._clusters.updateCluster(newBox);
+                this._clusters.addDocument(newBox);
             }
         } else {
             retVal = this._props.addDocument?.(newBox) || false;
@@ -416,7 +394,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 res(this.DocumentView?.());
                 return;
             }
-            const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
+            const findDoc = (finish: (dv: DocumentView) => void) => DocumentView.addViewRenderedCb(doc, dv => finish(dv));
             findDoc(dv => res(dv));
         });
 
@@ -459,7 +437,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             !d._keepZWhenDragged && (d.zIndex = zsorted.length + 1 + i); // bringToFront
         }
 
-        (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this._clusters.updateClusterDocs(docDragData.droppedDocuments);
+        (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this._clusters.addDocuments(docDragData.droppedDocuments);
         return true;
     }
 
@@ -528,7 +506,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         break;
                     case InkTool.None:
                         if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
-                            const hit = this._clusters.pickCluster(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
+                            const hit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
                             setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, hit !== -1, false);
                         }
                         break;
@@ -654,7 +632,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     onPointerMove = (e: PointerEvent) => {
-        if (this._clusters.tryDragCluster(e)) {
+        if (this._clusters.tryToDrag(e)) {
             e.stopPropagation(); // we're moving a cluster, so stop propagation and return true to end panning and let the document drag take over
             return true;
         }
@@ -675,7 +653,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
         // prettier-ignore
         return this.childDocs 
-            .map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.()))
+            .map(doc => DocumentView.getDocumentView(doc, this.DocumentView?.()))
             .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
             .map(inkView => inkView!)
             .map(inkView => ({ inkViewBounds: inkView.getBounds, inkStroke: inkView.ComponentView as InkingStroke, inkView }))
@@ -776,7 +754,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.childDocs
             .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
             .forEach(doc => {
-                const otherInk = DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())?.ComponentView as InkingStroke;
+                const otherInk = DocumentView.getDocumentView(doc, this.DocumentView?.())?.ComponentView as InkingStroke;
                 const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
                 const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
                 const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt));
@@ -1078,17 +1056,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 {...OmitKeys(entry, ['replica', 'pair']).omit}
                 key={childLayout[Id] + (entry.replica || '')}
                 Document={childLayout}
+                parent={this}
                 containerViewPath={this.DocumentView?.().docViewPath}
                 styleProvider={this._clusters.styleProvider}
                 TemplateDataDocument={childData}
                 dragStarting={this.dragStarting}
                 dragEnding={this.dragEnding}
+                isAnyChildContentActive={this.isAnyChildContentActive}
                 isGroupActive={this._props.isGroupActive}
                 renderDepth={this._props.renderDepth + 1}
                 hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)}
                 suppressSetHeight={!!this.layoutEngine}
                 RenderCutoffProvider={this.renderCutoffProvider}
-                CollectionFreeFormView={this}
                 LayoutTemplate={childLayout.z ? undefined : this._props.childLayoutTemplate}
                 LayoutTemplateString={childLayout.z ? undefined : this._props.childLayoutString}
                 rootSelected={childData ? this.rootSelected : returnFalse}
@@ -1282,7 +1261,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 })
             );
 
-        this._clusters.initClusters();
+        this._clusters.initLayout();
         return elements;
     };
 
@@ -1308,7 +1287,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     childDocsFunc = () => this.childDocs;
     closeInfo = action(() => { 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} />);
+    static _infoUI: ((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => JSX.Element) | null = null;
+    static SetInfoUICreator(func: (doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => JSX.Element) {
+        CollectionFreeFormView._infoUI = func;
+    }
+    infoUI = () =>
+        Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth
+            ? null //
+            : CollectionFreeFormView._infoUI?.(this.Document, this.layoutDoc, this.childDocsFunc, this.closeInfo) || null;
 
     componentDidMount() {
         this._props.setContentViewBox?.(this);
@@ -1551,9 +1537,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
 
         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[Id] && !DragManager.docsBeingDragged.includes(doc))
-            .forEach(doc => DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited));
+        activeDocs.filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc)).forEach(doc => DocumentView.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited));
 
         const horizLines: number[] = [];
         const vertLines: number[] = [];
@@ -1654,7 +1638,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge}
                 addDocTab={this.addDocTab}
                 slowLoadDocuments={this.slowLoadDocuments}
-                trySelectCluster={this._clusters.trySelectCluster}
+                trySelectCluster={this._clusters.tryToSelect}
                 activeDocuments={this.getActiveDocuments}
                 selectDocuments={this.selectDocuments}
                 addDocument={this.addDocument}
@@ -1744,22 +1728,22 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 }
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
-    !readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame();
+    !readOnly && (DocumentView.Selected()[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);
+    !readOnly && (DocumentView.Selected()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
-    const selView = SelectionManager.Views;
+    const selView = DocumentView.Selected();
     if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent';
     runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.()));
     return undefined;
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
-    SelectionManager.Views.forEach(view =>
+    DocumentView.Selected().forEach(view =>
         view._props.pinToPres(view.Document, {
             currentFrame: Cast(view.Document.currentFrame, 'number', null),
             pinData: {
@@ -1772,16 +1756,16 @@ ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function bringToFront() {
-    SelectionManager.Views.forEach(view => CollectionFreeFormView.from(view)?.bringToFront(view.Document));
+    DocumentView.Selected().forEach(view => CollectionFreeFormView.from(view)?.bringToFront(view.Document));
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function sendToBack() {
-    SelectionManager.Views.forEach(view => CollectionFreeFormView.from(view)?.bringToFront(view.Document, true));
+    DocumentView.Selected().forEach(view => CollectionFreeFormView.from(view)?.bringToFront(view.Document, true));
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function datavizFromSchema() {
     // creating a dataviz doc to represent the schema table
-    SelectionManager.Views.forEach(viewIn => {
+    DocumentView.Selected().forEach(viewIn => {
         const view = viewIn;
         if (!view.layoutDoc.schema_columnKeys) {
             view.layoutDoc.schema_columnKeys = new List<string>(['title', 'type', 'author', 'author_date']);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 3ab04f403..b96444024 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -17,15 +17,14 @@ import { CognitiveServices } from '../../../cognitive_services/CognitiveServices
 import { DocUtils } from '../../../documents/DocUtils';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Docs, DocumentOptions } from '../../../documents/Documents';
-import { SelectionManager } from '../../../util/SelectionManager';
-import { freeformScrollMode } from '../../../util/SettingsManager';
-import { SnappingManager } from '../../../util/SnappingManager';
+import { SnappingManager, freeformScrollMode } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { UndoManager, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { MarqueeViewBounds } from '../../PinFuncs';
 import { PreviewCursor } from '../../PreviewCursor';
+import { DocumentView } from '../../nodes/DocumentView';
 import { OpenWhere } from '../../nodes/OpenWhere';
 import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
@@ -175,7 +174,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
                 this.pasteTable(ns, x, y);
             })();
             e.stopPropagation();
-        } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views.length < 2) {
+        } else if (!e.ctrlKey && !e.metaKey && DocumentView.Selected().length < 2) {
             FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this._props.childLayoutString ? e.key : '';
             FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note');
             this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100));
@@ -249,7 +248,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         if (this._visible) {
             const mselect = this.marqueeSelect();
             if (!e.shiftKey) {
-                SelectionManager.DeselectAll(mselect.length ? undefined : this._props.Document);
+                DocumentView.DeselectAll(mselect.length ? undefined : this._props.Document);
             }
             const docs = mselect.length ? mselect : [this._props.Document];
             this._props.selectDocuments(docs);
@@ -344,7 +343,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     @undoBatch
     delete = action((e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
         const selected = this.marqueeSelect(false);
-        SelectionManager.DeselectAll();
+        DocumentView.DeselectAll();
         selected.forEach(doc => {
             hide ? (doc.hidden = true) : this._props.removeDocument?.(doc);
         });
@@ -380,7 +379,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     @undoBatch
     pileup = action(() => {
         const selected = this.marqueeSelect(false);
-        SelectionManager.DeselectAll();
+        DocumentView.DeselectAll();
         selected.forEach(d => this._props.removeDocument?.(d));
         const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!;
         this._props.addDocument?.(newCollection);
@@ -390,7 +389,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     });
 
     /**
-     * This triggers the TabDocView.PinDoc method which is the universal method
+     * This triggers the DocumentView.PinDoc method which is the universal method
      * used to pin documents to the currently active presentation trail.
      *
      * This one is unique in that it includes the bounds associated with marquee view.
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index e68ed0e17..eac0dc0e1 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -14,7 +14,6 @@ import { Id } from '../../../../fields/FieldSymbols';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
-import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { dropActionType } from '../../../util/DropActionTypes';
 import { SettingsManager } from '../../../util/SettingsManager';
@@ -23,7 +22,6 @@ import { UndoStack } from '../../UndoStack';
 import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
 import { DocumentView } from '../../nodes/DocumentView';
 import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
-import { CollectionStackedTimeline } from '../CollectionStackedTimeline';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionLinearView.scss';
 
@@ -128,14 +126,14 @@ export class CollectionLinearView extends CollectionSubView() {
             </span>
         );
     getCurrentlyPlayingUI = () =>
-        !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : (
+        !DocumentView.CurrentlyPlaying?.length ? null : (
             <span className="bottomPopup-background">
                 <span className="bottomPopup-text">
                     Currently playing:
-                    {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => (
+                    {DocumentView.CurrentlyPlaying.map((clip, i) => (
                         <>
-                            <span className="audio-title" onPointerDown={() => DocumentManager.Instance.showDocument(clip.Document, { willZoomCentered: true })}>
-                                {clip.Document.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '  ' : ',')}
+                            <span className="audio-title" onPointerDown={() => DocumentView.showDocument(clip.Document, { willZoomCentered: true })}>
+                                {clip.Document.title + (i === DocumentView.CurrentlyPlaying.length - 1 ? '  ' : ',')}
                             </span>
                             <FontAwesomeIcon icon={!clip.ComponentView?.IsPlaying?.() ? 'play' : 'pause'} size="lg" onPointerDown={() => clip.ComponentView?.TogglePause?.()} />{' '}
                             <FontAwesomeIcon icon="times" size="lg" onPointerDown={() => clip.ComponentView?.Pause?.()} />{' '}
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index 49ba85524..931e2c5e0 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { UndoManager } from '../../../util/UndoManager';
-import { StyleProp } from '../../StyleProvider';
+import { StyleProp } from '../../StyleProp';
 import { StyleProviderFuncType } from '../../nodes/FieldView';
 import { DimUnit } from './CollectionMulticolumnView';
 
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index ad77c327d..cff0a8b4c 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { UndoManager } from '../../../util/UndoManager';
-import { StyleProp } from '../../StyleProvider';
+import { StyleProp } from '../../StyleProp';
 import { StyleProviderFuncType } from '../../nodes/FieldView';
 import { DimUnit } from './CollectionMultirowView';
 
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 406a7d626..b30954ffd 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -15,16 +15,15 @@ import { ColumnType } from '../../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { DocUtils } from '../../../documents/DocUtils';
 import { Docs, DocumentOptions, FInfo } from '../../../documents/Documents';
-import { DocumentManager } from '../../../util/DocumentManager';
 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';
 import { ContextMenu } from '../../ContextMenu';
 import { EditableView } from '../../EditableView';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
+import { StyleProp } from '../../StyleProp';
+import { DefaultStyleProvider } from '../../StyleProvider';
 import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
@@ -92,12 +91,12 @@ export class CollectionSchemaView extends CollectionSubView() {
     }
 
     @computed get _selectedDocs() {
-        const selected = SelectionManager.Docs.filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document));
+        const selected = DocumentView.SelectedDocs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document));
         if (!selected.length) {
             // if no schema doc is directly selected, test if a child of a schema doc is selected (such as in the preview window)
-            const childOfSchemaDoc = SelectionManager.Docs.find(sel => DocumentManager.GetContextPath(sel, true).includes(this.Document));
+            const childOfSchemaDoc = DocumentView.SelectedDocs().find(sel => DocumentView.getContextPath(sel, true).includes(this.Document));
             if (childOfSchemaDoc) {
-                const contextPath = DocumentManager.GetContextPath(childOfSchemaDoc, true);
+                const contextPath = DocumentView.getContextPath(childOfSchemaDoc, true);
                 return [contextPath[contextPath.indexOf(childOfSchemaDoc) - 1]]; // the schema doc that is "selected" by virtue of one of its children being selected
             }
         }
@@ -175,6 +174,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         document.removeEventListener('keydown', this.onKeyDown);
     }
 
+    isUnstyledView = returnTrue; // used by style provide via ViewBoxInterface
     rowIndex = (doc: Doc) => this.sortedDocs.docs.indexOf(doc);
 
     @action
@@ -190,7 +190,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                             !e.shiftKey && this.clearSelection();
                             const newDoc = this.sortedDocs.docs[lastIndex + 1];
                             if (this._selectedDocs.includes(newDoc)) {
-                                SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+                                DocumentView.DeselectView(DocumentView.getFirstDocumentView(curDoc));
                             } else {
                                 this.addDocToSelection(newDoc, e.shiftKey);
                                 this._selectedCell && (this._selectedCell[0] = newDoc);
@@ -209,7 +209,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                         if (firstIndex > 0 && firstIndex < this.childDocs.length) {
                             !e.shiftKey && this.clearSelection();
                             const newDoc = this.sortedDocs.docs[firstIndex - 1];
-                            if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+                            if (this._selectedDocs.includes(newDoc)) DocumentView.DeselectView(DocumentView.getFirstDocumentView(curDoc));
                             else {
                                 this.addDocToSelection(newDoc, e.shiftKey);
                                 this._selectedCell && (this._selectedCell[0] = newDoc);
@@ -416,12 +416,12 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     @action
     addDocToSelection = (doc: Doc, extendSelection: boolean) => {
-        const rowDocView = DocumentManager.Instance.getDocumentView(doc);
-        if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection);
+        const rowDocView = DocumentView.getDocumentView(doc);
+        if (rowDocView) DocumentView.SelectView(rowDocView, extendSelection);
     };
 
     @action
-    clearSelection = () => SelectionManager.DeselectAll();
+    clearSelection = () => DocumentView.DeselectAll();
 
     selectRows = (doc: Doc, lastSelected: Doc) => {
         const index = this.rowIndex(doc);
@@ -476,9 +476,9 @@ export class CollectionSchemaView extends CollectionSubView() {
             this.dataDoc[this.fieldKey ?? 'data'] = new List<Doc>([...removed, ...draggedDocs, ...pushedDocs]);
             this.clearSelection();
             draggedDocs.forEach(doc => {
-                const draggedView = DocumentManager.Instance.getFirstDocumentView(doc);
-                if (draggedView) DocumentManager.Instance.RemoveView(draggedView);
-                DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true));
+                const draggedView = DocumentView.getFirstDocumentView(doc);
+                if (draggedView) DocumentView.removeView(draggedView);
+                DocumentView.addViewRenderedCb(doc, dv => dv.select(true));
             });
             return true;
         }
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index dfef3aa48..ee6987e89 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -19,9 +19,7 @@ import { ColumnType } from '../../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast, toList } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { FInfo, FInfoFieldType } from '../../../documents/Documents';
-import { DocFocusOrOpen } from '../../../util/DocumentManager';
 import { dropActionType } from '../../../util/DropActionTypes';
-import { SettingsManager } from '../../../util/SettingsManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch, undoable } from '../../../util/UndoManager';
@@ -29,7 +27,7 @@ import { EditableView } from '../../EditableView';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DefaultStyleProvider } from '../../StyleProvider';
 import { Colors } from '../../global/globalEnums';
-import { returnEmptyDocViewList } from '../../nodes/DocumentView';
+import { DocFocusOrOpen, returnEmptyDocViewList } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
@@ -300,8 +298,8 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
                         icon={<FontAwesomeIcon size="sm" icon="caret-down" />}
                         size={Size.XSMALL}
                         type={Type.TERT}
-                        color={SettingsManager.userColor}
-                        background={SettingsManager.userBackgroundColor}
+                        color={SnappingManager.userColor}
+                        background={SnappingManager.userBackgroundColor}
                         popup={
                             <div style={{ width: 'fit-content', height: '200px' }}>
                                 <DatePicker open dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} />
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index e75b01ab6..c595681b7 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -10,7 +10,6 @@ import { Gestures } from '../../../pen-gestures/GestureTypes';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
 import { UndoManager, undoable } from '../../util/UndoManager';
 import { GestureOverlay } from '../GestureOverlay';
 import { InkingStroke } from '../InkingStroke';
@@ -26,14 +25,14 @@ import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function IsNoneSelected() {
-    return SelectionManager.Views.length <= 0;
+    return DocumentView.Selected().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, getSelected: boolean) {
-    if (getSelected) return SelectionManager.Docs;
-    const selected = SelectionManager.Docs.lastElement();
+    if (getSelected) return DocumentView.SelectedDocs();
+    const selected = DocumentView.SelectedDocs().lastElement();
     selected ? (selected._type_collection = view) : console.log('[FontIconBox.tsx] changeView failed');
     return undefined;
 });
@@ -41,7 +40,7 @@ ScriptingGlobals.add(function setView(view: string, getSelected: boolean) {
 // 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;
+    const selectedViews = DocumentView.Selected();
     if (Doc.ActiveTool !== InkTool.None) {
         if (checkResult) {
             return ActiveFillColor();
@@ -71,7 +70,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
             }
         });
     } else {
-        const selected = SelectionManager.Docs.length ? SelectionManager.Docs : LinkManager.Instance.currentLink ? [LinkManager.Instance.currentLink] : [];
+        const selected = DocumentView.SelectedDocs().length ? DocumentView.SelectedDocs() : LinkManager.Instance.currentLink ? [LinkManager.Instance.currentLink] : [];
         if (checkResult) {
             return selected.lastElement()?._backgroundColor ?? 'transparent';
         }
@@ -90,10 +89,10 @@ ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) {
 // 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;
+        return DocumentView.Selected().length ? StrCast(DocumentView.SelectedDocs().lastElement().layout_headingColor) : Doc.SharingDoc().headingColor;
     }
-    if (SelectionManager.Views.length) {
-        SelectionManager.Docs.forEach(doc => {
+    if (DocumentView.Selected().length) {
+        DocumentView.SelectedDocs().forEach(doc => {
             doc[DocData].layout_headingColor = color === 'transparent' ? undefined : color;
             doc.layout_showTitle = color === 'transparent' ? undefined : StrCast(doc.layout_showTitle, 'title');
         });
@@ -108,7 +107,7 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
 // 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;
+    const selected = DocumentView.Selected().length ? DocumentView.Selected()[0] : undefined;
     if (checkResult) {
         if (NumCast(selected?.Document.z) >= 1) return true;
         return false;
@@ -119,7 +118,7 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
 
 // 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();
+    const selected = DocumentView.SelectedDocs().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', {
@@ -153,7 +152,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
         return map.get(attr)?.checkResult(selected);
     }
     const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
-    SelectionManager.Views.map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv));
+    DocumentView.Selected().map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv));
     setTimeout(() => batch.end(), 100);
     return undefined;
 });
@@ -347,7 +346,7 @@ 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();
+    const selected = DocumentView.SelectedDocs().lastElement() ?? Doc.UserDoc();
     // prettier-ignore
     const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
         ['inkMask', {
@@ -381,7 +380,20 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fil
         return map.get(option)?.checkResult();
     }
     map.get(option)?.setMode();
-    SelectionManager.Docs.filter(doc => doc._layout_isSvg).map(doc => map.get(option)?.setInk(doc));
+    DocumentView.SelectedDocs()
+        .filter(doc => doc._layout_isSvg)
+        .map(doc => map.get(option)?.setInk(doc));
+    return undefined;
+});
+
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) {
+    if (readOnly) {
+        return DocumentView.Selected().some(dv => dv.Document.keepZWhenDragged);
+    }
+    DocumentView.Selected().forEach(dv => {
+        dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged;
+    });
     return undefined;
 });
 
@@ -390,7 +402,7 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fil
  * */
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
-    const selected = SelectionManager.Views.lastElement();
+    const selected = DocumentView.Selected().lastElement();
     if (selected?.Document.type === DocumentType.WEB) {
         if (checkResult) {
             return StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href);
@@ -401,7 +413,7 @@ ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function webForward(checkResult?: boolean) {
-    const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox;
+    const selected = DocumentView.Selected().lastElement()?.ComponentView as WebBox;
     if (checkResult) {
         return selected?.forward(checkResult) ? undefined : 'lightGray';
     }
@@ -410,24 +422,24 @@ ScriptingGlobals.add(function webForward(checkResult?: boolean) {
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function webBack() {
-    const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox;
+    const selected = DocumentView.Selected().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;
+    const selected = DocumentView.Selected().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;
+    const selected = DocumentView.Selected().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;
+    const selected = DocumentView.Selected().lastElement()?.ComponentView as ImageBox;
     selected?.rotate();
 });
 
@@ -436,7 +448,7 @@ ScriptingGlobals.add(function imageRotate90() {
  * */
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) {
-    const selected = SelectionManager.Docs.lastElement();
+    const selected = DocumentView.SelectedDocs().lastElement();
     if (checkResult && selected) {
         const result: boolean = NumCast(selected.schema_previewWidth) > 0;
         if (result) return Colors.MEDIUM_BLUE;
@@ -453,7 +465,7 @@ ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) {
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
-    const selected = SelectionManager.Docs.lastElement();
+    const selected = DocumentView.SelectedDocs().lastElement();
     if (checkResult && selected) {
         return NumCast(selected._schema_singleLine) > 0 ? Colors.MEDIUM_BLUE : 'transparent';
     }
@@ -468,7 +480,7 @@ ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
  */
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
-    SelectionManager.Docs.forEach(doc => { doc._text_fontFamily = key; }); // prettier-ignore
+    DocumentView.SelectedDocs().forEach(doc => { doc._text_fontFamily = key; }); // prettier-ignore
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     if (checkResult) {
         return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily);
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index f99a18db2..cd735318e 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -8,7 +8,6 @@ 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';
@@ -59,8 +58,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
                   ? this.props.docView._props.LayoutTemplateString?.includes('link_anchor_1')
                       ? DocCast(linkDoc.link_anchor_2)
                       : DocCast(linkDoc.link_anchor_1)
-                  : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) ||
-                    LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null)?.annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null));
+                  : Doc.getOppositeAnchor(linkDoc, sourceDoc) || Doc.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null)?.annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null));
             return !destDoc || !sourceDoc ? null : (
                 <LinkMenuItem
                     key={linkDoc[Id]}
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index df3accb0d..9ce04ffac 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -11,13 +11,10 @@ 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 } from '../../util/DragManager';
 import { dropActionType } from '../../util/DropActionTypes';
-import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
-import { SelectionManager } from '../../util/SelectionManager';
-import { SettingsManager } from '../../util/SettingsManager';
+import { SnappingManager } from '../../util/SnappingManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
@@ -38,7 +35,7 @@ interface LinkMenuItemProps {
 
 // drag links and drop link targets (embedding them if needed)
 export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
-    const draggedDocs = (specificLinks || LinkManager.Links(sourceDoc)).map(link => LinkManager.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
+    const draggedDocs = (specificLinks || LinkManager.Links(sourceDoc)).map(link => Doc.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
 
     if (draggedDocs.length) {
         const moddrag: Doc[] = [];
@@ -81,7 +78,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
 
     onIconDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, returnFalse, returnFalse, () => {
-            const ancestor = DocumentManager.LinkCommonAncestor(this._props.linkDoc);
+            const ancestor = DocumentView.linkCommonAncestor(this._props.linkDoc);
             if (!ancestor?.ComponentView?.removeDocument?.(this._props.linkDoc)) {
                 ancestor?.ComponentView?.addDocument?.(this._props.linkDoc);
             }
@@ -105,14 +102,14 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                     Doc.ActivePresentation = trail;
                     DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight);
                 } else {
-                    SelectionManager.SelectView(this._props.docView, false);
+                    DocumentView.SelectView(this._props.docView, false);
                     LinkManager.Instance.currentLink = this._props.linkDoc === LinkManager.Instance.currentLink ? undefined : this._props.linkDoc;
                     LinkManager.Instance.currentLinkAnchor = LinkManager.Instance.currentLink ? this.sourceAnchor : undefined;
 
-                    if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
+                    if ((SnappingManager.PropertiesWidth ?? 0) < 100) {
                         setTimeout(
                             action(() => {
-                                SettingsManager.Instance.propertiesWidth = 250;
+                                SnappingManager.SetPropertiesWidth(250);
                             })
                         );
                     }
@@ -126,10 +123,12 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
             this,
             e,
             moveEv => {
-                const eleClone: any = this._drag.current!.cloneNode(true);
-                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?.();
+                const eleClone: any = this._drag.current?.cloneNode(true);
+                if (eleClone) {
+                    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;
             },
             emptyFunction,
@@ -146,13 +145,13 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                               : undefined;
 
                     if (focusDoc) this._props.docView._props.focus(focusDoc, { instant: true });
-                    LinkFollower.FollowLink(this._props.linkDoc, this._props.sourceDoc, false);
+                    DocumentView.FollowLink(this._props.linkDoc, this._props.sourceDoc, false);
                 }
             }
         );
     };
 
-    deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this._props.linkDoc))));
+    deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Doc.DeleteLink?.(this._props.linkDoc))));
     @observable _hover = false;
     docView = () => this._props.docView;
     render() {
@@ -181,7 +180,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                 style={{
                     fontSize: this._hover ? 'larger' : undefined,
                     fontWeight: this._hover ? 'bold' : undefined,
-                    background: LinkManager.Instance.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+                    background: LinkManager.Instance.currentLink === this._props.linkDoc ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
                 }}>
                 <div className="linkMenu-item-content expand-two">
                     <div
diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
index 72d81e9eb..e48e993cf 100644
--- a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
+++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
@@ -2,7 +2,6 @@ import { action } from 'mobx';
 import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
-import { SelectionManager } from '../../../util/SelectionManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
 import { OpenWhereMod } from '../../nodes/OpenWhere';
@@ -26,7 +25,7 @@ export const ButtonMenu = (props: IButtonMenu) => {
                 onClick={e => {
                     e.stopPropagation();
                     CollectionDockingView.AddSplit(NewLightboxView.LightboxDoc || NewLightboxView.LightboxDoc!, OpenWhereMod.none);
-                    SelectionManager.DeselectAll();
+                    DocumentView.DeselectAll();
                     NewLightboxView.SetNewLightboxDoc(undefined);
                 }}></div>
             <div
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index dc6bf3e9c..558ce7e38 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -9,16 +9,11 @@ import { emptyFunction } from '../../../Utils';
 import { CreateLinkToActiveAudio, Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast, toList } from '../../../fields/Types';
-import { DocumentManager } from '../../util/DocumentManager';
-import { LinkManager } from '../../util/LinkManager';
-import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
 import { DefaultStyleProvider } from '../StyleProvider';
-import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
-import { TabDocView } from '../collections/TabDocView';
 import { DocumentView } from '../nodes/DocumentView';
 import { OpenWhere } from '../nodes/OpenWhere';
 import { ExploreView } from './ExploreView';
@@ -79,7 +74,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
         }
     }
     public static AddDocTab = (docsIn: Doc | Doc[], location: OpenWhere, layoutTemplate?: Doc | string) => {
-        SelectionManager.DeselectAll();
+        DocumentView.DeselectAll();
         const doc = toList(docsIn).lastElement();
         return (
             doc &&
@@ -110,8 +105,8 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
         } else {
             const l = CreateLinkToActiveAudio(() => doc).lastElement();
             l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
-            CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
-            // TabDocView.PinDoc(doc, { hidePresBox: true });
+            DocumentView.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
+            // DocumentView.PinDoc(doc, { hidePresBox: true });
             this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]);
             if (doc !== LightboxView.LightboxDoc) {
                 this._savedState = {
@@ -130,7 +125,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
                 ...future
                     .slice()
                     .sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow))
-                    .sort((a, b) => LinkManager.Links(a).length - LinkManager.Links(b).length),
+                    .sort((a, b) => Doc.Links(a).length - Doc.Links(b).length),
             ];
         }
         this._doc = doc;
@@ -148,11 +143,11 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
     @action public static Next() {
         const doc = NewLightboxView._doc!;
         const target = (NewLightboxView._docTarget = this._future?.pop());
-        const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+        const targetDocView = target && DocumentView.getLightboxDocumentView(target);
         if (targetDocView && target) {
             const l = CreateLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
             l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
-            DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
+            DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
             if (NewLightboxView._history?.lastElement().target !== target) NewLightboxView._history?.push({ doc, target });
         } else if (!target && NewLightboxView.path.length) {
             const saved = NewLightboxView._savedState;
@@ -182,10 +177,10 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
             return;
         }
         const { doc, target } = NewLightboxView._history?.lastElement();
-        const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
+        const docView = DocumentView.getLightboxDocumentView(target || doc);
         if (docView) {
             NewLightboxView._docTarget = target;
-            target && DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
+            target && DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
         } else {
             NewLightboxView.SetNewLightboxDoc(doc, target);
         }
@@ -277,7 +272,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
                     removeDocument={undefined}
                     whenChildContentsActiveChanged={emptyFunction}
                     addDocTab={this.addDocTab}
-                    pinToPres={TabDocView.PinDoc}
+                    pinToPres={DocumentView.PinDoc}
                     focus={emptyFunction}
                 />
             </GestureOverlay>
@@ -316,8 +311,8 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
         if (coll) {
             const fieldKey = Doc.LayoutFieldKey(coll);
             const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '_annotations'])];
-            const links = LinkManager.Links(coll)
-                .map(link => LinkManager.getOppositeAnchor(link, coll))
+            const links = Doc.Links(coll)
+                .map(link => Doc.getOppositeAnchor(link, coll))
                 .filter(doc => doc)
                 .map(doc => doc!);
             NewLightboxView.SetNewLightboxDoc(coll, undefined, contents.length ? contents : links);
diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
index 23027808f..375408d01 100644
--- a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
+++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
@@ -1,12 +1,12 @@
 import * as React from 'react';
-import { IRecommendation } from './utils';
-import './Recommendation.scss';
-import { getType } from '../../utils';
 import { FaEyeSlash } from 'react-icons/fa';
-import { NewLightboxView } from '../../NewLightboxView';
-import { DocumentManager } from '../../../../util/DocumentManager';
 import { Doc } from '../../../../../fields/Doc';
 import { Docs } from '../../../../documents/Documents';
+import { DocumentView } from '../../../nodes/DocumentView';
+import { NewLightboxView } from '../../NewLightboxView';
+import { getType } from '../../utils';
+import './Recommendation.scss';
+import { IRecommendation } from './utils';
 
 export const Recommendation = (props: IRecommendation) => {
     const { title, data, type, text, transcript, loading, source, previewUrl, related_concepts, distance, docId } = props;
@@ -17,7 +17,7 @@ export const Recommendation = (props: IRecommendation) => {
             onClick={() => {
                 let doc: Doc | null = null;
                 if (source == 'Dash' && docId) {
-                    const docView = DocumentManager.Instance.getDocumentViewsById(docId).lastElement();
+                    const docView = DocumentView.getDocumentViewsById(docId).lastElement();
                     if (docView) {
                         doc = docView.Document;
                     }
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 4697491e0..9deed4de4 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -18,14 +18,15 @@ import { DocumentType } from '../../documents/DocumentTypes';
 import { DocUtils } from '../../documents/DocUtils';
 import { Networking } from '../../Network';
 import { DragManager } from '../../util/DragManager';
-import { LinkManager } from '../../util/LinkManager';
 import { undoBatch } from '../../util/UndoManager';
 import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { DocViewUtils } from '../DocViewUtils';
 import { PinDocView, PinProps } from '../PinFuncs';
 import './AudioBox.scss';
+import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { OpenWhere } from './OpenWhere';
 
@@ -99,7 +100,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return this._props.PanelHeight() < 50;
     } // used to collapse timeline when node is shrunk
     @computed get links() {
-        return LinkManager.Links(this.dataDoc);
+        return Doc.Links(this.dataDoc);
     }
     @computed get mediaState() {
         return this.dataDoc.mediaState as mediaState;
@@ -218,9 +219,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @action
     removeCurrentlyPlaying = () => {
         const docView = this.DocumentView?.();
-        if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
-            const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
-            index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
+        if (DocumentView.CurrentlyPlaying && docView) {
+            const index = DocumentView.CurrentlyPlaying.indexOf(docView);
+            index !== -1 && DocumentView.CurrentlyPlaying.splice(index, 1);
         }
     };
 
@@ -228,11 +229,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @action
     addCurrentlyPlaying = () => {
         const docView = this.DocumentView?.();
-        if (!CollectionStackedTimeline.CurrentlyPlaying) {
-            CollectionStackedTimeline.CurrentlyPlaying = [];
+        if (!DocumentView.CurrentlyPlaying) {
+            DocumentView.CurrentlyPlaying = [];
         }
-        if (docView && CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView) === -1) {
-            CollectionStackedTimeline.CurrentlyPlaying.push(docView);
+        if (docView && DocumentView.CurrentlyPlaying.indexOf(docView) === -1) {
+            DocumentView.CurrentlyPlaying.push(docView);
         }
     };
 
@@ -251,7 +252,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._stream = await navigator.mediaDevices.getUserMedia({ audio: true });
         this._recorder = new MediaRecorder(this._stream);
         this.dataDoc[this.fieldKey + '_recordingStart'] = new DateField();
-        DocUtils.ActiveRecordings.push(this);
+        DocViewUtils.ActiveRecordings.push(this);
         this._recorder.ondataavailable = async (e: any) => {
             const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
             if (!(result instanceof Error)) {
@@ -278,8 +279,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             this.dataDoc[this.fieldKey + '_duration'] = (now - this._recordStart - this._pausedTime) / 1000;
             this.mediaState = mediaState.Paused;
             this._stream?.getAudioTracks()[0].stop();
-            const ind = DocUtils.ActiveRecordings.indexOf(this);
-            ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
+            const ind = DocViewUtils.ActiveRecordings.indexOf(this);
+            ind !== -1 && DocViewUtils.ActiveRecordings.splice(ind, 1);
         }
     };
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 691d07e31..685a5aca4 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -5,18 +5,16 @@ import { OmitKeys } from '../../../ClientUtils';
 import { numberRange } from '../../../Utils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { TransitionTimer } from '../../../fields/DocSymbols';
+import { InkField } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
 import { DocComponent } from '../DocComponent';
-import { StyleProp } from '../StyleProvider';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { StyleProp } from '../StyleProp';
 import './CollectionFreeFormDocumentView.scss';
 import { DocumentView, DocumentViewProps } from './DocumentView';
 import { FieldViewProps } from './FieldView';
@@ -43,7 +41,8 @@ interface freeFormProps {
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     RenderCutoffProvider: (doc: Doc) => boolean;
-    CollectionFreeFormView: CollectionFreeFormView;
+    isAnyChildContentActive: () => boolean;
+    parent: any;
 }
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & freeFormProps>() {
@@ -66,7 +65,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     ]; // fields that are configured to be animatable using animation frames
     public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
     public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
-    public static from(dv?: DocumentView) {
+    public static from(dv?: DocumentView): CollectionFreeFormDocumentView | undefined {
         return dv?._props.parent instanceof CollectionFreeFormDocumentView ? dv._props.parent : undefined;
     }
 
@@ -120,7 +119,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         );
     }
 
-    CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
     // this way, downstream code only invalidates when it uses a specific prop, not when any prop changes
     DataTransition = ()    => this.Transition || StrCast(this.Document.dataTransition); // prettier-ignore
     RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
@@ -185,13 +183,32 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             const currentFrame = Cast(doc._currentFrame, 'number', null);
             if (currentFrame === undefined) {
                 doc._currentFrame = 0;
-                CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+                this.setupKeyframes(childDocs, 0);
             }
-            CollectionFreeFormView.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0);
+            this.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0);
             doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
         }
     }
 
+    public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) {
+        const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, timer, undefined, true);
+        const timecode = Math.round(time);
+        docs.forEach(doc => {
+            this.animFields.forEach(val => {
+                const findexed = Cast(doc[`${val.key}_indexed`], listSpec('number'), null);
+                findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
+            });
+            this.animStringFields.forEach(val => {
+                const findexed = Cast(doc[`${val}_indexed`], listSpec('string'), null);
+                findexed?.length <= timecode + 1 && findexed.push(undefined as any as string);
+            });
+            this.animDataFields(doc).forEach(val => {
+                const findexed = Cast(doc[`${val}_indexed`], listSpec(InkField), null);
+                findexed?.length <= timecode + 1 && findexed.push(undefined as any);
+            });
+        });
+        return newTimer;
+    }
     public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) {
         docs.forEach(doc => {
             if (doc.appearFrame === undefined) doc.appearFrame = currTimecode;
@@ -220,7 +237,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         const containerDocView = this._props.containerViewPath?.().lastElement();
         const screenXf = containerDocView?.screenToContentsTransform();
         if (screenXf) {
-            SelectionManager.DeselectAll();
+            DocumentView.DeselectAll();
             if (topDoc.z) {
                 const spt = screenXf.inverse().transformPoint(NumCast(topDoc.x), NumCast(topDoc.y));
                 topDoc.z = 0;
@@ -233,7 +250,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 topDoc.z = 1;
                 [topDoc.x, topDoc.y] = fpt;
             }
-            setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, containerDocView), false), 0);
+            setTimeout(() => DocumentView.SelectView(DocumentView.getDocumentView(topDoc, containerDocView), false), 0);
         }
     };
 
@@ -255,7 +272,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     //  'inactive' - this is a group child but it is not active
     //  undefined - this is not activated by a group
     isGroupActive = () => {
-        if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
+        if (this._props.isAnyChildContentActive()) return undefined;
         const backColor = this.BackgroundColor;
         const isGroup = this.dataDoc.isGroup && (!backColor || backColor === 'transparent');
         return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index c42ca4468..6ae6bb228 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -15,7 +15,7 @@ import { dropActionType } from '../../util/DropActionTypes';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { PinDocView, PinProps } from '../PinFuncs';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
 import './ComparisonBox.scss';
 import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index ecfdcc229..15187b4e4 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -16,7 +16,6 @@ import { TraceMobx } from '../../../../fields/util';
 import { DocUtils } from '../../../documents/DocUtils';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Docs } from '../../../documents/Documents';
-import { DocumentManager } from '../../../util/DocumentManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
@@ -254,7 +253,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
             this.toggleSidebar();
         }
         return new Promise<Opt<DocumentView>>(res => {
-            DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+            DocumentView.addViewRenderedCb(doc, dv => res(dv));
         });
     };
     @computed get sidebarWidthPercent() {
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 1c3134185..bc35ab8c8 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -8,7 +8,6 @@ import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
 import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
-import { DocumentManager } from '../../../../util/DocumentManager';
 import { undoable } from '../../../../util/UndoManager';
 import {} from '../../../DocComponent';
 import { ObservableReactComponent } from '../../../ObservableReactComponent';
@@ -16,6 +15,7 @@ import { PinProps, PinDocView } from '../../../PinFuncs';
 import { DataVizBox } from '../DataVizBox';
 import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
 import './Chart.scss';
+import { DocumentView } from '../../DocumentView';
 
 export interface DataPoint {
     x: number;
@@ -71,7 +71,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
     }
     @computed get parentViz() {
         return DocCast(this._props.Document.dataViz_parentViz);
-        // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
+        // return LinkManager.Links(this._props.Document) // out of all links
         //     .filter(link => {
         //         return link.link_anchor_1 == this._props.Document.dataViz_parentViz;
         //     }) // get links where this chart doc is the target of the link
@@ -80,7 +80,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
     @computed get incomingHighlited() {
         // return selected x and y axes
         // otherwise, use the selection of whatever is linked to us
-        const incomingVizBox = DocumentManager.Instance.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox;
+        const incomingVizBox = DocumentView.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox;
         const highlitedRowIds = NumListCast(incomingVizBox?.layoutDoc?.dataViz_highlitedRows);
         return this._tableData.filter((record, i) => highlitedRowIds.includes(this._tableDataIds[i])); // get all the datapoints they have selected field set by incoming anchor
     }
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index ffb2f528a..ef6d1d412 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -84,7 +84,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
 
     @computed get parentViz() {
         return DocCast(this._props.Document.dataViz_parentViz);
-        // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
+        // return LinkManager.Links(this._props.Document) // out of all links
         //     .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
         //     .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
     }
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 8038b2cd4..5cd77e274 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -7,6 +7,7 @@ import * as React from 'react';
 import { ClientUtils, setupMoveUpEvents } from '../../../../../ClientUtils';
 import { emptyFunction } from '../../../../../Utils';
 import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
+import { DocData } from '../../../../../fields/DocSymbols';
 import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
 import { Cast, DocCast } from '../../../../../fields/Types';
@@ -152,11 +153,9 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                     DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
                         dragComplete: completeEv => {
                             if (!completeEv.aborted && completeEv.annoDragData && completeEv.annoDragData.linkSourceDoc && completeEv.annoDragData.dropDocument && completeEv.linkDocument) {
-                                completeEv.linkDocument.link_displayLine = true;
-                                completeEv.linkDocument.link_matchEmbeddings = true;
-                                completeEv.linkDocument.link_displayArrow = true;
-                                // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
-                                // e.annoDragData.linkSourceDoc.followLinkZoom = false;
+                                completeEv.linkDocument[DocData].link_matchEmbeddings = true;
+                                completeEv.linkDocument[DocData].stroke_startMarker = true;
+                                this._props.docView?.()?._props.addDocument?.(completeEv.linkDocument);
                             }
                         },
                     });
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index e902d1792..ec9db8480 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -31,7 +31,6 @@ import { FunctionPlotBox } from './FunctionPlotBox';
 import { ImageBox } from './ImageBox';
 import { KeyValueBox } from './KeyValueBox';
 import { LabelBox } from './LabelBox';
-import { LinkAnchorBox } from './LinkAnchorBox';
 import { LinkBox } from './LinkBox';
 import { LoadingBox } from './LoadingBox';
 import { MapBox } from './MapBox/MapBox';
@@ -87,18 +86,18 @@ interface HTMLtagProps {
 export class HTMLtag extends React.Component<HTMLtagProps> {
     click = () => {
         const clickScript = (this.props as any).onClick as Opt<ScriptField>;
-        clickScript?.script.run({ this: this.props.Document, self: this.props.Document, scale: this.props.scaling });
+        clickScript?.script.run({ this: this.props.Document, scale: this.props.scaling });
     };
     onInput = (e: React.FormEvent<HTMLDivElement>) => {
         const onInputScript = (this.props as any).onInput as Opt<ScriptField>;
-        onInputScript?.script.run({ this: this.props.Document, self: this.props.Document, value: (e.target as any).textContent });
+        onInputScript?.script.run({ this: this.props.Document, value: (e.target as any).textContent });
     };
     render() {
         const style: { [key: string]: any } = {};
         const divKeys = OmitKeys(this.props, ['children', 'dragStarting', 'dragEnding', 'htmltag', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit;
         const replacer = (match: any, expr: string) =>
             // bcz: this executes a script to convert a property expression string:  { script }  into a value
-            (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.Document, this: this.props.Document, scale: this.props.scaling }).result as string) || '';
+            (ScriptField.MakeFunction(expr, { this: Doc.name, scale: 'number' })?.script.run({ this: this.props.Document, scale: this.props.scaling }).result as string) || '';
         Object.keys(divKeys).forEach((prop: string) => {
             const p = (this.props as any)[prop] as string;
             style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer);
@@ -121,7 +120,6 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
         super(props);
         makeObservable(this);
     }
-
     @computed get layout(): string {
         TraceMobx();
         if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
@@ -186,7 +184,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
         let layoutFrame = this.layout;
 
         // replace code content with a script  >{content}<   as in  <HTMLdiv>{this.title}</HTMLdiv>
-        const replacer = (match: any, prefix: string, expr: string, postfix: string) => prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix;
+        const replacer = (match: any, prefix: string, expr: string, postfix: string) => prefix + ((ScriptField.MakeFunction(expr, { this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix;
         layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
 
         // replace HTML<tag> with corresponding HTML tag as in:  <HTMLdiv> becomes  <HTMLtag Document={props.Document} htmltag='div'>
@@ -205,7 +203,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
                 const code = XRegExp.matchRecursive(splits[1], '{', '}', '', { valueNames: ['between', 'left', 'match', 'right', 'between'] });
                 layoutFrame = splits[0] + ` ${func}={props.${func}} ` + splits[1].substring(code[1].end + 1);
                 const script = code[1].value.replace(/^‘/, '').replace(/’$/, ''); // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts
-                return ScriptField.MakeScript(script, { this: Doc.name, self: Doc.name, scale: 'number', value: 'string' });
+                return ScriptField.MakeScript(script, { this: Doc.name, scale: 'number', value: 'string' });
             }
             return undefined;
             // add input function to props
@@ -247,7 +245,6 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
                     PresElementBox,
                     SearchBox,
                     FunctionPlotBox,
-                    LinkAnchorBox,
                     InkingStroke,
                     LinkBox,
                     ScriptingBox,
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index 364406197..23d934edb 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -6,10 +6,8 @@ import { factory } from 'typescript';
 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 { SnappingManager } from '../../util/SnappingManager';
-import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DocumentView } from './DocumentView';
 
@@ -25,9 +23,6 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
         makeObservable(this);
     }
 
-    static get DocViews() {
-        return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.Contains(v)) : DocumentManager.Instance.DocumentViews;
-    }
     render() {
         const { view } = this._props;
         const { left, top, right } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 };
@@ -69,7 +64,7 @@ export class DocumentIconContainer extends React.Component {
                             const match = node.text.match(/d([0-9]+)/);
                             if (match) {
                                 const m = parseInt(match[1]);
-                                const doc = DocumentIcon.DocViews[m].Document;
+                                const doc = DocumentView.allViews()[m].Document;
                                 usedDocuments.add(m);
                                 return factory.createIdentifier(`idToDoc("${doc[Id]}")`);
                             }
@@ -81,7 +76,7 @@ export class DocumentIconContainer extends React.Component {
                 return ts.visitNode(root, visit);
             },
             getVars() {
-                const docs = DocumentIcon.DocViews;
+                const docs = DocumentView.allViews();
                 const capturedVariables: { [name: string]: FieldType } = {};
                 usedDocuments.forEach(index => {
                     capturedVariables[`d${index}`] = docs.length > index ? docs[index].Document : `d${index}`;
@@ -91,6 +86,6 @@ export class DocumentIconContainer extends React.Component {
         };
     }
     render() {
-        return DocumentIcon.DocViews.map((dv, i) => <DocumentIcon key={dv.DocUniqueId} index={i} view={dv} />);
+        return DocumentView.allViews().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 977899589..0c5156339 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -8,11 +8,8 @@ import * as React from 'react';
 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/DocUtils';
 import { DragManager } from '../../util/DragManager';
-import { Hypothesis } from '../../util/HypothesisUtils';
 import { LinkManager } from '../../util/LinkManager';
 import { UndoManager, undoBatch } from '../../util/UndoManager';
 import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -169,16 +166,6 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
                 LinkManager.Instance.currentLink = linkDoc;
 
                 if (linkDoc) {
-                    if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
-                        // if linking from a Hypothes.is annotation
-                        const linkDocData = linkDoc[DocData];
-                        linkDocData.linksToAnnotation = true;
-                        linkDocData.annotationId = DocumentLinksButton.AnnotationId;
-                        linkDocData.annotationUri = DocumentLinksButton.AnnotationUri;
-                        const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink);
-                        Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId, startIsAnnotation ? startLink : endLink); // edit annotation to add a Dash hyperlink to the linked doc
-                    }
-
                     TaskCompletionBox.textDisplayed = 'Link Created';
                     TaskCompletionBox.popupX = screenX;
                     TaskCompletionBox.popupY = screenY - 133;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1e7f1015a..f8ce50c98 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -7,10 +7,10 @@ import { IReactionDisposer, action, computed, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
-import { ClientUtils, DivWidth, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../ClientUtils';
+import { ClientUtils, DivWidth, isTargetChildOf as isParentOf, lightOrDark, returnFalse, 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 { AclAdmin, AclEdit, AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -20,20 +20,17 @@ import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
+import { AudioAnnoState } from '../../../server/SharedMediaTypes';
 import { DocServer } from '../../DocServer';
-import { Networking } from '../../Network';
 import { DocUtils, FollowLinkScript } from '../../documents/DocUtils';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
-import { DictationManager } from '../../util/DictationManager';
-import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { dropActionType } from '../../util/DropActionTypes';
 import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter';
-import { LinkManager, UPDATE_SERVER_CACHE } from '../../util/LinkManager';
+import { UPDATE_SERVER_CACHE } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SearchUtil } from '../../util/SearchUtil';
-import { SelectionManager } from '../../util/SelectionManager';
 import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
@@ -42,16 +39,15 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent, ViewBoxInterface } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { FieldsDropdown } from '../FieldsDropdown';
-import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
-import { AudioAnnoState, StyleProp } from '../StyleProvider';
+import { PinProps } from '../PinFuncs';
+import { StyleProp } from '../StyleProp';
 import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
 import { DocumentLinksButton } from './DocumentLinksButton';
 import './DocumentView.scss';
 import { FieldViewProps, FieldViewSharedProps } from './FieldView';
 import { FocusViewOptions } from './FocusViewOptions';
 import { KeyValueBox } from './KeyValueBox';
-import { LinkAnchorBox } from './LinkAnchorBox';
 import { OpenWhere } from './OpenWhere';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { PresEffect, PresEffectDirection } from './trails';
@@ -90,6 +86,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     // this makes mobx trace() statements more descriptive
     public get displayName()  { return 'DocumentViewInternal(' + this.Document.title + ')'; } // prettier-ignore
     public static SelectAfterContextMenu = true; //  whether a document should be selected after it's contextmenu is triggered.
+
     /**
      * This function is filled in by MainView to allow non-viewBox views to add Docs as tabs without
      * needing to know about/reference MainView
@@ -183,11 +180,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     // anchors that are not rendered as DocumentViews (marked as 'layout_unrendered' with their 'annotationOn' set to this document).  e.g.,
     //     - PDF text regions are rendered as an Annotations without generating a DocumentView, '
     //     - RTF selections are rendered via Prosemirror and have a mark which contains the Document ID for the annotation link
-    //     - and links to PDF/Web docs at a certain scroll location never create an explicit view.
-    // For each of these, we create LinkAnchorBox's on the border of the DocumentView.
+    //     - and links to PDF/Web docs at a certain scroll location never create an explicit anchor view.
     @computed get directLinks() {
         TraceMobx();
-        return LinkManager.Instance.getAllRelatedLinks(this.Document).filter(
+        return Doc.Links(this.Document).filter(
             link =>
                 (link.link_matchEmbeddings ? link.link_anchor_1 === this.Document : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.Document)) ||
                 (link.link_matchEmbeddings ? link.link_anchor_2 === this.Document : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.Document)) ||
@@ -197,11 +193,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     }
     @computed get _allLinks(): Doc[] {
         TraceMobx();
-        return LinkManager.Instance.getAllRelatedLinks(this.Document).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
+        return Doc.Links(this.Document).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
     }
 
     @computed get filteredLinks() {
-        return DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
+        return DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []);
     }
 
     componentWillUnmount() {
@@ -259,7 +255,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
         const docView = this._docView;
         if (this._mainCont.current && docView) {
-            const views = SelectionManager.Views.filter(dv => dv.ContentDiv);
+            const views = DocumentView.Selected().filter(dv => dv.ContentDiv);
             const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [docView];
             const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document));
             const screenXf = docView.screenToViewTransform();
@@ -289,8 +285,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     };
     onBrowseClick = (e: React.MouseEvent) => {
         const browseTransitionTime = 500;
-        SelectionManager.DeselectAll();
-        DocumentManager.Instance.showDocument(this.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
+        DocumentView.DeselectAll();
+        DocumentView.showDocument(this.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
             const options: FocusViewOptions = { pointFocus: { X: e.clientX, Y: e.clientY }, zoomTime: browseTransitionTime };
             if (!focused && this._docView) {
                 this._docView
@@ -327,7 +323,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     UndoManager.RunInBatch(() => this.onDoubleClickHandler.script.run(scriptProps, console.log).result?.select && this._props.select(false), 'on double click: ' + this.Document.title);
                 } else if (!Doc.IsSystem(this.Document) && defaultDblclick !== 'ignore') {
                     UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.Document, OpenWhere.lightbox), 'double tap');
-                    SelectionManager.DeselectAll();
+                    DocumentView.DeselectAll();
                     Doc.UnBrushDoc(this.Document);
                 } else {
                     this._singleClickFunc?.();
@@ -377,7 +373,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         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;
+        if (!DocumentView.DownDocView) DocumentView.DownDocView = this._docView;
 
         this._downX = e.clientX;
         this._downY = e.clientY;
@@ -463,7 +459,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     }, 'set toggle detail');
 
     drop = undoable((e: Event, de: DragManager.DropEvent) => {
-        if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
+        if (this._props.dontRegisterView) return false;
         if (this.Document === Doc.ActiveDashboard) {
             e.stopPropagation();
             e.preventDefault();
@@ -484,7 +480,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     if (linkDoc) {
                         de.complete.linkDocument = linkDoc;
                         linkDoc.layout_isSvg = true;
-                        DocumentManager.LinkCommonAncestor(linkDoc)?.ComponentView?.addDocument?.(linkDoc);
+                        DocumentView.linkCommonAncestor(linkDoc)?.ComponentView?.addDocument?.(linkDoc);
                     }
                 }
                 e.stopPropagation();
@@ -516,7 +512,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) && DocumentView.SelectView(this.DocumentView(), false);
         }
         // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
         if (e) {
@@ -576,8 +572,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             if (this._props.bringToFront) {
                 const zorders = cm.findByDescription('ZOrder...');
                 const zorderItems: ContextMenuProps[] = zorders && 'subitems' in zorders ? zorders.subitems : [];
-                zorderItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' });
-                zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, true)), icon: 'arrow-down' });
+                zorderItems.push({ description: 'Bring to Front', event: () => DocumentView.Selected().forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' });
+                zorderItems.push({ description: 'Send to Back', event: () => DocumentView.Selected().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(
@@ -601,7 +597,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
                     !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
                     !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
-                } else if (LinkManager.Links(this.Document).length) {
+                } else if (Doc.Links(this.Document).length) {
                     onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
                     onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
                     !existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' });
@@ -718,7 +714,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             case StyleProp.PointerEvents: return 'none';
             case StyleProp.Highlighting:  return undefined;
             case StyleProp.Opacity:       {
-                const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
+                const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []);
                 return filtered.some(link => link._link_displayArrow) ? 0 : undefined;
             }
             default:
@@ -726,34 +722,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         return this._props.styleProvider?.(doc, props, property);
     };
 
-    // 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;
-        return this.filteredLinks.map(link => (
-            <div className="documentView-anchorCont" key={link[Id]}>
-                <DocumentView
-                    {...this._props}
-                    isContentActive={returnFalse}
-                    Document={link}
-                    containerViewPath={this._props.docViewPath}
-                    PanelWidth={this.anchorPanelWidth}
-                    PanelHeight={this.anchorPanelHeight}
-                    dontRegisterView={false}
-                    layout_showTitle={returnEmptyString}
-                    hideCaptions
-                    hideLinkAnchors
-                    layout_fitWidth={returnTrue}
-                    removeDocument={this.removeLinkByHiding(link)}
-                    styleProvider={this.anchorStyleProvider}
-                    LayoutTemplate={undefined}
-                    LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.Document)}`)}
-                />
-            </div>
-        ));
-    }
-
     @computed get viewBoxContents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
@@ -780,7 +748,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     setTitleFocus={this.setTitleFocus}
                     hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)}
                 />
-                {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
             </div>
         );
     }
@@ -1008,50 +975,45 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             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(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; }, // prettier-ignore
-                continuous: { indefinite: false },
-            }).then(results => {
-                if (results && [DictationManager.Controls.Infringed].includes(results)) {
-                    DictationManager.Controls.stop();
-                }
-                onEnd?.();
-            });
-
-            gumStream = stream;
-            recorder = new MediaRecorder(stream);
-            recorder.ondataavailable = async (e: any) => {
-                const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
-                if (!(result instanceof Error)) {
-                    const audioField = new AudioField(result.accessPaths.agnostic.client);
-                    const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
-                    if (audioAnnos) audioAnnos.push(audioField);
-                    else dataDoc[field + '_audioAnnotations'] = new List([audioField]);
-                }
-            };
-            recorder.start();
-            const stopFunc = () => {
-                recorder.stop();
-                DictationManager.Controls.stop(/* false */);
-                dataDoc.audioAnnoState = AudioAnnoState.stopped;
-                gumStream.getAudioTracks()[0].stop();
-            };
-            if (onRecording) onRecording(stopFunc);
-            else setTimeout(stopFunc, 5000);
-        });
-    }
 }
 
 @observer
 export class DocumentView extends DocComponent<DocumentViewProps>() {
     public static ROOT_DIV = 'documentView-effectsWrapper';
+    // LinkFollower
+    public static FollowLink: (linkDoc: Opt<Doc>, sourceDoc: Doc, altKey: boolean) => boolean;
+    // selection funcs
+    public static DeselectAll: (except?: Doc) => void | undefined;
+    public static DeselectView: (dv: DocumentView | undefined) => void | undefined;
+    public static SelectView: (dv: DocumentView | undefined, extendSelection: boolean) => void | undefined;
+    public static Selected: () => DocumentView[];
+    public static SelectedDocs: () => Doc[];
+    public static SelectSchemaDoc: (doc: Doc, deselectAllFirst?: boolean) => void;
+    public static SelectedSchemaDoc: () => Opt<Doc>;
+    // view mgr funcs
+    public static activateTabView: (tabDoc: Doc) => boolean;
+    public static allViews: () => DocumentView[];
+    public static addView: (dv: DocumentView) => void | undefined;
+    public static removeView: (dv: DocumentView) => void | undefined;
+    public static addViewRenderedCb: (doc: Opt<Doc>, func: (dv: DocumentView) => any) => boolean;
+    public static getFirstDocumentView: (toFind: Doc) => DocumentView | undefined;
+    public static getDocumentView: (target: Doc | undefined, preferredCollection?: DocumentView) => Opt<DocumentView>;
+    public static getContextPath: (doc: Opt<Doc>, includeExistingViews?: boolean) => Doc[];
+    public static getLightboxDocumentView: (toFind: Doc) => Opt<DocumentView>;
+    public static showDocumentView: (targetDocView: DocumentView, options: FocusViewOptions) => Promise<void>;
+    public static showDocument: (
+        targetDoc: Doc, // document to display
+        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.
+    ) => Promise<void>;
+    public static linkCommonAncestor: (link: Doc) => DocumentView | undefined;
+    // pin func
+    public static PinDoc: (docIn: Doc | Doc[], pinProps: PinProps) => void;
+    // gesture
+    public static DownDocView: DocumentView | undefined; // the first DocView that receives a pointerdown event.  used by GestureOverlay to determine the doc a gesture should apply to.
+    // media playing
+    @observable public static CurrentlyPlaying: DocumentView[] = [];
+
     public get displayName() {  return 'DocumentView(' + (this.Document?.title??"") + ')';  } // prettier-ignore
     public ContentRef = React.createRef<HTMLDivElement>();
     private _htmlOverlayEffect: Opt<Doc>;
@@ -1138,14 +1100,14 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     componentDidMount() {
         runInAction(() => this.Document[DocViews].add(this));
         this._disposers.onViewMounted = reaction(() => ScriptCast(this.Document.onViewMounted)?.script?.run({ this: this.Document }).result, emptyFunction);
-        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this);
+        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentView.addView(this);
     }
 
     componentWillUnmount() {
         this._viewTimer && clearTimeout(this._viewTimer);
         runInAction(() => this.Document[DocViews].delete(this));
         Object.values(this._disposers).forEach(disposer => disposer?.());
-        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
+        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentView.removeView(this);
     }
 
     public set IsSelected(val)  { runInAction(() => { this._selected = val; }); } // prettier-ignore
@@ -1176,10 +1138,6 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         const xf = this.screenToContentsTransform().scale(this.nativeScaling).inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
 
-        if (this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
-            const docuBox = this.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
-            if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), transition: undefined };
-        }
         // transition is returned so that the bounds will 'update' at the end of an animated transition.  This is needed by xAnchor in LinkBox
         const transition = this.docViewPath().find((parent: DocumentView) => parent.DataTransition?.() || parent.ComponentView?.viewTransition?.());
         return { left, top, right, bottom, transition: transition?.DataTransition?.() || transition?.ComponentView?.viewTransition?.() };
@@ -1289,7 +1247,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         if (checkResult) {
             return Doc.UserDoc().defaultTextLayout;
         }
-        const view = SelectionManager.Views[0]?._props.renderDepth > 0 ? SelectionManager.Views[0] : undefined;
+        const view = DocumentView.Selected()[0]?._props.renderDepth > 0 ? DocumentView.Selected()[0] : undefined;
         undoable(() => {
             let tempDoc: Opt<Doc>;
             if (view) {
@@ -1359,11 +1317,11 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale;
     isSelected = () => this.IsSelected;
     select = (extendSelection: boolean, focusSelection?: boolean) => {
-        if (this.IsSelected && SelectionManager.Views.length > 1) SelectionManager.DeselectView(this);
+        if (this.IsSelected && DocumentView.Selected().length > 1) DocumentView.DeselectView(this);
         else {
-            SelectionManager.SelectView(this, extendSelection);
+            DocumentView.SelectView(this, extendSelection);
             if (focusSelection) {
-                DocumentManager.Instance.showDocument(this.Document, {
+                DocumentView.showDocument(this.Document, {
                     willZoomCentered: true,
                     zoomScale: 0.9,
                     zoomTime: 500,
@@ -1506,6 +1464,37 @@ export function returnEmptyDocViewList() {
     return emptyPath;
 }
 
+// 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 = DocumentView.getDocumentView(containingDoc);
+        const dv = DocumentView.getDocumentView(doc, cv);
+        if (dv && (!containingDoc || dv.containerViewPath?.().lastElement()?.Document === containingDoc)) {
+            DocumentView.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.Document));
+        } else {
+            const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc));
+            const showDoc = !Doc.IsSystem(container) && !cv ? container : doc;
+            options.toggleTarget = undefined;
+            DocumentView.showDocument(showDoc, options, () => DocumentView.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
+                const cvFound = DocumentView.getDocumentView(containingDoc);
+                const dvFound = DocumentView.getDocumentView(doc, cvFound);
+                dvFound && Doc.linkFollowHighlight(dvFound.Document);
+            });
+        }
+    };
+    if (Doc.IsDataProto(doc) && Doc.GetEmbeddings(doc).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) {
+        doc = Doc.GetEmbeddings(doc).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!;
+    }
+    if (doc.hidden) {
+        doc.hidden = false;
+        options.toggleTarget = false;
+        setTimeout(func);
+    } else func();
+}
+ScriptingGlobals.add(DocFocusOrOpen);
+
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
     documentView.iconify();
@@ -1527,9 +1516,9 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
     const collectedLinks = DocListCast(linkCollection[DocData].data);
     let wid = NumCast(linkSource._width);
     let embedding: Doc | undefined;
-    const links = LinkManager.Links(linkSource);
+    const links = Doc.Links(linkSource);
     links.forEach(link => {
-        const other = LinkManager.getOppositeAnchor(link, linkSource);
+        const other = Doc.getOppositeAnchor(link, linkSource);
         const otherdoc = DocCast(other?.annotationOn ?? other);
         if (otherdoc && !collectedLinks?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
             embedding = Doc.MakeEmbedding(otherdoc);
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 8d1617e66..5e3bb9fec 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -17,7 +17,7 @@ import { ContextMenu } from '../../ContextMenu';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { SelectedDocView } from '../../selectedDoc';
-import { StyleProp } from '../../StyleProvider';
+import { StyleProp } from '../../StyleProp';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { OpenWhere } from '../OpenWhere';
 import './FontIconBox.scss';
@@ -116,7 +116,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
             default:                               type = 'slider';
                 break;
         } // prettier-ignore
-        const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, self: this.Document, value, _readOnly_: value === undefined });
+        const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, value, _readOnly_: value === undefined });
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
         // Script for checking the outcome of the toggle
         const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
@@ -142,7 +142,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         setupMoveUpEvents(
             this,
             e,
-            () => ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, self: this.Document, value: { doc: value, e } }).result,
+            () => ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, value: { doc: value, e } }).result,
             emptyFunction,
             emptyFunction
         ); // prettier-ignore
@@ -162,7 +162,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         const isViewDropdown = script?.script.originalScript.startsWith('{ return setView');
         if (isViewDropdown) {
             const selected = Array.from(script?.script.run({ _readOnly_: true }).result) as Doc[];
-            // const selected = SelectionManager.Docs;
+            // const selected = DocumentView.SelectedDocs();
             if (selected.lastElement()) {
                 if (StrCast(selected.lastElement().type) === DocumentType.COL) {
                     text = StrCast(selected.lastElement()._type_collection);
@@ -190,7 +190,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
             }
             noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Carousel3D, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
         } else {
-            text = script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result;
+            text = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result;
             // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
             getStyle = (val: string) => ({ fontFamily: val });
         }
@@ -208,7 +208,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         return (
             <Dropdown
                 selectedVal={text}
-                setSelectedVal={undoable(value => script.script.run({ this: this.Document, self: this.Document, value }), `dropdown select ${this.label}`)}
+                setSelectedVal={undoable(value => script.script.run({ this: this.Document, value }), `dropdown select ${this.label}`)}
                 color={SnappingManager.userColor}
                 background={SnappingManager.userVariantColor}
                 type={Type.TERT}
@@ -232,17 +232,17 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
      */
     @computed get colorButton() {
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
-        const curColor = this.colorScript?.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result ?? 'transparent';
+        const curColor = this.colorScript?.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result ?? 'transparent';
         const tooltip: string = StrCast(this.Document.toolTip);
 
         return (
             <ColorPicker
                 setSelectedColor={value => {
                     if (!this.colorBatch) this.colorBatch = UndoManager.StartBatch(`Set ${tooltip} color`);
-                    this.colorScript?.script.run({ this: this.Document, self: this.Document, value: value, _readOnly_: false });
+                    this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false });
                 }}
                 setFinalColor={value => {
-                    this.colorScript?.script.run({ this: this.Document, self: this.Document, value: value, _readOnly_: false });
+                    this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false });
                     this.colorBatch?.end();
                     this.colorBatch = undefined;
                 }}
@@ -262,7 +262,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         const tooltip: string = StrCast(this.Document.toolTip);
 
         // const script = ScriptCast(this.Document.onClick);
-        // const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
+        // const toggleStatus = script ? script.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result : false;
         // Colors
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
         const items = DocListCast(this.dataDoc.data);
@@ -278,10 +278,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                     tooltip: StrCast(item.toolTip),
                     val: StrCast(item.toolType),
                 }))}
-                selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType)}
+                selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType)}
                 setSelectedVal={(val: string | number) => {
                     const itemDoc = items.find(item => item.toolType === val);
-                    itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: val, _readOnly_: false });
+                    itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: val, _readOnly_: false });
                 }}
             />
         );
@@ -296,7 +296,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
 
         const script = ScriptCast(this.Document.onClick);
         const double = ScriptCast(this.Document.onDoubleClick);
-        const toggleStatus = script?.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result ?? false;
+        const toggleStatus = script?.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result ?? false;
         // Colors
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
         // const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
@@ -319,8 +319,8 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                         returnTrue,
                         emptyFunction,
                         action((clickEv, doubleTap) => {
-                            (!doubleTap || !double) && script?.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
-                            doubleTap && double?.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
+                            (!doubleTap || !double) && script?.script.run({ this: this.Document, value: !toggleStatus, _readOnly_: false });
+                            doubleTap && double?.script.run({ this: this.Document, value: !toggleStatus, _readOnly_: false });
                             this._hackToRecompute += 1;
                         })
                     )
@@ -341,15 +341,15 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
 
     @computed get editableText() {
         const script = ScriptCast(this.Document.script);
-        const checkResult = script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result;
+        const checkResult = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result;
 
-        const setValue = (value: string): boolean => script?.script.run({ this: this.Document, self: this.Document, value, _readOnly_: false }).result;
+        const setValue = (value: string): boolean => script?.script.run({ this: this.Document, value, _readOnly_: false }).result;
 
         return (
             <div className="menuButton editableText">
                 <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon="lock" />
                 <div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}>
-                    <EditableView GetValue={() => script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine contents={checkResult} />
+                    <EditableView GetValue={() => script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine contents={checkResult} />
                 </div>
             </div>
         );
@@ -358,7 +358,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
     renderButton = () => {
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
         const tooltip = StrCast(this.Document.toolTip);
-        const scriptFunc = () => ScriptCast(this.Document.onClick)?.script.run({ this: this.Document, self: this.Document, _readOnly_: false });
+        const scriptFunc = () => ScriptCast(this.Document.onClick)?.script.run({ this: this.Document, _readOnly_: false });
         const btnProps = { tooltip, icon: this.Icon(color)!, label: this.label };
         // prettier-ignore
         switch (this.type) {
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index f32d39aaf..3d1bd7563 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -2,7 +2,7 @@ import functionPlot from 'function-plot';
 import { computed, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { DocListCast } from '../../../fields/Doc';
+import { Doc, DocListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { Cast, StrCast } from '../../../fields/Types';
@@ -11,7 +11,6 @@ import { DocUtils } from '../../documents/DocUtils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
-import { LinkManager } from '../../util/LinkManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { PinDocView, PinProps } from '../PinFuncs';
@@ -49,8 +48,8 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
         return anchor;
     };
     @computed get graphFuncs() {
-        const links = LinkManager.Instance.getAllRelatedLinks(this.Document)
-            .map(d => LinkManager.getOppositeAnchor(d, this.Document))
+        const links = Doc.Links(this.Document)
+            .map(d => Doc.getOppositeAnchor(d, this.Document))
             .filter(d => d)
             .map(d => d!);
         const funcs = links.concat(DocListCast(this.dataDoc[this.fieldKey])).map(doc =>
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d317f46bb..3945da104 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -19,7 +19,6 @@ import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocUtils } from '../../documents/DocUtils';
 import { Networking } from '../../Network';
-import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
@@ -30,7 +29,8 @@ import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { OverlayView } from '../OverlayView';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PinDocView, PinProps } from '../PinFuncs';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
+import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FocusViewOptions } from './FocusViewOptions';
 import './ImageBox.scss';
@@ -254,7 +254,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             cropping.y = NumCast(this.Document.y);
             this._props.addDocTab(cropping, OpenWhere.inParent);
         }
-        DocumentManager.Instance.AddViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200));
+        DocumentView.addViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200));
         this._props.bringToFront?.(cropping);
         return cropping;
     };
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 46bb16e50..cd591fa42 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -59,6 +59,8 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
     get fieldDocToLayout() {
         return DocCast(this._props.Document);
     }
+    isUnstyledView = returnTrue; // used by style provider via ViewBoxInterface
+    dontRegisterView = returnTrue; // used by ViewBoxInterface
 
     @action
     onEnterKey = (e: React.KeyboardEvent): void => {
@@ -111,7 +113,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
                     if (setResult) setResult?.(value);
                     else target[key] = field;
                 };
-                const res = script.run({ this: Doc.Layout(doc), self: doc, _setCacheResult_ }, console.log);
+                const res = script.run({ this: Doc.Layout(doc), _setCacheResult_ }, console.log);
                 if (!res.success) {
                     if (key) target[key] = script.originalScript;
                     return false;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 4dec3506c..f80ff5f94 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -13,7 +13,7 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { PinDocView, PinProps } from '../PinFuncs';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
 import { FieldView, FieldViewProps } from './FieldView';
 import BigText from './LabelBigText';
 import './LabelBox.scss';
diff --git a/src/client/views/nodes/LinkAnchorBox.scss b/src/client/views/nodes/LinkAnchorBox.scss
deleted file mode 100644
index caff369df..000000000
--- a/src/client/views/nodes/LinkAnchorBox.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-.linkAnchorBox-cont,
-.linkAnchorBox-cont-small {
-    cursor: default;
-    position: absolute;
-    width: 15;
-    height: 15;
-    border-radius: 20px;
-    user-select: none;
-    pointer-events: all;
-
-    .linkAnchorBox-linkCloser {
-        position: absolute;
-        width: 18;
-        height: 18;
-        background: rgb(219, 21, 21);
-        top: -1px;
-        left: -1px;
-        border-radius: 5px;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        padding-left: 2px;
-        padding-top: 1px;
-    }
-    .linkAnchorBox-button {
-        position: relative;
-        display: inline-block;
-    }
-}
-
-.linkAnchorBox-cont-small {
-    width: 5px;
-    height: 5px;
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
deleted file mode 100644
index d43241de0..000000000
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import { action, computed, makeObservable } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-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 } from '../../util/DragManager';
-import { dropActionType } from '../../util/DropActionTypes';
-import { LinkFollower } from '../../util/LinkFollower';
-import { SelectionManager } from '../../util/SelectionManager';
-import { ViewBoxBaseComponent } from '../DocComponent';
-import { StyleProp } from '../StyleProvider';
-import { FieldView, FieldViewProps } from './FieldView';
-import './LinkAnchorBox.scss';
-import { LinkInfo } from './LinkDocPreview';
-
-const { MEDIUM_GRAY } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
-
-@observer
-export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
-    public static LayoutString(fieldKey: string) {
-        return FieldView.LayoutString(LinkAnchorBox, fieldKey);
-    }
-    _doubleTap = false;
-    _lastTap: number = 0;
-    _ref = React.createRef<HTMLDivElement>();
-    _isOpen = false;
-    _timeout: NodeJS.Timeout | undefined;
-
-    constructor(props: FieldViewProps) {
-        super(props);
-        makeObservable(this);
-    }
-
-    componentDidMount() {
-        this._props.setContentViewBox?.(this);
-    }
-
-    @computed get linkSource() {
-        return this.DocumentView?.().containerViewPath?.().lastElement().Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource);
-    }
-
-    onPointerDown = (e: React.PointerEvent) => {
-        const { linkSource } = this;
-        linkSource &&
-            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (clickEv, doubleTap) => {
-                if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false);
-                else this._props.select(false);
-            });
-    };
-    onPointerMove = action((e: PointerEvent) => {
-        const cdiv = this._ref?.current?.parentElement;
-        if (!this._isOpen && cdiv) {
-            const bounds = cdiv.getBoundingClientRect();
-            const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
-            const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
-            if (separation > 100) {
-                const dragData = new DragManager.DocumentDragData([this.Document]);
-                dragData.dropAction = dropActionType.embed;
-                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;
-            }
-            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;
-    });
-
-    specificContextMenu = (): void => {};
-
-    render() {
-        TraceMobx();
-        const small = this._props.PanelWidth() <= 1; // this happens when rendered in a treeView
-        const x = NumCast(this.layoutDoc[this.fieldKey + '_x'], 100);
-        const y = NumCast(this.layoutDoc[this.fieldKey + '_y'], 100);
-        const background = this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.BackgroundColor + ':anchor');
-        const anchor = this.fieldKey === 'link_anchor_1' ? 'link_anchor_2' : 'link_anchor_1';
-        const anchorScale = !this.dataDoc[this.fieldKey + '_useSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25;
-        const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title);
-        const selView = SelectionManager.Views.lastElement()?._props.LayoutTemplateString?.includes('link_anchor_1')
-            ? 'link_anchor_1'
-            : SelectionManager.Views.lastElement()?._props.LayoutTemplateString?.includes('link_anchor_2')
-              ? 'link_anchor_2'
-              : '';
-        return (
-            <div
-                ref={this._ref}
-                title={targetTitle}
-                className={`linkAnchorBox-cont${small ? '-small' : ''}`}
-                onPointerEnter={e =>
-                    LinkInfo.SetLinkInfo({
-                        DocumentView: this.DocumentView,
-                        styleProvider: this._props.styleProvider,
-                        linkSrc: this.linkSource,
-                        linkDoc: this.Document,
-                        showHeader: true,
-                        location: [e.clientX, e.clientY + 20],
-                        noPreview: false,
-                    })
-                }
-                onPointerDown={this.onPointerDown}
-                onContextMenu={this.specificContextMenu}
-                style={{
-                    border: selView && this.dataDoc[selView] === this.dataDoc[this.fieldKey] ? `solid ${MEDIUM_GRAY} 2px` : undefined,
-                    background,
-                    left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`,
-                    top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`,
-                    transform: `scale(${anchorScale})`,
-                    cursor: 'grab',
-                }}
-            />
-        );
-    }
-}
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 559b1fcae..6caa38a7f 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -13,12 +13,11 @@ import { TraceMobx } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
-import { DocumentManager } from '../../util/DocumentManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { LightboxView } from '../LightboxView';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
 import { ComparisonBox } from './ComparisonBox';
 import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
@@ -43,7 +42,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     anchor = (which: number) => {
         const anch = DocCast(this.dataDoc['link_anchor_' + which]);
         const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
-        return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
+        return DocumentView.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
     };
     _hackToSeeIfDeleted: any;
     componentWillUnmount() {
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 23cb25962..ff95f8547 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -2,16 +2,17 @@ import { action, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DocData } from '../../../fields/DocSymbols';
+import { StrCast } from '../../../fields/Types';
 import { LinkManager } from '../../util/LinkManager';
 import './LinkDescriptionPopup.scss';
 import { TaskCompletionBox } from './TaskCompletedBox';
-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;
+    // eslint-disable-next-line react/no-unused-class-component-methods
     @observable public showDescriptions: string = 'ON';
     @observable public popupX: number = 700;
     @observable public popupY: number = 350;
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 0936acc15..508008569 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -11,12 +11,10 @@ import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/T
 import { DocServer } from '../../DocServer';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
-import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
-import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { SearchUtil } from '../../util/SearchUtil';
-import { SettingsManager } from '../../util/SettingsManager';
+import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DocumentView } from './DocumentView';
@@ -134,17 +132,17 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                 const anchorDoc = anchorDocId ? PromiseValue(DocCast(DocServer.GetCachedRefField(anchorDocId) ?? DocServer.GetRefField(anchorDocId))) : undefined;
                 anchorDoc?.then?.(
                     action(anchor => {
-                        if (anchor instanceof Doc && LinkManager.Links(anchor).length) {
-                            this._linkDoc = this._linkDoc ?? LinkManager.Links(anchor)[0];
+                        if (anchor instanceof Doc && Doc.Links(anchor).length) {
+                            this._linkDoc = this._linkDoc ?? Doc.Links(anchor)[0];
                             const automaticLink = this._linkDoc.link_relationship === LinkManager.AutoKeywords;
                             if (automaticLink) {
                                 // automatic links specify the target in the link info, not the source
                                 const linkTarget = anchor;
-                                this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget);
+                                this._linkSrc = Doc.getOppositeAnchor(this._linkDoc, linkTarget);
                                 this._markerTargetDoc = this._targetDoc = linkTarget;
                             } else {
                                 this._linkSrc = anchor;
-                                const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+                                const linkTarget = Doc.getOppositeAnchor(this._linkDoc, this._linkSrc);
                                 this._markerTargetDoc = linkTarget;
                                 this._targetDoc = /* linkTarget?.type === DocumentType.MARKER && */ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
                             }
@@ -169,8 +167,8 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                 LinkManager.Instance.currentLink = this._linkDoc;
                 LinkManager.Instance.currentLinkAnchor = this._linkSrc;
                 this._props.DocumentView?.().select(false);
-                if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
-                    SettingsManager.Instance.propertiesWidth = 250;
+                if ((SnappingManager.PropertiesWidth ?? 0) < 100) {
+                    SnappingManager.SetPropertiesWidth(250);
                 }
             })
         );
@@ -196,13 +194,13 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
     followLink = () => {
         LinkInfo.Clear();
         if (this._linkDoc && this._linkSrc) {
-            LinkFollower.FollowLink(this._linkDoc, this._linkSrc, false);
+            DocumentView.FollowLink(this._linkDoc, this._linkSrc, false);
         } else if (this._props.hrefs?.length) {
             const webDoc =
                 Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0], false).keys())
                     .filter(doc => doc.type === DocumentType.WEB)
                     .lastElement() ?? Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
-            DocumentManager.Instance.showDocument(webDoc, {
+            DocumentView.showDocument(webDoc, {
                 openLocation: OpenWhere.lightbox,
                 willPan: true,
                 zoomTime: 500,
@@ -229,7 +227,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
     };
     @computed get previewHeader() {
         return !this._linkDoc || !this._markerTargetDoc || !this._targetDoc || !this._linkSrc ? null : (
-            <div className="linkDocPreview-info" style={{ background: SettingsManager.userBackgroundColor }}>
+            <div className="linkDocPreview-info" style={{ background: SnappingManager.userBackgroundColor }}>
                 <div className="linkDocPreview-buttonBar" style={{ float: 'left' }}>
                     <Tooltip title={<div className="dash-tooltip">Edit Link</div>} placement="top">
                         <div className="linkDocPreview-button" onPointerDown={this.editLink}>
@@ -282,7 +280,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                     ) : (
                         <DocumentView
                             ref={r => {
-                                const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+                                const targetanchor = this._linkDoc && this._linkSrc && Doc.getOppositeAnchor(this._linkDoc, this._linkSrc);
                                 targetanchor && this._targetDoc !== targetanchor && r?._props.focus?.(targetanchor, {});
                             }}
                             Document={this._targetDoc!}
@@ -325,7 +323,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                 className="linkDocPreview"
                 ref={this._linkDocRef}
                 onPointerDown={this.followLinkPointerDown}
-                style={{ borderColor: SettingsManager.userColor, left: this._props.location[0], top: this._props.location[1], width: this.width() + borders, height: this.height() + borders + (this._props.showHeader ? 37 : 0) }}>
+                style={{ borderColor: SnappingManager.userColor, left: this._props.location[0], top: this._props.location[1], width: this.width() + borders, height: this.height() + borders + (this._props.showHeader ? 37 : 0) }}>
                 {this.docPreview}
             </div>
         );
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index aa89398f3..5f343bdfe 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -6,12 +6,11 @@ import { Doc } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { StrCast } from '../../../fields/Types';
 import { Networking } from '../../Network';
-import { DocumentManager } from '../../util/DocumentManager';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LoadingBox.scss';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { Docs } from '../../documents/Documents';
 
 /**
  * LoadingBox Class represents a placeholder doc for documents that are currently
@@ -39,25 +38,11 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LoadingBox, fieldKey);
     }
-    // removes from currently loading display
-    public static removeCurrentlyLoading(doc: Doc) {
-        if (DocumentManager.Instance.CurrentlyLoading) {
-            const index = DocumentManager.Instance.CurrentlyLoading.indexOf(doc);
-            runInAction(() => index !== -1 && DocumentManager.Instance.CurrentlyLoading.splice(index, 1));
-        }
-    }
-
-    // adds doc to currently loading display
-    public static addCurrentlyLoading(doc: Doc) {
-        if (DocumentManager.Instance.CurrentlyLoading.indexOf(doc) === -1) {
-            runInAction(() => DocumentManager.Instance.CurrentlyLoading.push(doc));
-        }
-    }
 
     _timer: any;
     @observable progress = '';
     componentDidMount() {
-        if (!DocumentManager.Instance.CurrentlyLoading?.includes(this.Document)) {
+        if (!Doc.CurrentlyLoading?.includes(this.Document)) {
             this.Document.loadingError = 'Upload interrupted, please try again';
         } else {
             const updateFunc = async () => {
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
index f176317d2..b8fd8ac6a 100644
--- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -8,9 +8,9 @@ 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';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { DocumentView } from '../DocumentView';
 
 @observer
 export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -56,7 +56,7 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     componentDidMount() {
         this._disposer = reaction(
-            () => SelectionManager.Views.slice(),
+            () => DocumentView.Selected().slice(),
             () => DirectionsAnchorMenu.Instance.fadeOut(true)
         );
     }
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index 174511e1a..103a35434 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -13,9 +13,9 @@ import { unimplementedFunction } from '../../../../Utils';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { CalendarManager } from '../../../util/CalendarManager';
-import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { DocumentView } from '../DocumentView';
 import './MapAnchorMenu.scss';
 import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
 import { MarkerIcons } from './MarkerIcons';
@@ -126,7 +126,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     componentDidMount() {
         this._disposer = reaction(
-            () => SelectionManager.Views.slice(),
+            () => DocumentView.Selected().slice(),
             () => MapAnchorMenu.Instance.fadeOut(true)
         );
     }
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 50831f8ea..ac8010f11 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -21,9 +21,7 @@ import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types';
 import { DocUtils } from '../../../documents/DocUtils';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Docs } from '../../../documents/Documents';
-import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
-import { LinkManager } from '../../../util/LinkManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
 import { PinDocView, PinProps } from '../../PinFuncs';
@@ -249,7 +247,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             if (existingPin) {
                 setTimeout(() => {
                     // we use a timeout in case this is called from the sidebar which may have just added a link that hasn't made its way into th elink manager yet
-                    if (!LinkManager.Instance.getAllRelatedLinks(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) {
+                    if (!Doc.Links(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) {
                         const anchor = this.getAnchor(true, undefined, existingPin);
                         anchor && DocUtils.MakeLink(anchor, doc, { link_relationship: 'link to map location' });
                         doc.latitude = existingPin?.latitude;
@@ -438,7 +436,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             options.didMove = true;
         }
         return new Promise<Opt<DocumentView>>(res => {
-            DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+            DocumentView.addViewRenderedCb(doc, dv => res(dv));
         });
     };
 
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 9825824bd..7697fd295 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -509,9 +509,9 @@
 
 //     // TODO: auto center on select a document in the sidebar
 //     private handleMapCenter = (map: google.maps.Map) => {
-//         // console.log("print the selected views in selectionManager:")
-//         // if (SelectionManager.Views.lastElement()) {
-//         //     console.log(SelectionManager.Views.lastElement());
+//         // console.log("print the selected views in Document.Selected:")
+//         // if (DocumentView.Selected().lastElement()) {
+//         //     console.log(DocumentView.Selected().lastElement());
 //         // }
 //     };
 
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 3b4ffd4bd..bfd40692b 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -13,9 +13,7 @@ import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types';
 import { DocUtils } from '../../../documents/DocUtils';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Docs } from '../../../documents/Documents';
-import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
-import { LinkManager } from '../../../util/LinkManager';
 import { Transform } from '../../../util/Transform';
 import { UndoManager, undoable } from '../../../util/UndoManager';
 import { ViewBoxAnnotatableComponent } from '../../DocComponent';
@@ -130,7 +128,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
             if (existingPin) {
                 setTimeout(() => {
                     // we use a timeout in case this is called from the sidebar which may have just added a link that hasn't made its way into th elink manager yet
-                    if (!LinkManager.Instance.getAllRelatedLinks(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) {
+                    if (!Doc.Links(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) {
                         const anchor = this.getAnchor(true, undefined, existingPin);
                         anchor && DocUtils.MakeLink(anchor, doc, { link_relationship: 'link to map location' });
                         doc.latitude = existingPin?.latitude;
@@ -390,7 +388,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
             options.didMove = true;
         }
         return new Promise<Opt<DocumentView>>(res => {
-            DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+            DocumentView.addViewRenderedCb(doc, dv => res(dv));
         });
     };
     /*
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 5d1874aca..9038ed27a 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -19,9 +19,7 @@ import { emptyFunction } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocUtils } from '../../documents/DocUtils';
-import { DocumentManager } from '../../util/DocumentManager';
 import { KeyCodes } from '../../util/KeyCodes';
-import { SelectionManager } from '../../util/SelectionManager';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { CollectionStackingView } from '../collections/CollectionStackingView';
@@ -251,7 +249,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             this.toggleSidebar(false);
         }
         return new Promise<Opt<DocumentView>>(res => {
-            DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+            DocumentView.addViewRenderedCb(doc, dv => res(dv));
         });
     };
 
@@ -561,7 +559,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     removeDocument={this.removeDocument}
                 />
             ) : (
-                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
+                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.()!, false), true)}>
                     <ComponentTag
                         // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index ae6caf9f4..e46e40bfe 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -3,6 +3,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast } from '../../../../fields/Doc';
+import { DocData } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { BoolCast, DocCast } from '../../../../fields/Types';
@@ -10,19 +11,18 @@ import { VideoField } from '../../../../fields/URLField';
 import { Upload } from '../../../../server/SharedMediaTypes';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Docs } from '../../../documents/Documents';
-import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
 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 { mediaState } from '../AudioBox';
+import { DocumentView } from '../DocumentView';
 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>() {
@@ -102,7 +102,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
         screengrabber.overlayY = 590; // was 0
         screengrabber[DocData][Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
         Doc.AddToMyOverlay(screengrabber); // just adds doc to overlay
-        DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
+        DocumentView.addViewRenderedCb(screengrabber, docView => {
             RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
             RecordingBox.screengrabber.Record?.();
         });
@@ -119,7 +119,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
         value.overlayX = 70;
         value.overlayY = window.innerHeight - 180;
         Doc.AddToMyOverlay(value);
-        DocumentManager.Instance.AddViewRenderedCb(value, docView => {
+        DocumentView.addViewRenderedCb(value, docView => {
             Doc.UserDoc().currentRecording = docView.Document;
             docView.select(false);
             RecordingBox.resumeWorkspaceReplaying(value);
@@ -132,7 +132,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     @undoBatch
     public static addRecToWorkspace(value: RecordingBox) {
-        const ffView = Array.from(DocumentManager.Instance.DocumentViews).find(view => view.ComponentView instanceof CollectionFreeFormView);
+        const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView);
         (ffView?.ComponentView as CollectionFreeFormView)._props.addDocument?.(value.Document);
         Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value.Document);
         Doc.RemFromMyOverlay(value.Document);
@@ -142,7 +142,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     public static resumeWorkspaceReplaying(doc: Doc) {
-        const docView = DocumentManager.Instance.getDocumentView(doc);
+        const docView = DocumentView.getDocumentView(doc);
         if (docView?.ComponentView instanceof VideoBox) {
             docView.ComponentView.Play();
         }
@@ -150,7 +150,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     public static pauseWorkspaceReplaying(doc: Doc) {
-        const docView = DocumentManager.Instance.getDocumentView(doc);
+        const docView = DocumentView.getDocumentView(doc);
         const videoBox = docView?.ComponentView as VideoBox;
         if (videoBox) {
             videoBox.Pause();
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 8eb55b2f8..3be50f5e6 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -24,6 +24,7 @@ import { SettingsManager } from '../../util/SettingsManager';
 import { TrackMovements } from '../../util/TrackMovements';
 import { ContextMenu } from '../ContextMenu';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { DocViewUtils } from '../DocViewUtils';
 import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { mediaState } from './AudioBox';
@@ -161,8 +162,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         //     });
     }
     componentWillUnmount() {
-        const ind = DocUtils.ActiveRecordings.indexOf(this);
-        ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
+        const ind = DocViewUtils.ActiveRecordings.indexOf(this);
+        ind !== -1 && DocViewUtils.ActiveRecordings.splice(ind, 1);
     }
 
     specificContextMenu = (): void => {
@@ -265,7 +266,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 this._screenCapture = true;
                 this.dataDoc.mediaState = 'recording';
             });
-            DocUtils.ActiveRecordings.push(this);
+            DocViewUtils.ActiveRecordings.push(this);
         } else {
             this._audioRec?.stop();
             this._videoRec?.stop();
@@ -273,8 +274,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 this._screenCapture = false;
                 this.dataDoc.mediaState = 'paused';
             });
-            const ind = DocUtils.ActiveRecordings.indexOf(this);
-            ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
+            const ind = DocViewUtils.ActiveRecordings.indexOf(this);
+            ind !== -1 && DocViewUtils.ActiveRecordings.splice(ind, 1);
 
             CaptureManager.Instance.open(this.Document);
         }
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index f32d00386..afd73cfe8 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -16,9 +16,7 @@ import { emptyFunction, formatTime } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocUtils, FollowLinkScript } from '../../documents/DocUtils';
-import { DocumentManager } from '../../util/DocumentManager';
 import { dropActionType } from '../../util/DropActionTypes';
-import { LinkManager } from '../../util/LinkManager';
 import { ReplayMovements } from '../../util/ReplayMovements';
 import { undoBatch } from '../../util/UndoManager';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
@@ -26,16 +24,16 @@ import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionS
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
+import { VideoThumbnails } from '../global/globalEnums';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PinDocView, PinProps } from '../PinFuncs';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
 import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FocusViewOptions } from './FocusViewOptions';
 import { RecordingBox } from './RecordingBox';
 import './VideoBox.scss';
-import { VideoThumbnails } from '../global/globalEnums';
 
 /**
  * VideoBox
@@ -88,7 +86,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     @observable _scrubbing: boolean = false;
 
     @computed get links() {
-        return LinkManager.Links(this.dataDoc);
+        return Doc.Links(this.dataDoc);
     }
     @computed get heightPercent() {
         return NumCast(this.layoutDoc._layout_timelineHeightPercent, 100);
@@ -347,7 +345,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         this._props.addDocument?.(imageSnapshot);
         DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
         // link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);  // do we need to set an end time? should default to +0.1
-        setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
+        setTimeout(() => downX !== undefined && downY !== undefined && DocumentView.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
     };
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
@@ -392,7 +390,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             return this._stackedTimeline.getView(doc, options);
         }
         return new Promise<Opt<DocumentView>>(res => {
-            DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+            DocumentView.addViewRenderedCb(doc, dv => res(dv));
         });
     };
 
@@ -446,7 +444,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                 { fireImmediately: true }
             );
 
-            (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length !== VideoBox.numThumbnails) && this.getVideoThumbnails();
+            (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length !== VideoThumbnails.DENSE) && this.getVideoThumbnails();
         }
     };
 
@@ -652,20 +650,20 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     @action
     removeCurrentlyPlaying = () => {
         const docView = this.DocumentView?.();
-        if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
-            const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
-            index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
+        if (DocumentView.CurrentlyPlaying && docView) {
+            const index = DocumentView.CurrentlyPlaying.indexOf(docView);
+            index !== -1 && DocumentView.CurrentlyPlaying.splice(index, 1);
         }
     };
     // adds doc to currently playing display
     @action
     addCurrentlyPlaying = () => {
         const docView = this.DocumentView?.();
-        if (!CollectionStackedTimeline.CurrentlyPlaying) {
-            CollectionStackedTimeline.CurrentlyPlaying = [];
+        if (!DocumentView.CurrentlyPlaying) {
+            DocumentView.CurrentlyPlaying = [];
         }
-        if (docView && CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView) === -1) {
-            CollectionStackedTimeline.CurrentlyPlaying.push(docView);
+        if (docView && DocumentView.CurrentlyPlaying.indexOf(docView) === -1) {
+            DocumentView.CurrentlyPlaying.push(docView);
         }
     };
 
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 54f246b20..1003bc473 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -21,7 +21,6 @@ import { emptyFunction, stringHash } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocUtils } from '../../documents/DocUtils';
-import { DocumentManager } from '../../util/DocumentManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SnappingManager } from '../../util/SnappingManager';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
@@ -38,7 +37,7 @@ import { Annotation } from '../pdf/Annotation';
 import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
 import { PinDocView, PinProps } from '../PinFuncs';
 import { SidebarAnnos } from '../SidebarAnnos';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
 import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FocusViewOptions } from './FocusViewOptions';
@@ -324,7 +323,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href);
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false);
         return new Promise<Opt<DocumentView>>(res => {
-            DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+            DocumentView.addViewRenderedCb(doc, dv => res(dv));
         });
     };
 
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 1c5ea2dd4..5d53a2a5f 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -293,7 +293,6 @@ export class DashFieldView {
     unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
     constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
         makeObservable(this);
-        const self = this;
         this.node = node;
         this.tbox = tbox;
         this.getpos = getPos;
@@ -305,14 +304,14 @@ export class DashFieldView {
         this.dom.onkeypress = function (e: KeyboardEvent) {
             e.stopPropagation();
         };
-        this.dom.onkeydown = function (e: KeyboardEvent) {
+        this.dom.onkeydown = (e: KeyboardEvent) => {
             e.stopPropagation();
             if (e.key === 'Tab') {
                 e.preventDefault();
                 const editor = tbox.EditorView;
                 if (editor) {
                     const { state } = editor;
-                    for (let i = self.getpos() + 1; i < state.doc.content.size; i++) {
+                    for (let i = this.getpos() + 1; i < state.doc.content.size; i++) {
                         if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) {
                             editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i))));
                             return;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ad6629fc9..8a4a7718a 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -32,16 +32,13 @@ import { Docs } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DocUtils } from '../../../documents/DocUtils';
 import { DictationManager } from '../../../util/DictationManager';
-import { DocumentManager } from '../../../util/DocumentManager';
 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';
-import { SelectionManager } from '../../../util/SelectionManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionStackingView } from '../../collections/CollectionStackingView';
 import { CollectionTreeView } from '../../collections/CollectionTreeView';
 import { ContextMenu } from '../../ContextMenu';
@@ -53,9 +50,10 @@ import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
 import { PinDocView, PinProps } from '../../PinFuncs';
 import { SidebarAnnos } from '../../SidebarAnnos';
-import { styleFromLayoutString, StyleProp } from '../../StyleProvider';
+import { StyleProp } from '../../StyleProp';
+import { styleFromLayoutString } from '../../StyleProvider';
 import { mediaState } from '../AudioBox';
-import { DocumentView, DocumentViewInternal } from '../DocumentView';
+import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { FocusViewOptions } from '../FocusViewOptions';
 import { LinkInfo } from '../LinkDocPreview';
@@ -260,7 +258,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     let stopFunc: any;
                     const targetData = target[DocData];
                     targetData.mediaState = mediaState.Recording;
-                    DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
+                    DictationManager.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
 
                     const reactionDisposer = reaction(
                         () => target.mediaState,
@@ -271,7 +269,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                             }
                         }
                     );
-                    target.title = ComputedField.MakeFunction(`self["text_audioAnnotations_text"].lastElement()`);
+                    target.title = ComputedField.MakeFunction(`this.text_audioAnnotations_text.lastElement()`);
                 }
             });
         };
@@ -400,7 +398,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     insertTime = () => {
         let linkTime;
         let linkAnchor;
-        LinkManager.Links(this.dataDoc).forEach(l => {
+        Doc.Links(this.dataDoc).forEach(l => {
             const anchor = DocCast(l.link_anchor_1)?.annotationOn ? DocCast(l.link_anchor_1) : DocCast(l.link_anchor_2)?.annotationOn ? DocCast(l.link_anchor_2) : undefined;
             if (anchor && (anchor.annotationOn as Doc).mediaState === mediaState.Recording) {
                 linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
@@ -423,7 +421,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     autoLink = () => {
         const newAutoLinks = new Set<Doc>();
-        const oldAutoLinks = LinkManager.Links(this.Document).filter(
+        const oldAutoLinks = Doc.Links(this.Document).filter(
             link => 
                 ((!Doc.isTemplateForField(this.Document) &&
                     (!Doc.isTemplateForField(DocCast(link.link_anchor_1)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
@@ -442,7 +440,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             tr = tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)));
             this._editorView?.dispatch(tr);
         }
-        oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink);
+        oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(doc => Doc.DeleteLink?.(doc));
     };
 
     updateTitle = () => {
@@ -496,7 +494,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                         if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
                             alink =
                                 alink ??
-                                (LinkManager.Links(this.Document).find(
+                                (Doc.Links(this.Document).find(
                                     link =>
                                         Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.Document) && //
                                         Doc.AreProtosEqual(Cast(link.link_anchor_2, Doc, null), target)
@@ -787,7 +785,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     deleteAnnotation = (anchor: Doc) => {
         const batch = UndoManager.StartBatch('delete link');
-        LinkManager.Instance.deleteLink(LinkManager.Links(anchor)[0]);
+        Doc.DeleteLink?.(Doc.Links(anchor)[0]);
         // const docAnnotations = DocListCast(this._props.dataDoc[this.fieldKey]);
         // this._props.dataDoc[this.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
         // AnchorMenu.Instance.fadeOut(true);
@@ -837,7 +835,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             anchorDoc &&
                 DocServer.GetRefField(anchorDoc).then(
                     action(anchor => {
-                        anchor && SelectionManager.SelectSchemaViewDoc(anchor as Doc);
+                        anchor && DocumentView.SelectSchemaDoc(anchor as Doc);
                         AnchorMenu.Instance.Status = 'annotation';
                         AnchorMenu.Instance.Delete = !anchor && editor.state.selection.empty ? returnFalse : !anchor ? deleteMarkups : () => this.deleteAnnotation(anchor as Doc);
                         AnchorMenu.Instance.Pinned = false;
@@ -1124,7 +1122,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc));
         }
         return new Promise<Opt<DocumentView>>(res => {
-            DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+            DocumentView.addViewRenderedCb(doc, dv => res(dv));
         });
     };
     focus = (textAnchor: Doc, options: FocusViewOptions) => {
@@ -1206,7 +1204,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     }
     componentDidMount() {
         !this._props.dontSelectOnLoad && this._props.setContentViewBox?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
-        this._cachedLinks = LinkManager.Links(this.Document);
+        this._cachedLinks = Doc.Links(this.Document);
         this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
         this._disposers.layout_autoHeight = reaction(
             () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss] }),
@@ -1244,7 +1242,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             { fireImmediately: !Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') }
         );
         this._disposers.links = reaction(
-            () => LinkManager.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
+            () => Doc.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
             newLinks => {
                 this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
                 this._cachedLinks = newLinks;
@@ -1556,7 +1554,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     // const timecode = NumCast(anchor.timecodeToShow, 0);
                     const audiodoc = anchor.annotationOn as Doc;
                     const func = () => {
-                        const docView = DocumentManager.Instance.getDocumentView(audiodoc);
+                        const docView = DocumentView.getDocumentView(audiodoc);
                         if (!docView) {
                             this._props.addDocTab(audiodoc, OpenWhere.addBottom);
                             setTimeout(func);
@@ -1769,7 +1767,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             case 'Escape':
                 this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
                 (document.activeElement as any).blur?.();
-                SelectionManager.DeselectAll();
+                DocumentView.DeselectAll();
                 RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
                 return;
             case 'Enter':
@@ -1891,7 +1889,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     }
     @computed get sidebarCollection() {
         const renderComponent = (tag: string) => {
-            const ComponentTag: any = tag === CollectionViewType.Freeform ? CollectionFreeFormView : tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
+            const ComponentTag: any = tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView;
             return ComponentTag === CollectionStackingView ? (
                 <SidebarAnnos
                     ref={this._sidebarRef}
@@ -1913,7 +1911,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     setHeight={this.setSidebarHeight}
                 />
             ) : (
-                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
+                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.()!, false), true)}>
                     <ComponentTag
                         // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
@@ -1998,11 +1996,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     @observable _isHovering = false;
     onPassiveWheel = (e: WheelEvent) => {
         if (e.clientX > this.ProseRef!.getBoundingClientRect().right) {
-            if (this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform) {
-                // if the scrolled freeform is a child of the sidebar component, we need to let the event go through
-                // so react can let the freeform view handle it. We prevent default to stop any containing views from scrolling
-                e.preventDefault();
-            }
             return;
         }
 
@@ -2037,7 +2030,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     }
     render() {
         TraceMobx();
-        const scale = this._props.NativeDimScaling?.() || 1; // * NumCast(this.layoutDoc._freeform_scale, 1);
+        const scale = this._props.NativeDimScaling?.() || 1;
         const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
         setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
         const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0);
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index bc6454146..7a8b72be0 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -11,7 +11,7 @@ import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSy
 import { GetEffectiveAcl } from '../../../../fields/util';
 import { Docs } from '../../../documents/Documents';
 import { RTFMarkup } from '../../../util/RTFMarkup';
-import { SelectionManager } from '../../../util/SelectionManager';
+import { DocumentView } from '../DocumentView';
 import { OpenWhere } from '../OpenWhere';
 
 const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
@@ -172,7 +172,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
     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?.();
-        SelectionManager.DeselectAll();
+        DocumentView.DeselectAll();
     });
 
     bind('Alt-Enter', () => onKey() || true);
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 6c12b9991..a612f3c65 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -11,11 +11,10 @@ import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
 import { DocServer } from '../../../DocServer';
-import { LinkManager } from '../../../util/LinkManager';
-import { SelectionManager } from '../../../util/SelectionManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { DocumentView } from '../DocumentView';
 import { EquationBox } from '../EquationBox';
 import { FieldViewProps } from '../FieldView';
 import { FormattedTextBox } from './FormattedTextBox';
@@ -116,7 +115,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     _disposer: IReactionDisposer | undefined;
     componentDidMount() {
         this._disposer = reaction(
-            () => SelectionManager.Views.slice(),
+            () => DocumentView.Selected().slice(),
             () => this.updateMenu(undefined, undefined, undefined, undefined)
         );
     }
@@ -144,8 +143,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         const { activeSizes } = active;
         const { activeColors } = active;
         const { activeHighlights } = active;
-        const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc();
-        const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey);
+        const refDoc = DocumentView.Selected().lastElement()?.layoutDoc ?? Doc.UserDoc();
+        const refField = (pfx => (pfx ? pfx + '_' : ''))(DocumentView.Selected().lastElement()?.LayoutFieldKey);
         const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt));
 
         this._activeListType = this.getActiveListStyle();
@@ -237,8 +236,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
                 m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize);
                 m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHighlight));
             });
-        } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) {
-            SelectionManager.Views.forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
+        } else if (DocumentView.Selected().some(dv => dv.ComponentView instanceof EquationBox)) {
+            DocumentView.Selected().forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
         }
         return { activeFamilies: Array.from(activeFamilies), activeSizes: Array.from(activeSizes), activeColors: Array.from(activeColors), activeHighlights: Array.from(activeHighlights) };
     }
@@ -566,7 +565,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
                         if (linkDoc instanceof Doc) {
                             const linkAnchor1 = await Cast(linkDoc.link_anchor_1, Doc);
                             const linkAnchor2 = await Cast(linkDoc.link_anchor_2, Doc);
-                            const currentDoc = SelectionManager.Docs.lastElement();
+                            const currentDoc = DocumentView.Selected().lastElement().Document;
                             if (currentDoc && linkAnchor1 && linkAnchor2) {
                                 if (Doc.AreProtosEqual(currentDoc, linkAnchor1)) {
                                     return StrCast(linkAnchor2.title);
@@ -605,7 +604,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
                     .filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0)
                     .forEach((aref: any) => {
                         const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0];
-                        anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
+                        anchorId && DocServer.GetRefField(anchorId).then(linkDoc => Doc.DeleteLink?.(linkDoc as Doc));
                     });
             }
         }
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 2c366b49b..238267f6e 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -21,11 +21,10 @@ export class SummaryView {
     root: any;
 
     constructor(node: any, view: any, getPos: any) {
-        const self = this;
         this.dom = document.createElement('span');
         this.dom.className = this.className(node.attrs.visibility);
-        this.dom.onpointerdown = function (e: any) {
-            self.onPointerDown(e, node, view, getPos);
+        this.dom.onpointerdown = (e: any) => {
+            this.onPointerDown(e, node, view, getPos);
         };
         this.dom.onkeypress = function (e: any) {
             e.stopPropagation();
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index d5fad296f..91b0ebd5c 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -16,7 +16,6 @@ import { NumCast } from '../../../../fields/Types';
 import { Networking } from '../../../Network';
 import { DocUtils } from '../../../documents/DocUtils';
 import { Docs } from '../../../documents/Documents';
-import { DocumentManager } from '../../../util/DocumentManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
 import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { ImageEditorData } from '../ImageBox';
@@ -28,6 +27,7 @@ import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
 import { PointerHandler } from './generativeFillUtils/PointerHandler';
 import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
 import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
+import { DocumentView } from '../DocumentView';
 
 // enum BrushStyle {
 //     ADD,
@@ -295,7 +295,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                         _height: newCollectionSize,
                         title: 'Image edit collection',
                     });
-                    DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false });
+                    DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History' });
 
                     // opening new tab
                     CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
@@ -400,7 +400,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             parentDoc.current.gen_fill_children = new List<Doc>([newImg]);
         }
 
-        DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true });
+        DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}` });
         adjustImgPositions();
 
         if (isNewCollection && newCollectionRef.current) {
@@ -430,7 +430,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         ImageEditorData.Open = false;
         ImageEditorData.Source = '';
         if (newCollectionRef.current) {
-            DocumentManager.Instance.AddViewRenderedCb(newCollectionRef.current, dv => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce());
+            DocumentView.addViewRenderedCb(newCollectionRef.current, dv => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce());
         }
         setEdits([]);
     };
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 485ba7367..6b4f5e073 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -19,17 +19,14 @@ 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/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 } from '../../collections/collectionFreeForm';
-import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
+import { CollectionFreeFormPannableContents } from '../../collections/collectionFreeForm/CollectionFreeFormPannableContents';
 import { CollectionView } from '../../collections/CollectionView';
 import { TreeView } from '../../collections/TreeView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
@@ -55,8 +52,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         super(props);
         makeObservable(this);
         if (!PresBox.navigateToDocScript) {
-            PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(this.presentation_targetDoc, self)')!;
+            PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(this.presentation_targetDoc, this)')!;
         }
+        CollectionFreeFormPannableContents.SetOverlayPlugin((fform: Doc) => PresBox.Instance.pathLines(fform));
     }
 
     private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -122,8 +120,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return false;
     }
     @computed get selectedDocumentView() {
-        if (SelectionManager.Views.length) return SelectionManager.Views[0];
-        if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.Document);
+        if (DocumentView.Selected().length) return DocumentView.Selected()[0];
+        if (this.selectedArray.size) return DocumentView.getDocumentView(this.Document);
         return undefined;
     }
     @computed get isPres() {
@@ -175,7 +173,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this._unmounting = false;
         this.turnOffEdit(true);
         this._disposers.selection = reaction(
-            () => SelectionManager.Views.slice(),
+            () => DocumentView.Selected().slice(),
             views => (!PresBox.Instance || views.some(view => view.Document === this.Document)) && this.updateCurrentPresentation(),
             { fireImmediately: true }
         );
@@ -200,7 +198,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     startTempMedia = (targetDoc: Doc, activeItem: Doc) => {
         const duration: number = NumCast(activeItem.config_clipEnd) - NumCast(activeItem.config_clipStart);
         if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) {
-            const targMedia = DocumentManager.Instance.getDocumentView(targetDoc);
+            const targMedia = DocumentView.getDocumentView(targetDoc);
             targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.config_clipStart), NumCast(activeItem.config_clipStart) + duration);
         }
     };
@@ -208,7 +206,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     stopTempMedia = (targetDocField: FieldResult) => {
         const targetDoc = DocCast(DocCast(targetDocField).annotationOn) ?? DocCast(targetDocField);
         if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) {
-            const targMedia = DocumentManager.Instance.getDocumentView(targetDoc);
+            const targMedia = DocumentView.getDocumentView(targetDoc);
             targMedia?.ComponentView?.Pause?.();
         }
     };
@@ -260,7 +258,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // go to documents chain
     runSubroutines = (childrenToRun: Opt<Doc[]>, normallyNextSlide: Doc) => {
         if (childrenToRun && childrenToRun[0] !== normallyNextSlide) {
-            childrenToRun.forEach(child => DocumentManager.Instance.showDocument(child, {}));
+            childrenToRun.forEach(child => DocumentView.showDocument(child, {}));
         }
     };
 
@@ -280,7 +278,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 if (listItems && presIndexed < listItems.length) {
                     if (!first) {
                         const listItemDoc = listItems[presIndexed];
-                        const targetView = listItems && DocumentManager.Instance.getFirstDocumentView(listItemDoc);
+                        const targetView = listItems && DocumentView.getFirstDocumentView(listItemDoc);
                         Doc.linkFollowUnhighlight();
                         Doc.HighlightDoc(listItemDoc);
                         listItemDoc.presentation_effect = this.activeItem.presBulletEffect;
@@ -425,7 +423,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc);
             const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext;
             if (context) {
-                const ffview = CollectionFreeFormView.from(DocumentManager.Instance.getFirstDocumentView(context));
+                const ffview = CollectionFreeFormView.from(DocumentView.getFirstDocumentView(context));
                 if (ffview?.childDocs) {
                     PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, frameTime);
                     ffview.layoutDoc._currentFrame = NumCast(activeFrame);
@@ -605,7 +603,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] };
                 bestTarget._freeform_panX = viewport.panX;
                 bestTarget._freeform_panY = viewport.panY;
-                const dv = DocumentManager.Instance.getDocumentView(bestTarget);
+                const dv = DocumentView.getDocumentView(bestTarget);
                 if (dv) {
                     changed = true;
                     const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv._props.PanelWidth() / viewport.width, dv._props.PanelHeight() / viewport.height);
@@ -644,8 +642,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const eleViewCache = Array.from(this._eleArray);
         const resetSelection = action(() => {
             if (!this._props.isSelected()) {
-                const presDocView = DocumentManager.Instance.getDocumentView(this.Document);
-                if (presDocView) SelectionManager.SelectView(presDocView, false);
+                const presDocView = DocumentView.getDocumentView(this.Document);
+                if (presDocView) DocumentView.SelectView(presDocView, false);
                 this.clearSelectedArray();
                 selViewCache.forEach(doc => this.addToSelectedArray(doc));
                 this._dragArray.splice(0, this._dragArray.length, ...dragViewCache);
@@ -660,7 +658,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) {
         if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
-            (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
+            (DocumentView.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
             return;
         }
         const effect = activeItem.presentation_effect && activeItem.presentation_effect !== PresEffect.None ? activeItem.presentation_effect : undefined;
@@ -680,19 +678,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         };
         if (activeItem.presentation_openInLightbox) {
             const context = DocCast(targetDoc.annotationOn) ?? targetDoc;
-            if (!DocumentManager.Instance.getLightboxDocumentView(context)) {
+            if (!DocumentView.getLightboxDocumentView(context)) {
                 LightboxView.Instance.SetLightboxDoc(context);
             }
         }
         if (targetDoc) {
             if (activeItem.presentation_targetDoc instanceof Doc) activeItem.presentation_targetDoc[Animation] = undefined;
 
-            DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, () => {
+            DocumentView.addViewRenderedCb(LightboxView.LightboxDoc, () => {
                 // if target or the doc it annotates is not in the lightbox, then close the lightbox
-                if (!DocumentManager.Instance.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) {
+                if (!DocumentView.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) {
                     LightboxView.Instance.SetLightboxDoc(undefined);
                 }
-                DocumentManager.Instance.showDocument(targetDoc, options, finished);
+                DocumentView.showDocument(targetDoc, options, finished);
             });
         } else finished?.();
     }
@@ -1032,8 +1030,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @action
     selectPres = () => {
-        const presDocView = DocumentManager.Instance.getDocumentView(this.Document);
-        presDocView && SelectionManager.SelectView(presDocView, false);
+        const presDocView = DocumentView.getDocumentView(this.Document);
+        presDocView && DocumentView.SelectView(presDocView, false);
     };
 
     focusElement = (doc: Doc) => {
@@ -1044,7 +1042,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // Regular click
     @action
     selectElement = (doc: Doc, noNav = false) => {
-        CollectionStackedTimeline.CurrentlyPlaying?.map(clip => clip?.ComponentView?.Pause?.());
+        DocumentView.CurrentlyPlaying?.map(clip => clip?.ComponentView?.Pause?.());
         if (noNav) {
             const index = this.childDocs.indexOf(doc);
             if (index >= 0 && index < this.childDocs.length) {
@@ -1200,7 +1198,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const order: JSX.Element[] = [];
         const docs = new Set<Doc>();
         const presCollection = collection;
-        const dv = DocumentManager.Instance.getDocumentView(presCollection);
+        const dv = DocumentView.getDocumentView(presCollection);
         this.childDocs.forEach((doc, index) => {
             const tagDoc = PresBox.targetRenderedDoc(doc);
             const srcContext = Cast(tagDoc.embedContainer, Doc, null);
@@ -2206,7 +2204,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (doc) {
             const tabMap = CollectionDockingView.Instance?.tabMap;
             const docTab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc.type === DocumentType.COL)?.DashDoc;
-            const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? docTab;
+            const presCollection = DocumentView.getContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? docTab;
             const data = Cast(presCollection?.data, listSpec(Doc));
             const configData = Cast(this.Document.data, listSpec(Doc));
             if (data && configData) {
@@ -2282,12 +2280,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @action
     toggleProperties = () => {
-        SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250;
+        SnappingManager.SetPropertiesWidth(SnappingManager.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.PropertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+        const propTitle = SnappingManager.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 = SnappingManager.userVariantColor;
@@ -2316,7 +2314,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.PropertiesWidth > 0 ? activeColor : inactiveColor }} />
                             </div>
                         </Tooltip>
                     </>
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index af0ab3b53..306b98190 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -13,9 +13,8 @@ import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Ty
 import { emptyFunction } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
-import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
-import { SettingsManager } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoable, undoBatch } from '../../../util/UndoManager';
 import { TreeView } from '../../collections/TreeView';
@@ -23,7 +22,7 @@ import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
 import { PinDocView } from '../../PinFuncs';
-import { StyleProp } from '../../StyleProvider';
+import { StyleProp } from '../../StyleProp';
 import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { PresBox } from './PresBox';
@@ -282,8 +281,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @action
     toggleProperties = () => {
-        if (SettingsManager.Instance.propertiesWidth < 5) {
-            SettingsManager.Instance.propertiesWidth = 250;
+        if (SnappingManager.PropertiesWidth < 5) {
+            SnappingManager.SetPropertiesWidth(250);
         }
     };
 
@@ -417,7 +416,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @computed
     get toolbarWidth(): number {
-        const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
+        const presBoxDocView = DocumentView.getDocumentView(this.presBox);
         const width = NumCast(this.presBox?._width);
         return presBoxDocView ? presBoxDocView._props.PanelWidth() : width || 300;
     }
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index f02c56471..a837969aa 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -8,12 +8,12 @@ import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtil
 import { emptyFunction, unimplementedFunction } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocumentType } from '../../documents/DocumentTypes';
-import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
 import { LinkPopup } from '../linking/LinkPopup';
 import './AnchorMenu.scss';
 import { GPTPopup } from './GPTPopup/GPTPopup';
+import { DocumentView } from '../nodes/DocumentView';
 
 @observer
 export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -66,7 +66,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     componentDidMount() {
         this._disposer = reaction(
-            () => SelectionManager.Views.slice(),
+            () => DocumentView.Selected().slice(),
             () => AnchorMenu.Instance.fadeOut(true)
         );
     }
@@ -145,7 +145,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * all selected text available to summarize but its only supported for pdf and web ATM.
      * @returns Whether the GPT icon for summarization should appear
      */
-    canSummarize = () => SelectionManager.Docs.some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any));
+    canSummarize = () => DocumentView.SelectedDocs().some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any));
 
     render() {
         const buttons =
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 8b2b179d3..7dd4047c1 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -5,10 +5,10 @@ import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
 import { Highlight } from '../../../fields/DocSymbols';
 import { List } from '../../../fields/List';
 import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { undoable } from '../../util/UndoManager';
 import { ObservableReactComponent } from '../ObservableReactComponent';
+import { DocumentView } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { OpenWhere } from '../nodes/OpenWhere';
 import { AnchorMenu } from './AnchorMenu';
@@ -56,7 +56,7 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
 
     @computed get linkHighlighted() {
         const found = LinkManager.Instance.getAllDirectLinks(this._props.annoDoc).find(link => {
-            const a1 = LinkManager.getOppositeAnchor(link, this._props.annoDoc);
+            const a1 = Doc.getOppositeAnchor(link, this._props.annoDoc);
             return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1));
         });
         return found;
@@ -103,7 +103,7 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
             e.preventDefault();
         } else if (e.button === 0) {
             e.stopPropagation();
-            LinkFollower.FollowLink(undefined, this._props.annoDoc, false);
+            DocumentView.FollowLink(undefined, this._props.annoDoc, false);
         }
     };
     brushed = () => this._props.annoDoc && Doc.GetBrushHighlightStatus(this._props.annoDoc);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 15ac73f78..45b1d727e 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -15,17 +15,17 @@ import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
 import { DocUtils } from '../../documents/DocUtils';
-import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
+import { DocumentView } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import { LinkInfo } from '../nodes/LinkDocPreview';
 import { PDFBox } from '../nodes/PDFBox';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
 import { AnchorMenu } from './AnchorMenu';
 import { Annotation } from './Annotation';
 import { GPTPopup } from './GPTPopup/GPTPopup';
@@ -122,7 +122,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
         this._disposers.selected = reaction(
             () => this._props.isSelected(),
-            () => SelectionManager.Views.length === 1 && this.setupPdfJsViewer(),
+            () => DocumentView.Selected().length === 1 && this.setupPdfJsViewer(),
             { fireImmediately: true }
         );
         this._disposers.curPage = reaction(
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 5df934231..56552c952 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -12,16 +12,15 @@ import { DocCast, StrCast } from '../../../fields/Types';
 import { DocUtils } from '../../documents/DocUtils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
-import { DocumentManager } from '../../util/DocumentManager';
-import { LinkManager } from '../../util/LinkManager';
 import { SearchUtil } from '../../util/SearchUtil';
-import { SettingsManager } from '../../util/SettingsManager';
+import { SnappingManager } from '../../util/SnappingManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { IRecommendation, Recommendation } from '../newlightbox/components';
 import { fetchRecommendations } from '../newlightbox/utils';
+import { DocumentView } from '../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import './SearchBox.scss';
 
@@ -54,7 +53,7 @@ export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps>
      * or opening it in a new tab.
      */
     selectElement = async (doc: Doc, finishFunc: () => void) => {
-        await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc);
+        await DocumentView.showDocument(doc, { willZoomCentered: true }, finishFunc);
     };
 
     /**
@@ -65,12 +64,12 @@ export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps>
      */
     onResultClick = action(async (doc: Doc) => {
         this._props.selectItem(doc);
-        this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._props.searchString, undefined, false));
+        this.selectElement(doc, () => DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.(this._props.searchString, undefined, false));
     });
 
     componentWillUnmount(): void {
         const doc = this._props.Document;
-        DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
+        DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
     }
 
     @undoBatch
@@ -99,20 +98,18 @@ export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps>
                               }
                     }
                     style={{
-                        fontWeight: LinkManager.Links(this._props.linkFrom).find(
-                            link =>
-                                Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, this._props.linkFrom!), this._props.Document) ||
-                                Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Document)
+                        fontWeight: Doc.Links(this._props.linkFrom).find(
+                            link => Doc.AreProtosEqual(Doc.getOppositeAnchor(link, this._props.linkFrom!), this._props.Document) || Doc.AreProtosEqual(DocCast(Doc.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Document)
                         )
                             ? 'bold'
                             : '',
                     }}
                     className={this._props.className}>
                     <div className="searchBox-result-title">{title as string}</div>
-                    <div className="searchBox-result-type" style={{ color: SettingsManager.userVariantColor }}>
+                    <div className="searchBox-result-type" style={{ color: SnappingManager.userVariantColor }}>
                         {formattedType}
                     </div>
-                    <div className="searchBox-result-keys" style={{ color: SettingsManager.userVariantColor }}>
+                    <div className="searchBox-result-keys" style={{ color: SnappingManager.userVariantColor }}>
                         {this._props.matchedKeys.join(', ')}
                     </div>
                 </div>
@@ -373,7 +370,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
 
         const query = StrCast(this._searchString);
         Doc.SetSearchQuery(query);
-        if (!this._props.linkSearch) Array.from(this._results.keys()).forEach(doc => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true));
+        if (!this._props.linkSearch) Array.from(this._results.keys()).forEach(doc => DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true));
         this._results.clear();
 
         if (query) {
@@ -412,7 +409,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
         this._timeout && clearTimeout(this._timeout);
         this._timeout = undefined;
         this._results.forEach((_, doc) => {
-            DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
+            DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
             Doc.UnBrushDoc(doc);
             Doc.UnHighlightDoc(doc);
             Doc.ClearSearchMatches();
@@ -476,7 +473,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
         const recommendationsJSX: JSX.Element[] = this._recommendations.map(props => <Recommendation {...props} />);
 
         return (
-            <div className="searchBox-container" style={{ pointerEvents: 'all', color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}>
+            <div className="searchBox-container" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}>
                 <div className="searchBox-bar">
                     {isLinkSearch ? null : (
                         <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}>
@@ -501,7 +498,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
                 </div>
                 {resultsJSX.length > 0 && (
                     <div className="searchBox-results-container">
-                        <div className="section-header" style={{ background: SettingsManager.userVariantColor }}>
+                        <div className="section-header" style={{ background: SnappingManager.userVariantColor }}>
                             <div className="section-title">Results</div>
                             <div className="section-subtitle">{`${validResults} result` + (validResults === 1 ? '' : 's')}</div>
                         </div>
@@ -510,7 +507,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
                 )}
                 {recommendationsJSX.length > 0 && (
                     <div className="searchBox-recommendations-container">
-                        <div className="section-header" style={{ background: SettingsManager.userVariantColor }}>
+                        <div className="section-header" style={{ background: SnappingManager.userVariantColor }}>
                             <div className="section-title">Recommendations</div>
                             <div className="section-subtitle">{`${validResults} result` + (validResults === 1 ? '' : 's')}</div>
                         </div>
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index 6ed3b240d..78a1a92f7 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -6,8 +6,8 @@ import * as React from 'react';
 import { emptyFunction } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
-import { DocumentManager } from '../../util/DocumentManager';
-import { SettingsManager } from '../../util/SettingsManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { DocumentView } from '../nodes/DocumentView';
 import { FocusViewOptions } from '../nodes/FocusViewOptions';
 
 export interface SelectedDocViewProps {
@@ -33,14 +33,14 @@ export class SelectedDocView extends React.Component<SelectedDocViewProps> {
                         return {
                             text: StrCast(doc.title),
                             val: StrCast(doc._id),
-                            color: SettingsManager.userColor,
-                            background: SettingsManager.userBackgroundColor,
+                            color: SnappingManager.userColor,
+                            background: SnappingManager.userBackgroundColor,
                             icon: <FontAwesomeIcon size="1x" icon={Doc.toIcon(doc)} />,
-                            onClick: () => DocumentManager.Instance.showDocument(doc, options, emptyFunction),
+                            onClick: () => DocumentView.showDocument(doc, options, emptyFunction),
                         };
                     })}
-                    color={SettingsManager.userColor}
-                    background={SettingsManager.userBackgroundColor}
+                    color={SnappingManager.userColor}
+                    background={SnappingManager.userBackgroundColor}
                 />
             </div>
         );
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index f82ae34c5..9f97efcce 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -11,8 +11,6 @@ import { AclAdmin, DashVersion } from '../../../fields/DocSymbols';
 import { StrCast } from '../../../fields/Types';
 import { GetEffectiveAcl } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
-import { CurrentUserUtils } from '../../util/CurrentUserUtils';
-import { DocumentManager } from '../../util/DocumentManager';
 import { dropActionType } from '../../util/DropActionTypes';
 import { PingManager } from '../../util/PingManager';
 import { ReportManager } from '../../util/reportManager/ReportManager';
@@ -25,7 +23,7 @@ import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { CollectionLinearView } from '../collections/collectionLinear';
 import { DashboardView } from '../DashboardView';
 import { Colors } from '../global/globalEnums';
-import { DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider } from '../StyleProvider';
 import './TopBar.scss';
@@ -170,7 +168,7 @@ export class TopBar extends ObservableReactComponent<{}> {
                     color={this.color}
                     style={{ fontWeight: 700, fontSize: '1rem' }}
                     onClick={(e: React.MouseEvent) => {
-                        const dashView = Doc.ActiveDashboard && DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard);
+                        const dashView = Doc.ActiveDashboard && DocumentView.getDocumentView(Doc.ActiveDashboard);
                         dashView?.showContextMenu(e.clientX + 20, e.clientY + 30);
                     }}
                 />
@@ -184,7 +182,7 @@ export class TopBar extends ObservableReactComponent<{}> {
      * and allows the user to access their account settings etc.
      */
     @computed get topbarRight() {
-        const upToDate = DashVersion === CurrentUserUtils.ServerVersion;
+        const upToDate = DashVersion === SnappingManager.ServerVersion;
         return (
             <div className="topbar-right">
                 {Doc.ActiveDashboard ? (
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 5fd053eef..bb6995398 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -224,8 +224,25 @@ 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;
+    @observable.shallow public static CurrentlyLoading: Doc[] = observable([]);
+    // removes from currently loading display
+    public static removeCurrentlyLoading(doc: Doc) {
+        if (Doc.CurrentlyLoading) {
+            const index = Doc.CurrentlyLoading.indexOf(doc);
+            runInAction(() => index !== -1 && Doc.CurrentlyLoading.splice(index, 1));
+        }
+    }
+    // adds doc to currently loading display
+    public static addCurrentlyLoading(doc: Doc) {
+        if (Doc.CurrentlyLoading.indexOf(doc) === -1) {
+            runInAction(() => Doc.CurrentlyLoading.push(doc));
+        }
+    }
 
-    public static AddLink: undefined | ((link: Doc, checkExists?: boolean) => void);
+    public static AddLink: (link: Doc, checkExists?: boolean) => void;
+    public static DeleteLink: (link: Doc) => void;
+    public static Links: (link: Doc | undefined) => Doc[];
+    public static getOppositeAnchor: (linkDoc: Doc, anchor: Doc) => Doc | undefined;
 
     public static get MySharedDocs()          { return DocCast(Doc.UserDoc().mySharedDocs); } // prettier-ignore
     public static get MyUserDocView()         { return DocCast(Doc.UserDoc().myUserDocView); } // prettier-ignore
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 8a3787768..8fe365ac2 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -142,7 +142,6 @@ export class ScriptField extends ObjectField {
         return CompileScript(script, {
             params: {
                 this: Doc?.name || 'Doc', // this is the doc that executes the script
-                self: Doc?.name || 'Doc', // self is the root doc of the doc that executes the script
                 documentView: 'any',
                 _last_: 'any', // _last_ is the previous value of a computed field when it is being triggered to re-run.
                 _setCacheResult_: 'any', // set the cached value of the function
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index c73689e1f..335683270 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -86,7 +86,6 @@ export const documentSchema = createSchema({
     followLinkLocation: 'string', // flag for where to place content when following a click interaction (e.g., add:right, lightbox, default, )
     hideLinkButton: 'boolean', // whether the blue link counter button should be hidden
     layout_hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden
-    link_displayLine: 'boolean', // whether a link connection should be shown between link anchor endpoints.
     isLightbox: 'boolean', // whether the marked object will display addDocTab() calls that target "lightbox" destinations
     layers: listSpec('string'), // which layers the document is part of
     _lockedPosition: 'boolean', // whether the document can be moved (dragged)
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 55a85ed1b..7e67690d3 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -92,7 +92,6 @@ import {
     faTrash,
     faTrashAlt,
     faTree,
-    faRoute,
     faTv,
     faUndoAlt,
     faVideo,
@@ -104,7 +103,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 '../ClientUtils';
+import { 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';
@@ -112,8 +111,8 @@ import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { SettingsManager } from '../client/util/SettingsManager';
 import { Transform } from '../client/util/Transform';
 import { UndoManager } from '../client/util/UndoManager';
+import { DashboardView } from '../client/views/DashboardView';
 import { GestureOverlay } from '../client/views/GestureOverlay';
-import { TabDocView } from '../client/views/collections/TabDocView';
 import { AudioBox } from '../client/views/nodes/AudioBox';
 import { DocumentView } from '../client/views/nodes/DocumentView';
 import { RadialMenu } from '../client/views/nodes/RadialMenu';
@@ -127,7 +126,7 @@ import './AudioUpload.scss';
 import { Uploader } from './ImageUpload';
 import './ImageUpload.scss';
 import './MobileInterface.scss';
-import { DashboardView } from '../client/views/DashboardView';
+import { emptyFunction } from '../Utils';
 
 library.add(
     ...[
@@ -691,7 +690,7 @@ export class MobileInterface extends React.Component {
         // Only making button available if it is an image
         if (!(this._activeDoc.type === 'collection' || this._activeDoc.type === 'presentation')) {
             return (
-                <div className="docButton" title={'Pin to presentation'} style={{ backgroundColor: 'white', color: 'black' }} onClick={e => TabDocView.PinDoc(this._activeDoc, {})}>
+                <div className="docButton" title={'Pin to presentation'} style={{ backgroundColor: 'white', color: 'black' }} onClick={e => DocumentView.PinDoc(this._activeDoc, {})}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
                 </div>
             );
diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts
index 0a961f570..8ae13454e 100644
--- a/src/server/SharedMediaTypes.ts
+++ b/src/server/SharedMediaTypes.ts
@@ -13,6 +13,11 @@ export namespace AcceptableMedia {
     export const audioFormats = ['.wav', '.mp3', '.mpeg', '.flac', '.au', '.aiff', '.m4a', '.webm'];
 }
 
+export enum AudioAnnoState {
+    stopped = 'stopped',
+    playing = 'playing',
+}
+
 export namespace Upload {
     export function isImageInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation {
         return 'nativeWidth' in uploadResponse;
-- 
cgit v1.2.3-70-g09d2

