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/pdf/AnchorMenu.tsx | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)

(limited to 'src/client/views/pdf/AnchorMenu.tsx')

diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 59f191af0..9c4080154 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -4,7 +4,8 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ColorResult } from 'react-color';
-import { Utils, returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../Utils';
+import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { unimplementedFunction } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -17,6 +18,7 @@ import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
 
 @observer
 export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: AnchorMenu;
 
     private _disposer: IReactionDisposer | undefined;
@@ -37,7 +39,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     // GPT additions
     @observable private selectedText: string = '';
     @action
-    public setSelectedText = (txt: string) => (this.selectedText = txt);
+    public setSelectedText = (txt: string) => {
+        this.selectedText = txt;
+    };
 
     public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
 
@@ -64,7 +68,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     componentDidMount() {
         this._disposer = reaction(
             () => SelectionManager.Views.slice(),
-            sel => AnchorMenu.Instance.fadeOut(true)
+            () => AnchorMenu.Instance.fadeOut(true)
         );
     }
 
@@ -72,7 +76,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * Invokes the API with the selected text and stores it in the summarized text.
      * @param e pointer down event
      */
-    gptSummarize = async (e: React.PointerEvent) => {
+    gptSummarize = async () => {
         // move this logic to gptpopup, need to implement generate again
         GPTPopup.Instance.setVisible(true);
         GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
@@ -128,7 +132,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             <Group>
                 <IconButton
                     icon={<FontAwesomeIcon icon="highlighter" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />}
-                    tooltip={'Click to Highlight'}
+                    tooltip="Click to Highlight"
                     onClick={this.highlightClicked}
                     colorPicker={this.highlightColor}
                     color={SettingsManager.userColor}
@@ -144,7 +148,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             hsl: { a: 0, h: 0, s: 0, l: 0 },
             rgb: { a: 0, r: 0, b: 0, g: 0 },
         };
-        this.highlightColor = Utils.colorString(col);
+        this.highlightColor = ClientUtils.colorString(col);
     };
 
     /**
@@ -167,7 +171,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             color={SettingsManager.userColor}
                         />
                     </div>
-                    {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
+                    {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */}
                     {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
                         <IconButton
                             tooltip="Summarize with AI" //
@@ -187,7 +191,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                     <Popup
                         tooltip="Find document to link to selected text" //
                         type={Type.PRIM}
-                        icon={<FontAwesomeIcon icon={'search'} />}
+                        icon={<FontAwesomeIcon icon="search" />}
                         popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />}
                         color={SettingsManager.userColor}
                     />
@@ -230,7 +234,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                     )}
                     {this.IsTargetToggler !== returnFalse && (
                         <Toggle
-                            tooltip={'Make target visibility toggle on click'}
+                            tooltip="Make target visibility toggle on click"
                             type={Type.PRIM}
                             toggleType={ToggleType.BUTTON}
                             toggleStatus={this.IsTargetToggler()}
-- 
cgit v1.2.3-70-g09d2


From b6229b0a6141afbfd0e78e3ec870218187864def Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 19 Apr 2024 11:02:05 -0400
Subject: fixed text search highlighting.  fixed first typed characfter of note
 to have marks.

---
 package-lock.json                                  |   2 +-
 src/client/apis/gpt/GPT.ts                         |   8 +-
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/util/DropConverter.ts                   |  77 ++---
 src/client/views/nodes/ComparisonBox.tsx           |  28 +-
 src/client/views/nodes/KeyValueBox.tsx             |  61 ++--
 .../nodes/formattedText/FormattedTextBox.scss      |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 346 +++++++++++----------
 .../formattedText/FormattedTextBoxComment.tsx      |   2 +-
 src/client/views/nodes/formattedText/marks_rts.ts  |   6 +-
 src/client/views/pdf/AnchorMenu.tsx                |   6 +-
 src/fields/Doc.ts                                  | 160 +++++++---
 src/fields/util.ts                                 |   2 +-
 webpack.config.js                                  |   2 +-
 14 files changed, 399 insertions(+), 305 deletions(-)

(limited to 'src/client/views/pdf/AnchorMenu.tsx')

diff --git a/package-lock.json b/package-lock.json
index d366bb717..cc1a0ea64 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -296,7 +296,7 @@
                 "webpack-dev-server": "^5.0.4"
             },
             "engines": {
-                "node": ">=10.0.0"
+                "node": ">=12.0.0"
             }
         },
         "node_modules/@aashutoshrathi/word-wrap": {
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 63563cb79..e3e87e017 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -27,9 +27,9 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
  */
 let lastCall = '';
 let lastResp = '';
-const gptAPICall = async (inputText: string, callType: GPTCallType) => {
-    if (callType === GPTCallType.SUMMARY) inputText += '.';
-    const opts: GPTCallOpts = callTypeMap[callType];
+const gptAPICall = async (inputTextIn: string, callType: GPTCallType) => {
+    const inputText = callType === GPTCallType.SUMMARY ? inputTextIn + '.' : inputTextIn;
+    const opts = callTypeMap[callType];
     if (lastCall === inputText) return lastResp;
     try {
         const configuration: ClientOptions = {
@@ -69,8 +69,8 @@ const gptImageCall = async (prompt: string, n?: number) => {
         // return response.data.data[0].url;
     } catch (err) {
         console.error(err);
-        return;
     }
+    return undefined;
 };
 
 export { gptAPICall, gptImageCall, GPTCallType };
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 7811f8605..535f7cf9d 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1057,4 +1057,4 @@ ScriptingGlobals.add(function importDocument()  { return CurrentUserUtils.import
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
 // eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function getSharingDoc()   { return Doc.SharingDoc() });
\ No newline at end of file
+ScriptingGlobals.add(function getSharingDoc()   { return Doc.SharingDoc() });
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index ed5749d06..0314af06b 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,10 +1,9 @@
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, StrListCast } from '../../fields/Doc';
 import { DocData } from '../../fields/DocSymbols';
 import { ObjectField } from '../../fields/ObjectField';
 import { RichTextField } from '../../fields/RichTextField';
-import { listSpec } from '../../fields/Schema';
 import { ComputedField, ScriptField } from '../../fields/ScriptField';
-import { Cast, StrCast } from '../../fields/Types';
+import { StrCast } from '../../fields/Types';
 import { ImageField } from '../../fields/URLField';
 import { Docs } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
@@ -12,21 +11,6 @@ import { ButtonType, FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'
 import { DragManager } from './DragManager';
 import { ScriptingGlobals } from './ScriptingGlobals';
 
-/**
- * Converts a Doc to a render template that can be applied to other Docs to customize how they render while
- * still using the other Doc as the backing data store (ie, dataDoc). During rendering, if a layout Doc is provided
- * with 'isTemplateDoc' set, then the layout Doc is treated as a template for the rendered Doc.  The template Doc is
- * "expanded" to create an template instance for the rendered Doc.
- *
- *
- * @param doc the doc to convert to a template
- * @returns 'doc'
- */
-export function MakeTemplate(doc: Doc) {
-    doc.isTemplateDoc = makeTemplate(doc, true);
-    return doc;
-}
-
 /**
  *
  * Recursively converts 'doc' into a template that can be used to render other documents.
@@ -63,26 +47,22 @@ function makeTemplate(doc: Doc, first: boolean = true): boolean {
     }
     return isTemplate;
 }
-export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
-    data?.draggedDocuments.map((doc, i) => {
-        let dbox = doc;
-        // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
-        if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes(FontIconBox.name)) {
-            if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) {
-                //dbox = Doc.MakeEmbedding(doc);  // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon
-                dbox = Doc.MakeEmbedding(dbox);
-                const dragProps = Cast(dbox.dropPropertiesToRemove, listSpec('string'), []);
-                const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps));
-                remProps.map(prop => (dbox[prop] = undefined));
-            }
-        } else if (!doc.onDragStart && !doc.isButtonBar) {
-            dbox = makeUserTemplateButton(doc);
-        } else if (doc.isButtonBar) {
-            dbox.ignoreClick = true;
-        }
-        data.droppedDocuments[i] = dbox;
-    });
+
+/**
+ * Converts a Doc to a render template that can be applied to other Docs to customize how they render while
+ * still using the other Doc as the backing data store (ie, dataDoc). During rendering, if a layout Doc is provided
+ * with 'isTemplateDoc' set, then the layout Doc is treated as a template for the rendered Doc.  The template Doc is
+ * "expanded" to create an template instance for the rendered Doc.
+ *
+ *
+ * @param doc the doc to convert to a template
+ * @returns 'doc'
+ */
+export function MakeTemplate(doc: Doc) {
+    doc.isTemplateDoc = makeTemplate(doc, true);
+    return doc;
 }
+
 export function makeUserTemplateButton(doc: Doc) {
     const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
     if (layoutDoc.type !== DocumentType.FONTICON) {
@@ -106,7 +86,30 @@ export function makeUserTemplateButton(doc: Doc) {
     dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory)');
     return dbox;
 }
+export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
+    data?.draggedDocuments.forEach((doc, i) => {
+        let dbox = doc;
+        // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
+        if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes(FontIconBox.name)) {
+            if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) {
+                // dbox = Doc.MakeEmbedding(doc);  // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon
+                dbox = Doc.MakeEmbedding(dbox);
+                const dragProps = StrListCast(dbox.dropPropertiesToRemove);
+                const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps));
+                remProps.forEach(prop => {
+                    dbox[prop] = undefined;
+                });
+            }
+        } else if (!doc.onDragStart && !doc.isButtonBar) {
+            dbox = makeUserTemplateButton(doc);
+        } else if (doc.isButtonBar) {
+            dbox.ignoreClick = true;
+        }
+        data.droppedDocuments[i] = dbox;
+    });
+}
 ScriptingGlobals.add(
+    // eslint-disable-next-line prefer-arrow-callback
     function convertToButtons(dragData: any) {
         convertDropDataToButtons(dragData as DragManager.DocumentDragData);
     },
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 99f9c03bf..708536de0 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -52,13 +52,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     @undoBatch
     private internalDrop = (e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
         if (dropEvent.complete.docDragData) {
-            const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
-            const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.Document, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
-            Doc.SetContainer(droppedDocs.lastElement(), this.dataDoc);
+            const { droppedDocuments } = dropEvent.complete.docDragData;
+            const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocuments, this.Document, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
+            Doc.SetContainer(droppedDocuments.lastElement(), this.dataDoc);
             !added && e.preventDefault();
             e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
             return added;
         }
+        return undefined;
     };
 
     private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
@@ -83,7 +84,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                     // on click, animate slider movement to the targetWidth
                     this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth();
                     setTimeout(
-                        action(() => (this._animating = '')),
+                        action(() => {
+                            this._animating = '';
+                        }),
                         200
                     );
                 })
@@ -109,7 +112,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         });
         if (anchor) {
             if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
-            /* addAsAnnotation &&*/ this.addDocument(anchor);
+            /* addAsAnnotation && */ this.addDocument(anchor);
             PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document);
             return anchor;
         }
@@ -148,7 +151,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 return true;
             },
             emptyFunction,
-            e => this.clearDoc(which)
+            () => this.clearDoc(which)
         );
     };
     docStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
@@ -213,15 +216,16 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         const displayDoc = (whichSlot: string) => {
             const whichDoc = DocCast(this.dataDoc[whichSlot]);
             const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
-            const layoutTemplateString = targetDoc ? '' : this.testForTextFields(whichSlot);
-            return targetDoc || layoutTemplateString ? (
+            const layoutString = targetDoc ? '' : this.testForTextFields(whichSlot);
+            return targetDoc || layoutString ? (
                 <>
                     <DocumentView
+                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
-                        ignoreUsePath={layoutTemplateString ? true : undefined}
+                        ignoreUsePath={layoutString ? true : undefined}
                         renderDepth={this.props.renderDepth + 1}
-                        LayoutTemplateString={layoutTemplateString}
-                        Document={layoutTemplateString ? this.Document : targetDoc}
+                        LayoutTemplateString={layoutString}
+                        Document={layoutString ? this.Document : targetDoc}
                         containerViewPath={this.DocumentView?.().docViewPath}
                         moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2}
@@ -234,7 +238,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         hideLinkButton
                         pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
                     />
-                    {layoutTemplateString ? null : clearButton(whichSlot)}
+                    {layoutString ? null : clearButton(whichSlot)}
                 </> // placeholder image if doc is missing
             ) : (
                 <div className="placeholder">
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 74773b244..b8296ce51 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -79,7 +80,8 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
      * @param value
      * @returns
      */
-    public static CompileKVPScript(rawvalue: string): KVPScript | undefined {
+    public static CompileKVPScript(rawvalueIn: string): KVPScript | undefined {
+        let rawvalue = rawvalueIn;
         const onDelegate = rawvalue.startsWith('=');
         rawvalue = onDelegate ? rawvalue.substring(1) : rawvalue;
         const type: 'computed' | 'script' | false = rawvalue.startsWith(':=') ? 'computed' : rawvalue.startsWith('$=') ? 'script' : false;
@@ -87,7 +89,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
         rawvalue = rawvalue.replace(/.*\(\((.*)\)\)/, 'dashCallChat(_setCacheResult_, this, `$1`)');
         const value = ["'", '"', '`'].includes(rawvalue.length ? rawvalue[0] : '') || !isNaN(rawvalue as any) ? rawvalue : '`' + rawvalue + '`';
 
-        var script = ScriptField.CompileScript(rawvalue, {}, true, undefined, DocumentIconContainer.getTransformer());
+        let script = ScriptField.CompileScript(rawvalue, {}, true, undefined, DocumentIconContainer.getTransformer());
         if (!script.compiled) {
             script = ScriptField.CompileScript(value, {}, true, undefined, DocumentIconContainer.getTransformer());
         }
@@ -153,20 +155,20 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
 
         const ids: { [key: string]: string } = {};
         const protos = Doc.GetAllPrototypes(doc);
-        for (const proto of protos) {
+        protos.forEach(proto => {
             Object.keys(proto).forEach(key => {
                 if (!(key in ids) && realDoc[key] !== ComputedField.undefined) {
                     ids[key] = key;
                 }
             });
-        }
+        });
 
         const rows: JSX.Element[] = [];
         let i = 0;
         const self = this;
         const keys = Object.keys(ids).slice();
-        //for (const key of [...keys.filter(id => id !== 'layout' && !id.includes('_')).sort(), ...keys.filter(id => id === 'layout' || id.includes('_')).sort()]) {
-        for (const key of keys.sort((a: string, b: string) => {
+        // for (const key of [...keys.filter(id => id !== 'layout' && !id.includes('_')).sort(), ...keys.filter(id => id === 'layout' || id.includes('_')).sort()]) {
+        const sortedKeys = keys.sort((a: string, b: string) => {
             const a_ = a.split('_')[0];
             const b_ = b.split('_')[0];
             if (a_ < b_) return -1;
@@ -174,7 +176,8 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
             if (a === a_) return -1;
             if (b === b_) return 1;
             return a === b ? 0 : a < b ? -1 : 1;
-        })) {
+        });
+        sortedKeys.forEach(key => {
             rows.push(
                 <KeyValuePair
                     doc={realDoc}
@@ -195,7 +198,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
                     keyName={key}
                 />
             );
-        }
+        });
         return rows;
     }
     @computed get newKeyValue() {
@@ -229,7 +232,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
         this._splitPercentage = Math.max(0, 100 - Math.round(((e.clientX - nativeWidth.left) / nativeWidth.width) * 100));
     };
     @action
-    onDividerUp = (e: PointerEvent): void => {
+    onDividerUp = (): void => {
         document.removeEventListener('pointermove', this.onDividerMove);
         document.removeEventListener('pointerup', this.onDividerUp);
     };
@@ -244,11 +247,11 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
         const rows = this.rows.filter(row => row.isChecked);
         if (rows.length > 1) {
             const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this._props.Document).title}`, _chromeHidden: true });
-            for (const row of rows) {
+            rows.forEach(row => {
                 const field = this.createFieldView(DocCast(this._props.Document), row);
                 field && Doc.AddDocToList(parent, 'data', field);
                 row.uncheck();
-            }
+            });
             return parent;
         }
         return rows.length ? this.createFieldView(DocCast(this._props.Document), rows.lastElement()) : undefined;
@@ -256,22 +259,23 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
 
     createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
         const metaKey = row._props.keyName;
-        const fieldTemplate = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
-        fieldTemplate.title = metaKey;
-        fieldTemplate.layout_fitWidth = true;
-        fieldTemplate._xMargin = 10;
-        fieldTemplate._yMargin = 10;
-        fieldTemplate._width = 100;
-        fieldTemplate._height = 40;
-        fieldTemplate.layout = this.inferType(templateDoc[metaKey], metaKey);
-        return fieldTemplate;
+        const fieldTempDoc = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
+        fieldTempDoc.title = metaKey;
+        fieldTempDoc.layout_fitWidth = true;
+        fieldTempDoc._xMargin = 10;
+        fieldTempDoc._yMargin = 10;
+        fieldTempDoc._width = 100;
+        fieldTempDoc._height = 40;
+        fieldTempDoc.layout = this.inferType(templateDoc[metaKey], metaKey);
+        return fieldTempDoc;
     };
 
     inferType = (data: FieldResult, metaKey: string) => {
         const options = { _width: 300, _height: 300, title: metaKey };
         if (data instanceof RichTextField || typeof data === 'string' || typeof data === 'number') {
             return FormattedTextBox.LayoutString(metaKey);
-        } else if (data instanceof List) {
+        }
+        if (data instanceof List) {
             if (data.length === 0) {
                 return Docs.Create.StackingDocument([], options);
             }
@@ -280,21 +284,18 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
                 return Docs.Create.StackingDocument([], options);
             }
             switch (first.data.constructor) {
-                case RichTextField:
-                    return Docs.Create.TreeDocument([], options);
-                case ImageField:
-                    return Docs.Create.MasonryDocument([], options);
-                default:
-                    console.log(`Template for ${first.data.constructor} not supported!`);
-                    return undefined;
-            }
+                case RichTextField: return Docs.Create.TreeDocument([], options);
+                case ImageField:    return Docs.Create.MasonryDocument([], options);
+                default:            console.log(`Template for ${first.data.constructor} not supported!`);
+                                    return undefined;
+            } // prettier-ignore
         } else if (data instanceof ImageField) {
             return ImageBox.LayoutString(metaKey);
         }
         return new Doc();
     };
 
-    specificContextMenu = (e: React.MouseEvent): void => {
+    specificContextMenu = (): void => {
         const cm = ContextMenu.Instance;
         const open = cm.findByDescription('Change Perspective...');
         const openItems: ContextMenuProps[] = open && 'subitems' in open ? open.subitems : [];
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 38dd2e847..5b2b558fc 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -350,7 +350,7 @@ footnote::before {
     span {
         font-family: inherit;
         background-color: inherit;
-        display: 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 // contents; // fixes problem where extra space is added around <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 31252e0ab..c7543560d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from '
 import { EditorView } from 'prosemirror-view';
 import * as React from 'react';
 import { BsMarkdownFill } from 'react-icons/bs';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, smoothScroll } from '../../../../ClientUtils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, StopEvent } from '../../../../ClientUtils';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
@@ -82,6 +82,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
     public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
+    // eslint-disable-next-line no-use-before-define
     public static Instance: FormattedTextBox;
     public static LiveTextUndo: UndoManager.Batch | undefined;
     static _globalHighlightsCache: string = '';
@@ -100,7 +101,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     private _inDrop = false;
     private _finishingLink = false;
     private _searchIndex = 0;
-    private _lastTimedMark: Mark | undefined = undefined;
     private _cachedLinks: Doc[] = [];
     private _undoTyping?: UndoManager.Batch;
     private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -111,10 +111,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     private _keymap: any = undefined;
     private _rules: RichTextRules | undefined;
     private _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason.  This flags the 2nd click as happening on a selection which allows bullet points to toggle
-    private _forceDownNode: Node | undefined;
-    private _downX = 0;
-    private _downY = 0;
-    private _downTime = 0;
     private _break = true;
     public ProseRef?: HTMLDivElement;
     public get EditorView() {
@@ -173,8 +169,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 keymap(baseKeymap),
                 new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
                 new Plugin({
-                    view(editorView) {
-                        return new FormattedTextBoxComment(editorView);
+                    view(/* editorView */) {
+                        return new FormattedTextBoxComment();
                     },
                 }),
             ],
@@ -220,13 +216,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     public RemoveLinkFromDoc(linkDoc?: Doc) {
         this.unhighlightSearchTerms();
         const state = this._editorView?.state;
-        const a1 = linkDoc?.link_anchor_1 as Doc;
-        const a2 = linkDoc?.link_anchor_2 as Doc;
+        const a1 = DocCast(linkDoc?.link_anchor_1);
+        const a2 = DocCast(linkDoc?.link_anchor_2);
         if (state && a1 && a2 && this._editorView) {
             this.removeDocument(a1);
             this.removeDocument(a2);
-            var allFoundLinkAnchors: any[] = [];
-            state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => {
+            let allFoundLinkAnchors: any[] = [];
+            state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any /* , pos: number, parent: any */) => {
                 const foundLinkAnchors = findLinkMark(node.marks)?.attrs.allAnchors.filter((a: any) => a.anchorId === a1[Id] || a.anchorId === a2[Id]) || [];
                 allFoundLinkAnchors = foundLinkAnchors.length ? foundLinkAnchors : allFoundLinkAnchors;
                 return true;
@@ -249,7 +245,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     }
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
-        const rootDoc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document);
+        const rootDoc: Doc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document);
         if (!pinProps && this._editorView?.state.selection.empty) return rootDoc;
         const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc });
         this.addDocument(anchor);
@@ -264,11 +260,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     setupAnchorMenu = () => {
         AnchorMenu.Instance.Status = 'marquee';
 
-        AnchorMenu.Instance.OnClick = (e: PointerEvent) => {
+        AnchorMenu.Instance.OnClick = () => {
             !this.layoutDoc.layout_showSidebar && this.toggleSidebar();
             setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created
         };
-        AnchorMenu.Instance.OnAudio = (e: PointerEvent) => {
+        AnchorMenu.Instance.OnAudio = () => {
             !this.layoutDoc.layout_showSidebar && this.toggleSidebar();
             const anchor = this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true, true);
 
@@ -279,7 +275,8 @@ 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));
+                    DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
+
                     const reactionDisposer = reaction(
                         () => target.mediaState,
                         dictation => {
@@ -324,7 +321,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 ele.append(contents);
             }
             this._selectionHTML = ele?.innerHTML;
-        } catch (e) {}
+        } catch (e) {
+            /* empty */
+        }
     };
 
     leafText = (node: Node) => {
@@ -339,7 +338,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         return '';
     };
     dispatchTransaction = (tx: Transaction) => {
-        if (this._editorView && (this._editorView as any).docView) {
+        if (this._editorView) {
             const state = this._editorView.state.apply(tx);
             this._editorView.updateState(state);
             this.tryUpdateDoc(false);
@@ -347,13 +346,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     };
 
     tryUpdateDoc = (force: boolean) => {
-        if (this._editorView && (this._editorView as any).docView) {
+        if (this._editorView) {
             const { state } = this._editorView;
             const { dataDoc } = this;
             const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText);
             const newJson = JSON.stringify(state.toJSON());
             const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
-            const templateData = this.Document !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template
             const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype
             const layoutData = this.layoutDoc.isTemplateDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text inherited from a prototype
             const effectiveAcl = GetEffectiveAcl(dataDoc);
@@ -362,7 +360,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
             if ([AclEdit, AclAdmin, AclSelfEdit, AclAugment].includes(effectiveAcl)) {
                 const accumTags = [] as string[];
-                state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
+                state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any /* , pos: number, parent: any */) => {
                     if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) {
                         accumTags.push(node.attrs.fieldKey);
                     }
@@ -417,37 +415,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     insertTime = () => {
         let linkTime;
         let linkAnchor;
-        let link;
-        LinkManager.Links(this.dataDoc).forEach((l, i) => {
-            const anchor = (l.link_anchor_1 as Doc).annotationOn ? (l.link_anchor_1 as Doc) : (l.link_anchor_2 as Doc).annotationOn ? (l.link_anchor_2 as Doc) : undefined;
+        LinkManager.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 */);
                 linkAnchor = anchor;
-                link = l;
             }
         });
         if (this._editorView && linkTime) {
-            const state = this._editorView.state;
-            const now = Date.now();
-            let mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(now / 1000) });
-            if (!this._break && state.selection.to !== state.selection.from) {
-                for (let i = state.selection.from; i <= state.selection.to; i++) {
-                    const pos = state.doc.resolve(i);
-                    const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark);
-                    if (um) {
-                        mark = um;
-                        break;
-                    }
-                }
-            }
-
-            const path = (this._editorView.state.selection.$from as any).path;
-            if (linkAnchor && path[path.length - 3].type !== this._editorView.state.schema.nodes.code_block) {
+            const { state } = this._editorView;
+            const { path } = state.selection.$from as any;
+            if (linkAnchor && path[path.length - 3].type !== state.schema.nodes.code_block) {
                 const time = linkTime + Date.now() / 1000 - this._recordingStart / 1000;
                 this._break = false;
-                const from = state.selection.from;
-                const value = this._editorView.state.schema.nodes.audiotag.create({ timeCode: time, audioId: linkAnchor[Id] });
-                const replaced = this._editorView.state.tr.insert(from - 1, value);
+                const { from } = state.selection;
+                const value = state.schema.nodes.audiotag.create({ timeCode: time, audioId: linkAnchor[Id] });
+                const replaced = state.tr.insert(from - 1, value);
                 this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1))));
             }
         }
@@ -464,14 +447,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 link.link_relationship === LinkManager.AutoKeywords
         ); // prettier-ignore
         if (this._editorView?.state.doc.textContent) {
-            const f = this._editorView.state.selection.from;
-
-            const t = this._editorView.state.selection.to;
-            var tr = this._editorView.state.tr as any;
-            const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
-            tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
-            Doc.MyPublishedDocs.filter(term => term.title).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
-            tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
+            let { tr } = this._editorView.state;
+            const { from, to } = this._editorView.state.selection;
+            const { autoLinkAnchor } = this._editorView.state.schema.marks;
+            tr = tr.removeMark(0, tr.doc.content.size, autoLinkAnchor);
+            Doc.MyPublishedDocs.filter(term => term.title).forEach(term => {
+                tr = this.hyperlinkTerm(tr, term, newAutoLinks);
+            });
+            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);
@@ -507,11 +490,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
      * function of a freeform view that is driven by the text box's text.  The include directive will copy the code of the published
      * document into the code being evaluated.
      */
-    hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
+    hyperlinkTerm = (trIn: any, target: Doc, newAutoLinks: Set<Doc>) => {
+        let tr = trIn;
         const editorView = this._editorView;
-        if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
+        if (editorView && !Doc.AreProtosEqual(target, this.Document)) {
             const autoLinkTerm = Field.toString(target.title as FieldType).replace(/^@/, '');
-            var alink: Doc | undefined;
+            let alink: Doc | undefined;
             this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
                 if (
                     !sel.$anchor.pos ||
@@ -523,7 +507,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 ) {
                     const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
                     tr = tr.addMark(sel.from, sel.to, splitter);
-                    tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+                    tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number /* , parent: any */) => {
                         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 ??
@@ -554,12 +538,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         return true;
     };
     highlightSearchTerms = (terms: string[], backward: boolean) => {
-        if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
-            const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
-            const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
-            const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
-            const length = res[0].length;
-            let tr = this._editorView.state.tr;
+        const { _editorView } = this;
+        if (_editorView && terms.some(t => t)) {
+            const { state } = _editorView;
+            let { tr } = state;
+            const mark = state.schema.mark(state.schema.marks.search_highlight);
+            const activeMark = state.schema.mark(state.schema.marks.search_highlight, { selected: true });
+            const res = terms.filter(t => t).map(term => this.findInNode(_editorView, state.doc, term));
+            const { length } = res[0];
             const flattened: TextSelection[] = [];
             res.map(r => r.map(h => flattened.push(h)));
             this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
@@ -574,18 +560,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             }
 
             const lastSel = Math.min(flattened.length - 1, this._searchIndex);
-            flattened.forEach((h: TextSelection, ind: number) => (tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)));
-            flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
+            flattened.forEach((h: TextSelection, ind: number) => {
+                tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark);
+            });
+            flattened[lastSel] && _editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
         }
     };
 
     unhighlightSearchTerms = () => {
-        if (window.screen.width < 600) null;
-        else if (this._editorView && (this._editorView as any).docView) {
-            const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
-            const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
-            const end = this._editorView.state.doc.nodeSize - 2;
-            this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+        if (this._editorView) {
+            const { state } = this._editorView;
+            if (state) {
+                const mark = state.schema.mark(state.schema.marks.search_highlight);
+                const activeMark = state.schema.mark(state.schema.marks.search_highlight, { selected: true });
+                const end = state.doc.nodeSize - 2;
+                this._editorView.dispatch(state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+            }
         }
     };
     adoptAnnotation = (start: number, end: number, mark: Mark) => {
@@ -634,7 +624,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                         float: 'unset',
                     });
                     if (!de.embedKey && ![dropActionType.embed, dropActionType.copy].includes(dropAction ?? dropActionType.move)) {
-                        added = dragData.removeDocument?.(draggedDoc) ? true : false;
+                        added = !!dragData.removeDocument?.(draggedDoc);
                     } else {
                         added = true;
                     }
@@ -646,7 +636,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                             this._inDrop = true;
                             const pos = view.posAtCoords({ left: de.x, top: de.y })?.pos;
                             pos && view.dispatch(view.state.tr.insert(pos, node));
-                            added = pos ? true : false; // pos will be null if you don't drop onto an actual text location
+                            added = !!pos; // pos will be null if you don't drop onto an actual text location
                         } catch (e) {
                             console.log('Drop failed', e);
                             added = false;
@@ -680,29 +670,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 }
                 offset += (context.content as any).content[i].nodeSize;
             }
-            return null;
-        } else {
-            return null;
         }
+        return null;
     }
 
-    //Recursively finds matches within a given node
+    // Recursively finds matches within a given node
     findInNode(pm: EditorView, node: Node, find: string) {
         let ret: TextSelection[] = [];
 
         if (node.isTextblock) {
-            let index = 0,
-                foundAt;
+            let index = 0;
+            let foundAt;
             const ep = this.getNodeEndpoints(pm.state.doc, node);
             const regexp = new RegExp(find, 'i');
             if (regexp) {
-                var blockOffset = 0;
-                for (var i = 0; i < node.childCount; i++) {
-                    var textContent = '';
+                let blockOffset = 0;
+                for (let i = 0; i < node.childCount; i++) {
+                    let textContent = '';
                     while (i < node.childCount && node.child(i).type === pm.state.schema.nodes.text) {
                         textContent += node.child(i).textContent;
                         i++;
                     }
+                    // eslint-disable-next-line no-cond-assign
                     while (ep && (foundAt = textContent.slice(index).search(regexp)) > -1) {
                         const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + 1), pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + find.length + 1));
                         ret.push(sel);
@@ -713,14 +702,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 }
             }
         } else {
-            node.content.forEach((child, i) => (ret = ret.concat(this.findInNode(pm, child, find))));
+            node.content.forEach(child => {
+                ret = ret.concat(this.findInNode(pm, child, find));
+            });
         }
         return ret;
     }
 
     updateHighlights = (highlights: string[]) => {
         if (Array.from(highlights).join('') === FormattedTextBox._globalHighlightsCache) return;
-        setTimeout(() => (FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('')));
+        setTimeout(() => {
+            FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('');
+        });
         clearStyleSheetRules(FormattedTextBox._userStyleSheet);
         if (!highlights.includes('Audio Tags')) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, '');
@@ -757,12 +750,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             const hr = Math.round(Date.now() / 1000 / 60 / 60);
             numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
         }
+        // eslint-disable-next-line operator-assignment
         this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx.  so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView)
     };
 
     @observable _showSidebar = false;
     @computed get SidebarShown() {
-        return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false;
+        return !!(this._showSidebar || this.layoutDoc._layout_showSidebar);
     }
 
     @action
@@ -820,7 +814,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     pinToPres = (anchor: Doc) => this._props.pinToPres(anchor, {});
 
     @undoBatch
-    makeTargetToggle = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle);
+    makeTargetToggle = (anchor: Doc) => {
+        anchor.followLinkToggle = !anchor.followLinkToggle;
+    };
 
     @undoBatch
     showTargetTrail = (anchor: Doc) => {
@@ -836,11 +832,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     specificContextMenu = (e: React.MouseEvent): void => {
         const cm = ContextMenu.Instance;
 
-        const editor = this._editorView!;
-        const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
         let target = e.target as any; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
         while (target && !target.dataset?.targethrefs) target = target.parentElement;
-        if (target && !(e.nativeEvent as any).dash) {
+        const editor = this._editorView;
+        if (editor && target && !(e.nativeEvent as any).dash) {
             const hrefs = (target.dataset?.targethrefs as string)
                 ?.trim()
                 .split(' ')
@@ -850,8 +845,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 .replace(Doc.localServerPath(), '')
                 .split('?')[0];
             const deleteMarkups = undoBatch(() => {
-                const sel = editor.state.selection;
-                editor.dispatch(editor.state.tr.removeMark(sel.from, sel.to, editor.state.schema.marks.linkAnchor));
+                const { selection } = editor.state;
+                editor.dispatch(editor.state.tr.removeMark(selection.from, selection.to, editor.state.schema.marks.linkAnchor));
             });
             e.persist();
             anchorDoc &&
@@ -887,7 +882,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             event: undoBatch(() => {
                 this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
                 this.Document.layout_fieldKey = 'layout_meta';
-                setTimeout(() => (this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50), 50);
+                setTimeout(() => {
+                    this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50;
+                }, 50);
             }),
             icon: 'eye',
         });
@@ -926,19 +923,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
         appearanceItems.push({
             description: !this.Document._layout_noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle',
-            event: () => (this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar),
+            event: () => {
+                this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar;
+            },
             icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye',
         });
         appearanceItems.push({
             description: (this.Document._layout_enableAltContentUI ? 'Hide' : 'Show') + ' Alt Content UI',
-            event: () => (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI),
+            event: () => {
+                this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI;
+            },
             icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
         });
         !Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
         !Doc.noviceMode &&
             appearanceItems.push({
                 description: 'Broadcast Message',
-                event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text)),
+                event: () =>
+                    DocServer.GetRefField('rtfProto').then(proto => {
+                        proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text);
+                    }),
                 icon: 'expand-arrows-alt',
             });
 
@@ -960,19 +964,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
         const options = cm.findByDescription('Options...');
         const optionItems = options && 'subitems' in options ? options.subitems : [];
-        optionItems.push({ description: `Toggle auto update from template`, event: () => (this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate']), icon: 'star' });
+        optionItems.push({
+            description: `Toggle auto update from template`,
+            event: () => {
+                this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate'];
+            },
+            icon: 'star',
+        });
         optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
         optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
         this._props.renderDepth &&
             optionItems.push({
                 description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns',
-                event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR),
+                event: () => {
+                    this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR;
+                },
                 icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars',
             });
         !Doc.noviceMode &&
             optionItems.push({
                 description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`,
-                event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight),
+                event: () => {
+                    this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight;
+                },
                 icon: this.Document._layout_autoHeight ? 'lock' : 'unlock',
             });
         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
@@ -980,7 +994,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const helpItems = help && 'subitems' in help ? help.subitems : [];
         helpItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> });
         !help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' });
-        this._downX = this._downY = Number.NaN;
     };
 
     animateRes = (resIndex: number, newText: string) => {
@@ -995,7 +1008,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     askGPT = action(async () => {
         try {
-            let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
+            const res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
             if (!res) {
                 console.error('GPT call failed');
                 this.animateRes(0, 'Something went wrong.');
@@ -1020,8 +1033,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         if (this._editorView && this._recordingDictation) {
             this.stopDictation(true);
             this._break = true;
-            const state = this._editorView.state;
-            const to = state.selection.to;
+            const { state } = this._editorView;
+            const { to } = state.selection;
             const updated = TextSelection.create(state.doc, to, to);
             this._editorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({})));
             if (this._recordingDictation) {
@@ -1067,7 +1080,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     }
                 }
             }
-            const from = this._editorView.state.selection.from;
+            const { from } = this._editorView.state.selection;
             this._break = false;
             const tr = this._editorView.state.tr.insertText(value);
             this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, tr.doc.content.size)).scrollIntoView());
@@ -1076,23 +1089,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     // TODO: nda -- Look at how link anchors are added
     makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string, noPreview?: boolean, addAsAnnotation?: boolean) {
-        const state = this._editorView?.state;
-        if (state) {
+        const { _editorView } = this;
+        if (_editorView) {
+            const { state } = _editorView;
             let selectedText = '';
-            const sel = state.selection;
+            const { selection } = state;
             const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
-            let tr = state.tr.addMark(sel.from, sel.to, splitter);
-            if (sel.from !== sel.to) {
+            let tr = state.tr.addMark(selection.from, selection.to, splitter);
+            if (selection.from !== selection.to) {
                 const anchor =
                     anchorDoc ??
                     Docs.Create.ConfigDocument({
                         //
-                        title: 'text(' + this._editorView?.state.doc.textBetween(sel.from, sel.to) + ')',
+                        title: 'text(' + state.doc.textBetween(selection.from, selection.to) + ')',
                         annotationOn: this.dataDoc,
                     });
                 const href = targetHref ?? Doc.localServerPath(anchor);
                 if (anchor !== anchorDoc && addAsAnnotation) this.addDocument(anchor);
-                tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+                tr.doc.nodesBetween(selection.from, selection.to, (node: any, pos: number /* , parent: any */) => {
                     if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
                         const allAnchors = [{ href, title, anchorId: anchor[Id] }];
                         allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allAnchors ?? []));
@@ -1102,7 +1116,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     }
                 });
                 this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
-                this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
+                this._editorView!.dispatch(tr.removeMark(selection.from, selection.to, splitter));
                 this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
                 anchor.text = selectedText;
                 anchor.text_html = this._selectionHTML ?? selectedText;
@@ -1123,15 +1137,19 @@ 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)));
+        return new Promise<Opt<DocumentView>>(res => {
+            DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv));
+        });
     };
     focus = (textAnchor: Doc, options: FocusViewOptions) => {
         const focusSpeed = options.zoomTime ?? 500;
         const textAnchorId = textAnchor[Id];
+        let start = 0;
         const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
             const nodes: Node[] = [];
             let hadStart = start !== 0;
             frag.forEach((node, index) => {
+                // eslint-disable-next-line no-use-before-define
                 const examinedNode = findAnchorNode(node, editor);
                 if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) {
                     nodes.push(examinedNode.node);
@@ -1163,7 +1181,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, '')) ? { node, start: 0 } : undefined;
         };
 
-        let start = 0;
         this._didScroll = false; // assume we don't need to scroll.  if we do, this will get set to true in handleScrollToSelextion when we dispatch the setSelection below
         if (this._editorView && textAnchorId) {
             const editor = this._editorView;
@@ -1179,13 +1196,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
                 const escAnchorId = textAnchorId[0] >= '0' && textAnchorId[0] <= '9' ? `\\3${textAnchorId[0]} ${textAnchorId.substr(1)}` : textAnchorId;
                 addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' });
-                setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed);
+                setTimeout(() => {
+                    this._focusSpeed = undefined;
+                }, this._focusSpeed);
                 setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
                 return focusSpeed;
-            } else {
-                return this._props.focus(this.Document, options);
             }
+            return this._props.focus(this.Document, options);
         }
+        return undefined;
     };
 
     // if the scroll height has changed and we're in layout_autoHeight mode, then we need to update the textHeight component of the doc.
@@ -1205,7 +1224,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
         this._disposers.layout_autoHeight = reaction(
             () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss] }),
-            (autoHeight, fontSize) => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
+            autoHeight => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
         );
         this._disposers.highlights = reaction(
             () => Array.from(FormattedTextBox._globalHighlights).slice(),
@@ -1214,7 +1233,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         );
         this._disposers.width = reaction(
             () => this._props.PanelWidth(),
-            width => this.tryUpdateScrollHeight()
+            () => this.tryUpdateScrollHeight()
         );
         this._disposers.scrollHeight = reaction(
             () => ({ scrollHeight: this.scrollHeight, layoutAutoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }),
@@ -1348,7 +1367,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         return text;
     };
 
-    handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
+    handlePaste = (view: EditorView, event: Event /* , slice: Slice */): boolean => {
         const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor');
         return !!(pdfAnchorId && this.addPdfReference(pdfAnchorId));
     };
@@ -1381,7 +1400,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         return false;
     };
 
-    isActiveTab(el: Element | null | undefined) {
+    isActiveTab(elIn: Element | null | undefined) {
+        let el = elIn;
         while (el && el !== document.body) {
             if (getComputedStyle(el).display === 'none') return false;
             el = el.parentNode as any;
@@ -1477,9 +1497,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 const tr = tr2.setStoredMarks(storedMarks);
 
                 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);
                 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;
+                const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
+                const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? [];
+                const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark];
+                const { tr } = this._editorView.state;
+                this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))).setStoredMarks(storedMarks));
+                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
             }
         }
         if (selectOnLoad) {
@@ -1526,8 +1555,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     onPointerDown = (e: React.PointerEvent): void => {
         if ((e.nativeEvent as any).handledByInnerReactInstance) {
-            return; //e.stopPropagation();
-        } else (e.nativeEvent as any).handledByInnerReactInstance = true;
+            return; // e.stopPropagation();
+        }
+        (e.nativeEvent as any).handledByInnerReactInstance = true;
 
         if (this.Document.forceActive) e.stopPropagation();
         this.tryUpdateScrollHeight(); // if a doc a fitWidth doc is being viewed in different embedContainer (eg freeform & lightbox), then it will have conflicting heights.  so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
@@ -1553,9 +1583,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         if (this._recordingDictation && !e.ctrlKey && e.button === 0) {
             this.breakupDictation();
         }
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        this._downTime = Date.now();
         FormattedTextBoxComment.textBox = this;
         if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
             if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
@@ -1569,9 +1596,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             e.preventDefault();
         }
     };
-    onSelectEnd = (e: PointerEvent) => {
-        document.removeEventListener('pointerup', this.onSelectEnd);
-    };
+    onSelectEnd = (): void => document.removeEventListener('pointerup', this.onSelectEnd);
     onPointerUp = (e: React.PointerEvent): void => {
         const state = this.EditorView?.state;
         if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
@@ -1607,7 +1632,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     };
     @action
     onFocused = (e: React.FocusEvent): void => {
-        //applyDevTools.applyDevTools(this._editorView);
+        // applyDevTools.applyDevTools(this._editorView);
         this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc);
         e.stopPropagation();
     };
@@ -1618,10 +1643,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             e.stopPropagation();
             return;
         }
-        if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) {
-            this._forceDownNode = undefined;
-            return;
-        }
         if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) {
             // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment.  Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
             const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
@@ -1645,13 +1666,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         if (this._props.rootSelected?.()) {
             // if text box is selected, then it consumes all click events
             (e.nativeEvent as any).handledByInnerReactInstance = true;
-            this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
+            this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, e.shiftKey);
         }
         this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed;
-        this._forceDownNode = (this._editorView!.state.selection as NodeSelection)?.node;
     };
     // this hackiness handles clicking on the list item bullets to do expand/collapse.  the bullets are ::before pseudo elements so there's no real way to hit test against them.
-    hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, downNode: Node | undefined = undefined, selectOrderedList: boolean = false) {
+    hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, selectOrderedList: boolean = false) {
         this._forceUncollapse = false;
         clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
         const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
@@ -1721,6 +1741,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
         if (match) {
             this.dataDoc.title_custom = true;
+            // eslint-disable-next-line prefer-destructuring
             this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc
             this.EditorView?.dispatch(this.EditorView?.state.tr.deleteRange(0, match[1].length + 1));
         }
@@ -1730,31 +1751,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         FormattedTextBox.LiveTextUndo?.end();
         FormattedTextBox.LiveTextUndo = undefined;
 
-        const state = this._editorView!.state;
         // if the text box blurs and none of its contents are focused(), then pass the blur along
         setTimeout(() => !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.());
     };
 
     onKeyDown = (e: React.KeyboardEvent) => {
+        const { _editorView } = this;
+        if (!_editorView) return;
         if ((e.altKey || e.ctrlKey) && e.key === 't') {
-            e.preventDefault();
-            e.stopPropagation();
             this._props.setTitleFocus?.();
+            StopEvent(e);
             return;
         }
-        const state = this._editorView!.state;
+        const { state } = _editorView;
         if (!state.selection.empty && e.key === '%') {
             this._rules!.EnteringStyle = true;
-            e.preventDefault();
-            e.stopPropagation();
+            StopEvent(e);
             return;
         }
 
         if (state.selection.empty || !this._rules!.EnteringStyle) {
             this._rules!.EnteringStyle = false;
         }
-        let stopPropagation = true;
-        for (var i = state.selection.from; i <= state.selection.to; i++) {
+        for (let i = state.selection.from; i <= state.selection.to; i++) {
             const node = state.doc.resolve(i);
             if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== ClientUtils.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.Document))) {
                 e.preventDefault();
@@ -1769,22 +1788,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 return;
             case 'Enter':
                 this.insertTime();
+            // eslint-disable-next-line no-fallthrough
             case 'Tab':
                 e.preventDefault();
                 break;
-            case 'c':
-                this._editorView?.state.selection.empty && (stopPropagation = false);
+            case 'Space':
+            case 'Backspace':
                 break;
             default:
-                if (this._lastTimedMark?.attrs.userid === ClientUtils.CurrentUserEmail) break;
-            case ' ':
-                if (e.code !== 'Space' && e.code !== 'Backspace') {
-                    [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document)) &&
-                        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
+                if ([AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document))) {
+                    const modified = Math.floor(Date.now() / 1000);
+                    const mark = state.selection.$to.marks().find(m => m.type === schema.marks.user_mark && m.attrs.modified === modified);
+                    _editorView.dispatch(state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(mark ?? schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail, modified })));
                 }
                 break;
         }
-        if (stopPropagation) e.stopPropagation();
+        e.stopPropagation();
         this.startUndoTypingBatch();
     };
     ondrop = (e: React.DragEvent) => {
@@ -1804,6 +1823,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
         const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
         if (children && !SnappingManager.IsDragging) {
+            // eslint-disable-next-line no-use-before-define
             const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
             const toNum = (val: string) => Number(val.replace('px', ''));
             const toHgt = (node: Element): number => {
@@ -1815,7 +1835,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             const scrollHeight = this.ProseRef && proseHeight;
             if (this._props.setHeight && !this._props.suppressSetHeight && scrollHeight && !this._props.dontRegisterView) {
                 // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
-                const setScrollHeight = () => (this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
+                const setScrollHeight = () => {
+                    this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight;
+                };
 
                 if (this.Document === this.layoutDoc || this.layoutDoc.resolvedDataDoc) {
                     setScrollHeight();
@@ -1833,7 +1855,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     };
     sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
     sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
-    setSidebarHeight = (height: number) => (this.dataDoc[this.SidebarKey + '_height'] = height);
+    setSidebarHeight = (height: number) => {
+        this.dataDoc[this.SidebarKey + '_height'] = height;
+    };
     sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
     sidebarScreenToLocal = () =>
         this._props
@@ -1851,10 +1875,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                         e,
                         returnFalse,
                         emptyFunction,
-                        action(e => (this._recordingDictation = !this._recordingDictation))
+                        action(() => {
+                            this._recordingDictation = !this._recordingDictation;
+                        })
                     )
                 }>
-                <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon={'microphone'} size="sm" />
+                <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon="microphone" size="sm" />
             </div>
         );
     }
@@ -1883,6 +1909,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             return ComponentTag === CollectionStackingView ? (
                 <SidebarAnnos
                     ref={this._sidebarRef}
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props}
                     Document={this.Document}
                     layoutDoc={this.layoutDoc}
@@ -1902,6 +1929,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             ) : (
                 <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}
                         ref={this._sidebarTagRef as any}
                         setContentView={emptyFunction}
@@ -1924,8 +1952,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                         renderDepth={this._props.renderDepth + 1}
                         setHeight={this.setSidebarHeight}
                         fitContentsToBox={this.fitContentsToBox}
-                        noSidebar={true}
-                        treeViewHideTitle={true}
+                        noSidebar
+                        treeViewHideTitle
                         fieldKey={this.layoutDoc[this.SidebarKey + '_type_collection'] === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`}
                     />
                 </div>
@@ -1963,7 +1991,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 }>
                 <div
                     className="formattedTextBox-alternateButton"
-                    onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => this.cycleAlternateText())}
+                    onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => this.cycleAlternateText())}
                     style={{
                         display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none',
                         background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -2000,7 +2028,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             // prevent default if selected || child is active  but this doc isn't scrollable
             if (
                 !isNaN(height) &&
-                (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
+                (this._scrollRef?.scrollHeight ?? 0) <= Math.ceil((height || this._props.PanelHeight()) / scale) && //
                 (this._props.rootSelected?.() || this.isAnyChildContentActive())
             ) {
                 e.preventDefault();
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index c0cb60c6d..533fefa09 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -50,7 +50,7 @@ export class FormattedTextBoxComment {
     static userMark: Mark;
     static textBox: FormattedTextBox | undefined;
 
-    constructor(view: any) {
+    constructor() {
         if (!FormattedTextBoxComment.tooltip) {
             const tooltip = (FormattedTextBoxComment.tooltip = document.createElement('div'));
             const tooltipText = (FormattedTextBoxComment.tooltipText = document.createElement('div'));
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 8f716ad7a..0f3306bef 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -12,7 +12,7 @@ export const marks: { [index: string]: MarkSpec } = {
         attrs: {
             id: { default: '' },
         },
-        toDOM(node: any) {
+        toDOM() {
             return ['div', { className: 'dummy' }, 0];
         },
     },
@@ -44,7 +44,7 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM(node: any) {
             const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
             const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
-            return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
+            return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /* 'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
         },
     },
     noAutoLinkAnchor: {
@@ -60,7 +60,7 @@ export const marks: { [index: string]: MarkSpec } = {
                 },
             },
         ],
-        toDOM(node: any) {
+        toDOM() {
             return ['span', { 'data-noAutoLink': 'true' }, 0];
         },
     },
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 9c4080154..02ff661e5 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ColorResult } from 'react-color';
 import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
-import { unimplementedFunction } from '../../../Utils';
+import { emptyFunction, unimplementedFunction } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -50,8 +50,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
     public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
-    public Highlight: (color: string) => Opt<Doc> = (color: string) => undefined;
-    public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+    public Highlight: (color: string) => Opt<Doc> = (/* color: string */) => undefined;
+    public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = emptyFunction;
     public Delete: () => void = unimplementedFunction;
     public PinToPres: () => void = unimplementedFunction;
     public MakeTargetToggle: () => void = unimplementedFunction;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 60c6402d4..3fb914423 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,3 +1,5 @@
+/* eslint-disable default-param-last */
+/* eslint-disable no-use-before-define */
 import { action, computed, makeObservable, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { alias, map, serializable } from 'serializr';
@@ -35,7 +37,7 @@ export namespace Field {
      * @returns string representation of the field
      */
     export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean): string {
-        const onDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
+        const isOnDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
         const field = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
         const valFunc = (field: FieldType): string => {
             const res =
@@ -53,7 +55,7 @@ export namespace Field {
                       .trim()
                       .replace(/^new List\((.*)\)$/, '$1');
         };
-        return !Field.IsField(field) ? (key.startsWith('_') ? '=' : '') : (onDelegate ? '=' : '') + valFunc(field);
+        return !Field.IsField(field) ? (key.startsWith('_') ? '=' : '') : (isOnDelegate ? '=' : '') + valFunc(field);
     }
     export function toScriptString(field: FieldType) {
         switch (typeof field) {
@@ -79,7 +81,7 @@ export namespace Field {
         // as a kind of macro to include the content of those documents
         Doc.MyPublishedDocs.forEach(doc => {
             const regexMultilineFlag = 'm';
-            const regex = new RegExp(`^\\^${StrCast(doc.title).replace(/[\(\)]*/g, '')}\\s`, regexMultilineFlag); // need to remove characters that can cause the regular expression to be invalid
+            const regex = new RegExp(`^\\^${StrCast(doc.title).replace(/[()]*/g, '')}\\s`, regexMultilineFlag); // need to remove characters that can cause the regular expression to be invalid
             const sections = (Cast(doc.text, RichTextField, null)?.Text ?? '').split('--DOCDATA--');
             if (script.match(regex)) {
                 script = script.replace(regex, sections[0]) + (sections.length > 1 ? sections[1] : '');
@@ -148,19 +150,24 @@ export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol; im
 // caches the document access permissions for the current user.
 // this recursively updates all protos as well.
 export function updateCachedAcls(doc: Doc) {
-    if (!doc) return;
-
-    const target = (doc as any)?.__fieldTuples ?? doc;
-    const permissions: { [key: string]: symbol } = !target.author || target.author === ClientUtils.CurrentUserEmail ? { 'acl-Me': AclAdmin } : {};
-    Object.keys(target).filter(key => key.startsWith('acl') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl));
-    if (Object.keys(permissions).length || doc[DocAcl]?.length) {
-        runInAction(() => (doc[DocAcl] = permissions));
-    }
+    if (doc) {
+        const target = (doc as any)?.__fieldTuples ?? doc;
+        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);
+        });
+        if (Object.keys(permissions).length || doc[DocAcl]?.length) {
+            runInAction(() => {
+                doc[DocAcl] = permissions;
+            });
+        }
 
-    if (doc.proto instanceof Promise) {
-        doc.proto.then(proto => updateCachedAcls(DocCast(proto)));
-        return doc.proto;
+        if (doc.proto instanceof Promise) {
+            doc.proto.then(proto => updateCachedAcls(DocCast(proto)));
+            return doc.proto;
+        }
     }
+    return undefined;
 }
 
 @scriptingGlobal
@@ -267,9 +274,9 @@ export class Doc extends RefField {
                     return Reflect.getOwnPropertyDescriptor(target, prop);
                 }
                 return {
-                    configurable: true, //TODO Should configurable be true?
+                    configurable: true, // TODO Should configurable be true?
                     enumerable: true,
-                    value: 0, //() => target.__fieldTuples[prop])
+                    value: 0, // () => target.__fieldTuples[prop])
                 };
             },
             deleteProperty: deleteProperty,
@@ -281,7 +288,8 @@ export class Doc extends RefField {
         if (!id || forceSave) {
             DocServer.CreateField(docProxy);
         }
-        return docProxy;
+        // eslint-disable-next-line no-constructor-return
+        return docProxy; // need to return the proxy from the constructor so that all our added fields will get called
     }
 
     [key: string]: FieldResult;
@@ -293,6 +301,7 @@ export class Doc extends RefField {
     private set __fieldTuples(value) {
         // called by deserializer to set all fields in one shot
         this[FieldTuples] = value;
+        // eslint-disable-next-line no-restricted-syntax
         for (const key in value) {
             const field = value[key];
             field !== undefined && (this[FieldKeys][key] = true);
@@ -345,7 +354,7 @@ export class Doc extends RefField {
             let renderFieldKey: any;
             const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layout_fieldKey, 'layout')];
             if (typeof layoutField === 'string') {
-                renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0]; //layoutField.split("'")[1];
+                [renderFieldKey] = layoutField.split("fieldKey={'")[1].split("'"); // layoutField.split("'")[1];
             } else {
                 return Cast(layoutField, Doc, null);
             }
@@ -361,6 +370,7 @@ export class Doc extends RefField {
             for (const key in set) {
                 const fprefix = 'fields.';
                 if (!key.startsWith(fprefix)) {
+                    // eslint-disable-next-line no-continue
                     continue;
                 }
                 const fKey = key.substring(fprefix.length);
@@ -380,6 +390,7 @@ export class Doc extends RefField {
                 const writeMode = DocServer.getFieldWriteMode(fKey);
                 if (fKey.startsWith('acl') || writeMode !== DocServer.WriteMode.Playground) {
                     delete this[CachedUpdates][fKey];
+                    // eslint-disable-next-line no-await-in-loop
                     await fn();
                 } else {
                     this[CachedUpdates][fKey] = fn;
@@ -390,6 +401,7 @@ export class Doc extends RefField {
         if (unset) {
             for (const key in unset) {
                 if (!key.startsWith('fields.')) {
+                    // eslint-disable-next-line no-continue
                     continue;
                 }
                 const fKey = key.substring(7);
@@ -400,6 +412,7 @@ export class Doc extends RefField {
                 };
                 if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
                     delete this[CachedUpdates][fKey];
+                    // eslint-disable-next-line no-await-in-loop
                     await fn();
                 } else {
                     this[CachedUpdates][fKey] = fn;
@@ -475,8 +488,8 @@ export namespace Doc {
     // 2) if the data doc has the field, then it's written there.
     // 3) if neither already has the field, then 'defaultProto' determines whether to write it to the data doc (or the embedding)
     //
-    export async function SetInPlace(doc: Doc, key: string, value: FieldType | undefined, defaultProto: boolean) {
-        if (key.startsWith('_')) key = key.substring(1);
+    export async function SetInPlace(doc: Doc, keyIn: string, value: FieldType | undefined, defaultProto: boolean) {
+        const key = keyIn.startsWith('_') ? keyIn.substring(1) : keyIn;
         const hasProto = doc[DocData] !== doc ? doc[DocData] : undefined;
         const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
         const onProto = hasProto && Object.getOwnPropertyNames(hasProto).indexOf(key) !== -1;
@@ -561,7 +574,7 @@ export namespace Doc {
      * @returns true if successful, false otherwise.
      */
     export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, ignoreProto = false) {
-        const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
+        const key = fieldKey || Doc.LayoutFieldKey(listDoc);
         const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
         if (list) {
             const ind = list.indexOf(doc);
@@ -578,7 +591,7 @@ export namespace Doc {
      * @returns true if successful, false otherwise.
      */
     export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean, ignoreProto?: boolean) {
-        const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
+        const key = fieldKey || Doc.LayoutFieldKey(listDoc);
         const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
         if (list) {
             if (!allowDuplicates) {
@@ -595,6 +608,7 @@ export namespace Doc {
                     if (reversed) list.splice(0, 0, doc);
                     else list.push(doc);
                 } else {
+                    // eslint-disable-next-line no-lonely-if
                     if (reversed) list.splice(before ? list.length - ind + 1 : list.length - ind, 0, doc);
                     else list.splice(before ? ind : ind + 1, 0, doc);
                 }
@@ -662,7 +676,9 @@ export namespace Doc {
         await Promise.all(
             Object.keys(doc).map(async key => {
                 if (filter.includes(key)) return;
-                const assignKey = (val: any) => (copy[key] = val);
+                const assignKey = (val: any) => {
+                    copy[key] = val;
+                };
                 const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
                 const field = ProxyField.WithoutProxy(() => doc[key]);
                 const copyObjectField = async (field: ObjectField) => {
@@ -714,7 +730,8 @@ export namespace Doc {
                 } else if (field instanceof ObjectField) {
                     await copyObjectField(field);
                 } else if (field instanceof Promise) {
-                    debugger; //This shouldn't happen...
+                    // eslint-disable-next-line no-debugger
+                    debugger; // This shouldn't happen...
                 } else {
                     assignKey(field);
                 }
@@ -741,7 +758,7 @@ export namespace Doc {
         visited.add(clone);
         Object.keys(clone)
             .filter(key => key !== 'cloneOf')
-            .map(key => {
+            .forEach(key => {
                 const docAtKey = DocCast(clone[key]);
                 if (docAtKey && !Doc.IsSystem(docAtKey)) {
                     if (!Array.from(cloneMap.values()).includes(docAtKey)) {
@@ -763,13 +780,13 @@ export namespace Doc {
         const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.embedContainer ? [DocCast(doc.embedContainer)] : [], cloneLinks, cloneTemplates);
         const repaired = new Set<Doc>();
         const linkedDocs = Array.from(linkMap.values());
-        linkedDocs.map(link => Doc.AddLink?.(link, true));
-        rtfMap.map(({ copy, key, field }) => {
-            const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
+        linkedDocs.forEach(link => Doc.AddLink?.(link, true));
+        rtfMap.forEach(({ copy, key, field }) => {
+            const replacer = (match: any, attr: string, id: string /* , offset: any, string: any */) => {
                 const mapped = cloneMap.get(id);
                 return attr + '"' + (mapped ? mapped[Id] : id) + '"';
             };
-            const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => {
+            const replacer2 = (match: any, href: string, id: string /* , offset: any, string: any */) => {
                 const mapped = cloneMap.get(id);
                 return href + (mapped ? mapped[Id] : id);
             };
@@ -778,7 +795,7 @@ 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.map(clone => Doc.repairClone(clone, cloneMap, cloneTemplates, repaired));
+        clonedDocs.forEach(clone => Doc.repairClone(clone, cloneMap, cloneTemplates, repaired));
         return { clone: copy, map: cloneMap, linkMap };
     }
 
@@ -809,6 +826,7 @@ export namespace Doc {
             if (templateLayoutDoc.resolvedDataDoc === targetDoc[DocData]) {
                 expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
             } else {
+                // eslint-disable-next-line no-param-reassign
                 templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
                 if (!targetDoc[expandedLayoutFieldKey]) {
                     _pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
@@ -879,6 +897,7 @@ export namespace Doc {
                         }
                     }
                 } else if (cfield instanceof ComputedField) {
+                    /* empty */
                 } else if (field instanceof ObjectField) {
                     if (field instanceof Doc) {
                         Doc.FindReferences(field, references, system);
@@ -895,7 +914,8 @@ export namespace Doc {
                         Doc.FindReferences(field.value, references, system);
                     }
                 } else if (field instanceof Promise) {
-                    debugger; //This shouldn't happend...
+                    // eslint-disable-next-line no-debugger
+                    debugger; // This shouldn't happend...
                 }
             }
         });
@@ -925,7 +945,8 @@ export namespace Doc {
                                 ? new ProxyField(Doc.MakeCopy(doc[key] as any)) // copy the expanded render template
                                 : ObjectField.MakeCopy(field);
                     } else if (field instanceof Promise) {
-                        debugger; //This shouldn't happend...
+                        // eslint-disable-next-line no-debugger
+                        debugger; // This shouldn't happend...
                     } else {
                         copy[key] = field;
                     }
@@ -955,7 +976,9 @@ export namespace Doc {
             delegate.author = ClientUtils.CurrentUserEmail;
             Object.keys(doc)
                 .filter(key => key.startsWith('acl'))
-                .forEach(key => (delegate[key] = doc[key]));
+                .forEach(key => {
+                    delegate[key] = doc[key];
+                });
             title && (delegate.title = title);
             delegate[Initializing] = false;
             if (!Doc.IsSystem(doc)) Doc.AddEmbedding(doc, delegate);
@@ -968,7 +991,7 @@ export namespace Doc {
     // (ie, the 'data' doc), and then creates another delegate of that (ie, the 'layout' doc).
     // This is appropriate if you're trying to create a document that behaves like all
     // regularly created documents (e.g, text docs, pdfs, etc which all have data/layout docs)
-    export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string) {
+    export function MakeDelegateWithProto(doc: Doc /* , id?: string, title?: string */) {
         const ndoc = Doc.ApplyTemplate(doc);
         if (ndoc) {
             Doc.GetProto(ndoc).isDataDoc = true;
@@ -1017,7 +1040,6 @@ export namespace Doc {
         !keepFieldKey && (templateField.title = metadataFieldKey);
 
         const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)];
-        const templateCaptionValue = templateField.caption;
         // move any data that the template field had been rendering over to the template doc so that things will still be rendered
         // when the template field is adjusted to point to the new metadatafield key.
         // note 1: if the template field contained a list of documents, each of those documents will be converted to templates as well.
@@ -1103,7 +1125,9 @@ export namespace Doc {
         return manager._searchQuery;
     }
     export function SetSearchQuery(query: string) {
-        runInAction(() => (manager._searchQuery = query));
+        runInAction(() => {
+            manager._searchQuery = query;
+        });
     }
     export function UserDoc(): Doc {
         return manager._user_doc;
@@ -1115,12 +1139,13 @@ export namespace Doc {
         return Cast(Doc.UserDoc().myLinkDatabase, Doc, null);
     }
     export function SetUserDoc(doc: Doc) {
+        // eslint-disable-next-line no-return-assign
         return (manager._user_doc = doc);
     }
 
-    const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) {
-        return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined;
-    });
+    const isSearchMatchCache = computedFn((doc: Doc) =>
+        (brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
+        brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined)); // prettier-ignore
     export function IsSearchMatch(doc: Doc) {
         return isSearchMatchCache(doc);
     }
@@ -1166,10 +1191,14 @@ export namespace Doc {
         return BrushDoc(doc, true);
     }
     export function UnBrushAllDocs() {
-        Array.from(brushManager.BrushedDoc).forEach(action(doc => (doc[Brushed] = false)));
+        Array.from(brushManager.BrushedDoc).forEach(
+            action(doc => {
+                doc[Brushed] = false;
+            })
+        );
     }
 
-    let UnhighlightWatchers: (() => void)[] = [];
+    const UnhighlightWatchers: (() => void)[] = [];
     export let UnhighlightTimer: any;
     export function AddUnHighlightWatcher(watcher: () => void) {
         if (UnhighlightTimer) {
@@ -1184,9 +1213,9 @@ export namespace Doc {
         highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
         document.removeEventListener('pointerdown', linkFollowUnhighlight);
     }
-    export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presentation_effect?: Doc) {
+    export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presentationEffect?: Doc) {
         linkFollowUnhighlight();
-        (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presentation_effect));
+        (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presentationEffect));
         document.removeEventListener('pointerdown', linkFollowUnhighlight);
         document.addEventListener('pointerdown', linkFollowUnhighlight);
         if (UnhighlightTimer) clearTimeout(UnhighlightTimer);
@@ -1196,16 +1225,16 @@ export namespace Doc {
         }, 5000);
     }
 
-    export var highlightedDocs = new ObservableSet<Doc>();
+    export const highlightedDocs = new ObservableSet<Doc>();
     export function IsHighlighted(doc: Doc) {
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate || doc.opacity === 0) return false;
         return doc[Highlight] || doc[DocData][Highlight];
     }
-    export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presentation_effect?: Doc) {
+    export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presentationEffect?: Doc) {
         runInAction(() => {
             highlightedDocs.add(doc);
             doc[Highlight] = true;
-            doc[Animation] = presentation_effect;
+            doc[Animation] = presentationEffect;
             if (dataAndDisplayDocs && !doc.resolvedDataDoc) {
                 // if doc is a layout template then we don't want to highlight the proto since that will be the entire template, not just the specific layout field
                 highlightedDocs.add(doc[DocData]);
@@ -1294,6 +1323,7 @@ export namespace Doc {
                 const fields = childFilters[i].split(FilterSep); // split key:value:modifier
                 if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) {
                     if (fields[2] === modifiers && modifiers && fields[1] === value.toString()) {
+                        // eslint-disable-next-line no-param-reassign
                         if (toggle) modifiers = 'remove';
                         else return;
                     }
@@ -1318,9 +1348,12 @@ export namespace Doc {
                 return [Number(childFiltersByRanges[i + 1]), Number(childFiltersByRanges[i + 2])];
             }
         }
+        return undefined;
     }
     export function assignDocToField(doc: Doc, field: string, id: string) {
-        DocServer.GetRefField(id).then(layout => layout instanceof Doc && (doc[field] = layout));
+        DocServer.GetRefField(id).then(layout => {
+            layout instanceof Doc && (doc[field] = layout);
+        });
         return id;
     }
 
@@ -1395,6 +1428,7 @@ export namespace Doc {
             case DocumentType.EQUATION: return 'calculator';
             case DocumentType.SIMULATION: return 'rocket';
             case DocumentType.CONFIG: return 'folder-closed';
+            default:
         }
         return 'question';
     }
@@ -1413,6 +1447,7 @@ export namespace Doc {
             const response = await fetch(upload, { method: 'POST', body: formData });
             const json = await response.json();
             if (json !== 'error') {
+                // eslint-disable-next-line @typescript-eslint/no-unused-vars
                 const docs = await DocServer.GetRefFields(json.docids as string[]);
                 const doc = DocCast(await DocServer.GetRefField(json.id));
                 const links = await DocServer.GetRefFields(json.linkids as string[]);
@@ -1478,6 +1513,7 @@ export namespace Doc {
          */
         export function FromJson({ data, title, appendToExisting, excludeEmptyObjects }: JsonConversionOpts): Opt<Doc> {
             if (excludeEmptyObjects === undefined) {
+                // eslint-disable-next-line no-param-reassign
                 excludeEmptyObjects = true;
             }
             if (data === undefined || data === null || ![...primitives, 'object'].includes(typeof data)) {
@@ -1517,12 +1553,12 @@ export namespace Doc {
             if (hasEntries || !excludeEmptyObjects) {
                 const resolved = target ?? new Doc();
                 if (hasEntries) {
-                    let result: Opt<FieldType>;
-                    Object.keys(object).map(key => {
+                    Object.keys(object).forEach(key => {
                         // if excludeEmptyObjects is true, any qualifying conversions from toField will
                         // be undefined, and thus the results that would have
                         // otherwise been empty (List or Doc)s will just not be written
-                        if ((result = toField(object[key], excludeEmptyObjects, key))) {
+                        const result = toField(object[key], excludeEmptyObjects, key);
+                        if (result) {
                             resolved[key] = result;
                         }
                     });
@@ -1530,6 +1566,7 @@ export namespace Doc {
                 title && (resolved.title = title);
                 return resolved;
             }
+            return undefined;
         };
 
         /**
@@ -1545,10 +1582,13 @@ export namespace Doc {
             // if excludeEmptyObjects is true, any qualifying conversions from toField will
             // be undefined, and thus the results that would have
             // otherwise been empty (List or Doc)s will just not be written
-            list.map(item => (result = toField(item, excludeEmptyObjects)) && target.push(result));
+            list.forEach(item => {
+                (result = toField(item, excludeEmptyObjects)) && target.push(result);
+            });
             if (target.length || !excludeEmptyObjects) {
                 return target;
             }
+            return undefined;
         };
 
         const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<FieldType> => {
@@ -1569,58 +1609,76 @@ export namespace Doc {
 export function IdToDoc(id: string) {
     return DocCast(DocServer.GetCachedRefField(id));
 }
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function idToDoc(id: string): any {
     return IdToDoc(id);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function renameEmbedding(doc: any) {
     return StrCast(doc[DocData].title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function getProto(doc: any) {
     return Doc.GetProto(doc);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function getDocTemplate(doc?: any) {
     return Doc.getDocTemplate(doc);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function getEmbedding(doc: any) {
     return Doc.MakeEmbedding(doc);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) {
     return doc.isTemplateDoc ? Doc.MakeDelegateWithProto(doc) : Doc.MakeCopy(doc, copyProto);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function copyField(field: any) {
     return Field.Copy(field);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function docList(field: any) {
     return DocListCast(field);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added: Doc) {
     return Doc.AddDocToList(doc, field, added);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) {
     return Doc.SetInPlace(doc, field, value, false);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) {
     return Doc.AreProtosEqual(doc1, doc2);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) {
     return Doc.assignDocToField(doc, field, id);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function docCastAsync(doc: FieldResult): any {
     return Cast(doc, Doc);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function activePresentationItem() {
     const curPres = Doc.ActivePresentation;
     return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: 'match' | 'check' | 'x' | 'remove') {
     Doc.setDocFilter(container, key, value, modifiers);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) {
     Doc.setDocRangeFilter(container, key, range);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function toJavascriptString(str: string) {
     return Field.toJavascriptString(str as FieldType);
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function RtfField() {
     return RichTextField.RTFfield();
 });
diff --git a/src/fields/util.ts b/src/fields/util.ts
index f87c200c1..5ed10cf05 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -380,7 +380,7 @@ export function deleteProperty(target: any, prop: string | number | symbol) {
 //
 export function containedFieldChangedHandler(container: List<FieldType> | Doc, prop: string | number, liveContainedField: ObjectField) {
     let lastValue: FieldResult = liveContainedField instanceof ObjectField ? ObjectField.MakeCopy(liveContainedField) : liveContainedField;
-    return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: FieldType[] | undefined; length: number | undefined; hint?: any }, dummyServerOp?: any) => {
+    return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: FieldType[] | undefined; length: number | undefined; hint?: any } /* , dummyServerOp?: any */) => {
         const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: FieldType) => SerializationHelper.Serialize(item)) });
         // prettier-ignore
         const serverOp = diff?.op === '$addToSet'
diff --git a/webpack.config.js b/webpack.config.js
index 6ddd68d97..58df9a57d 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,7 +3,7 @@ const path = require('path');
 const webpack = require('webpack');
 const HtmlWebpackPlugin = require('html-webpack-plugin');
 // eslint-disable-next-line import/no-extraneous-dependencies
-//const ESLintPlugin = require('eslint-webpack-plugin');
+// const ESLintPlugin = require('eslint-webpack-plugin');
 // const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
 const { parsed } = require('dotenv').config();
 
-- 
cgit v1.2.3-70-g09d2


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

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

(limited to 'src/client/views/pdf/AnchorMenu.tsx')

diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index 855f48f7e..5269f763b 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -1,14 +1,14 @@
-import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Opt } from "../../fields/Doc";
-import { Networking } from "../Network";
-import { ScriptingGlobals } from "../util/ScriptingGlobals";
-import { MainViewModal } from "../views/MainViewModal";
-import "./GoogleAuthenticationManager.scss";
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Opt } from '../../fields/Doc';
+import { Networking } from '../Network';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { MainViewModal } from '../views/MainViewModal';
+import './GoogleAuthenticationManager.scss';
 
-const AuthenticationUrl = "https://accounts.google.com/o/oauth2/v2/auth";
-const prompt = "Paste authorization code here...";
+const AuthenticationUrl = 'https://accounts.google.com/o/oauth2/v2/auth';
+const prompt = 'Paste authorization code here...';
 
 @observer
 export class GoogleAuthenticationManager extends React.Component<{}> {
@@ -23,11 +23,11 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
     private disposer: Opt<IReactionDisposer>;
 
     private set isOpen(value: boolean) {
-        runInAction(() => this.openState = value);
+        runInAction(() => (this.openState = value));
     }
 
     private set shouldShowPasteTarget(value: boolean) {
-        runInAction(() => this.showPasteTargetState = value);
+        runInAction(() => (this.showPasteTargetState = value));
     }
 
     public cancel() {
@@ -35,7 +35,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
     }
 
     public fetchOrGenerateAccessToken = async (displayIfFound = false) => {
-        let response: any = await Networking.FetchFromServer("/readGoogleAccessToken");
+        let response: any = await Networking.FetchFromServer('/readGoogleAccessToken');
         // if this is an authentication url, activate the UI to register the new access token
         if (new RegExp(AuthenticationUrl).test(response)) {
             this.isOpen = true;
@@ -47,7 +47,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
                     async authenticationCode => {
                         if (authenticationCode && /\d{1}\/[\w-]{55}/.test(authenticationCode)) {
                             this.disposer?.();
-                            const response = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode });
+                            const response = await Networking.PostToServer('/writeGoogleAccessToken', { authenticationCode });
                             runInAction(() => {
                                 this.success = true;
                                 this.credentials = response;
@@ -71,7 +71,7 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
             this.isOpen = true;
         }
         return response.access_token;
-    }
+    };
 
     resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => {
         if (!visibleForMS && !fadesOutInMS) {
@@ -89,14 +89,20 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
         this.displayLauncher = false;
         this.shouldShowPasteTarget = false;
         if (visibleForMS > 0 && fadesOutInMS > 0) {
-            setTimeout(action(() => {
-                this.isOpen = false;
-                setTimeout(action(() => {
-                    this.success = undefined;
-                    this.displayLauncher = true;
-                    this.credentials = undefined;
-                }), fadesOutInMS);
-            }), visibleForMS);
+            setTimeout(
+                action(() => {
+                    this.isOpen = false;
+                    setTimeout(
+                        action(() => {
+                            this.success = undefined;
+                            this.displayLauncher = true;
+                            this.credentials = undefined;
+                        }),
+                        fadesOutInMS
+                    );
+                }),
+                visibleForMS
+            );
         }
     });
 
@@ -108,61 +114,44 @@ export class GoogleAuthenticationManager extends React.Component<{}> {
     private get renderPrompt() {
         return (
             <div className={'authorize-container'}>
-
-                {this.displayLauncher ? <button
-                    className={"dispatch"}
-                    onClick={() => {
-                        window.open(this.authenticationLink);
-                        setTimeout(() => this.shouldShowPasteTarget = true, 500);
-                    }}
-                    style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}
-                >Authorize a Google account...</button> : (null)}
-                {this.showPasteTargetState ? <input
-                    className={'paste-target'}
-                    onChange={action(e => this.authenticationCode = e.currentTarget.value)}
-                    placeholder={prompt}
-                /> : (null)}
-                {this.credentials ?
+                {this.displayLauncher ? (
+                    <button
+                        className={'dispatch'}
+                        onClick={() => {
+                            window.open(this.authenticationLink);
+                            setTimeout(() => (this.shouldShowPasteTarget = true), 500);
+                        }}
+                        style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}>
+                        Authorize a Google account...
+                    </button>
+                ) : null}
+                {this.showPasteTargetState ? <input className={'paste-target'} onChange={action(e => (this.authenticationCode = e.currentTarget.value))} placeholder={prompt} /> : null}
+                {this.credentials ? (
                     <>
-                        <img
-                            className={'avatar'}
-                            src={this.credentials.userInfo.picture}
-                        />
-                        <span
-                            className={'welcome'}
-                        >Welcome to Dash, {this.credentials.userInfo.name}
-                        </span>
+                        <img className={'avatar'} src={this.credentials.userInfo.picture} />
+                        <span className={'welcome'}>Welcome to Dash, {this.credentials.userInfo.name}</span>
                         <div
                             className={'disconnect'}
                             onClick={async () => {
-                                await Networking.FetchFromServer("/revokeGoogleAccessToken");
+                                await Networking.FetchFromServer('/revokeGoogleAccessToken');
                                 this.resetState(0, 0);
-                            }}
-                        >Disconnect Account</div>
-                    </> : (null)}
+                            }}>
+                            Disconnect Account
+                        </div>
+                    </>
+                ) : null}
             </div>
         );
     }
 
     private get dialogueBoxStyle() {
-        const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
-        return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 };
+        const borderColor = this.success === undefined ? 'black' : this.success ? 'green' : 'red';
+        return { borderColor, transition: '0.2s borderColor ease', zIndex: 1002 };
     }
 
     render() {
-        return (
-            <MainViewModal
-                isDisplayed={this.openState}
-                interactive={true}
-                contents={this.renderPrompt}
-                // overlayDisplayedOpacity={0.9}
-                dialogueBoxStyle={this.dialogueBoxStyle}
-                overlayStyle={{ zIndex: 1001 }}
-                closeOnExternalClick={action(() => this.isOpen = false)}
-            />
-        );
+        return <MainViewModal isDisplayed={this.openState} interactive={true} contents={this.renderPrompt} dialogueBoxStyle={this.dialogueBoxStyle} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={action(() => (this.isOpen = false))} />;
     }
-
 }
 
-ScriptingGlobals.add("GoogleAuthenticationManager", GoogleAuthenticationManager);
\ No newline at end of file
+ScriptingGlobals.add('GoogleAuthenticationManager', GoogleAuthenticationManager);
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 07a2708ec..1eb977813 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -291,7 +291,7 @@ export namespace GooglePhotos {
                 response.results.map(
                     item =>
                         new Promise<string>(resolve => {
-                            Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl));
+                            Query.GetImage(item.mediaItem.id).then(itm => resolve(itm.baseUrl));
                         })
                 )
             );
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f827ea81c..cf397e85a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,9 +1,13 @@
+/* eslint-disable prefer-destructuring */
+/* eslint-disable default-param-last */
+/* eslint-disable no-use-before-define */
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { saveAs } from 'file-saver';
 import * as JSZip from 'jszip';
 import { action, reaction, runInAction } from 'mobx';
 import { basename } from 'path';
 import { ClientUtils, OmitKeys } from '../../ClientUtils';
+// eslint-disable-next-line import/extensions
 import * as JSZipUtils from '../../JSZipUtils';
 import { decycle } from '../../decycler/decycler';
 import { DateField } from '../../fields/DateField';
@@ -81,6 +85,7 @@ export enum FInfoFieldType {
     string = 'string',
     boolean = 'boolean',
     number = 'number',
+    // eslint-disable-next-line @typescript-eslint/no-shadow
     Doc = 'Doc',
     enumeration = 'enum',
     date = 'date',
@@ -1360,9 +1365,9 @@ export namespace DocUtils {
         if (key === LinkedTo) {
             // links are not a field value, so handled here.  value is an expression of form ([field=]idToDoc("..."))
             const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
-            const matchLink = (value: string, anchor: Doc) => {
-                const linkedToExp = (value ?? '').split('=');
-                if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
+            const matchLink = (val: string, anchor: Doc) => {
+                const linkedToExp = (val ?? '').split('=');
+                if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val;
                 return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
             };
             // prettier-ignore
@@ -1493,17 +1498,17 @@ export namespace DocUtils {
         if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link';
         if (target.doc === Doc.UserDoc()) return undefined;
 
-        const makeLink = action((linkDoc: Doc, showPopup?: number[]) => {
-            if (showPopup) {
+        const makeLink = action((linkDoc: Doc, showAt?: number[]) => {
+            if (showAt) {
                 LinkManager.Instance.currentLink = linkDoc;
 
                 TaskCompletionBox.textDisplayed = 'Link Created';
-                TaskCompletionBox.popupX = showPopup[0];
-                TaskCompletionBox.popupY = showPopup[1] - 33;
+                TaskCompletionBox.popupX = showAt[0];
+                TaskCompletionBox.popupY = showAt[1] - 33;
                 TaskCompletionBox.taskCompleted = true;
 
-                LinkDescriptionPopup.Instance.popupX = showPopup[0];
-                LinkDescriptionPopup.Instance.popupY = showPopup[1];
+                LinkDescriptionPopup.Instance.popupX = showAt[0];
+                LinkDescriptionPopup.Instance.popupY = showAt[1];
                 LinkDescriptionPopup.Instance.display = true;
 
                 const rect = document.body.getBoundingClientRect();
@@ -1811,7 +1816,7 @@ export namespace DocUtils {
         batch.end();
         return doc;
     }
-    export function findTemplate(templateName: string, type: string, signature: string) {
+    export function findTemplate(templateName: string, type: string) {
         let docLayoutTemplate: Opt<Doc>;
         const iconViews = DocListCast(Cast(Doc.UserDoc().template_icons, Doc, null)?.data);
         const templBtns = DocListCast(Cast(Doc.UserDoc().template_buttons, Doc, null)?.data);
@@ -1841,7 +1846,7 @@ export namespace DocUtils {
         const templateName = templateSignature.replace(/\(.*\)/, '');
         doc.layout_fieldKey = 'layout_' + (templateSignature || (docLayoutTemplate?.title ?? ''));
         // eslint-disable-next-line no-param-reassign
-        docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature);
+        docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type));
 
         const customName = 'layout_' + templateSignature;
         const _width = NumCast(doc._width);
@@ -2166,12 +2171,16 @@ export namespace DocUtils {
 
         const docs: { [id: string]: any } = {};
         const links: { [id: string]: any } = {};
-        Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1]));
-        Array.from(linkMap.entries()).forEach(l => (links[l[0]] = l[1]));
+        Array.from(map.entries()).forEach(f => {
+            docs[f[0]] = f[1];
+        });
+        Array.from(linkMap.entries()).forEach(l => {
+            links[l[0]] = l[1];
+        });
         const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer));
 
         const zip = new JSZip();
-        var count = 0;
+        let count = 0;
         const promArr = Array.from(proms)
             .filter(url => url?.startsWith('/files'))
             .map(url => url.replace('/', '')); // window.location.origin));
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 46aa4d238..8e3936d34 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -54,7 +54,6 @@ export class CalendarManager extends ObservableReactComponent<{}> {
     @observable private targetDoc: Doc | undefined = undefined; // the target document
     @observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the target doc
     @observable private dialogueBoxOpacity = 1; // for the modal
-    @observable private overlayOpacity = 0.4; // for the modal
 
     @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
 
@@ -205,14 +204,12 @@ export class CalendarManager extends ObservableReactComponent<{}> {
                     if (docs.length) {
                         docs.forEach(doc => doc && Doc.BrushDoc(doc));
                         this.dialogueBoxOpacity = 0.1;
-                        this.overlayOpacity = 0.1;
                     }
                 })}
                 onPointerLeave={action(() => {
                     if (docs.length) {
                         docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
                         this.dialogueBoxOpacity = 1;
-                        this.overlayOpacity = 0.4;
                     }
                 })}>
                 {contents}
@@ -352,6 +349,6 @@ export class CalendarManager extends ObservableReactComponent<{}> {
     }
 
     render() {
-        return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+        return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.close} />;
     }
 }
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 2939ba581..a03c80f2a 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -13,18 +15,22 @@ import { SelectionManager } from './SelectionManager';
 
 @observer
 export class CaptureManager extends React.Component<{}> {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: CaptureManager;
     static _settingsStyle = addStyleSheet();
     @observable _document: any = undefined;
     @observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not.
 
+    // eslint-disable-next-line react/sort-comp
     constructor(props: {}) {
         super(props);
         makeObservable(this);
         CaptureManager.Instance = this;
     }
 
-    public close = action(() => (this.isOpen = false));
+    public close = action(() => {
+        this.isOpen = false;
+    });
     public open = action((doc: Doc) => {
         this.isOpen = true;
         this._document = doc;
@@ -99,15 +105,15 @@ export class CaptureManager extends React.Component<{}> {
             <div className="capture-interface">
                 <div className="capture-t1">
                     <div className="recordButtonOutline" style={{}}>
-                        <div className="recordButtonInner" style={{}}></div>
+                        <div className="recordButtonInner" style={{}} />
                     </div>
                     Conversation Capture
                 </div>
-                <div className="capture-t2"></div>
+                <div className="capture-t2" />
                 {this.visibilityContent}
                 {this.linksContent}
                 <div className="close-button" onClick={this.close}>
-                    <FontAwesomeIcon icon={'times'} color="black" size={'lg'} />
+                    <FontAwesomeIcon icon="times" color="black" size="lg" />
                 </div>
                 {this.closeButtons}
             </div>
@@ -119,11 +125,10 @@ export class CaptureManager extends React.Component<{}> {
             <MainViewModal
                 contents={this.captureInterface}
                 isDisplayed={this.isOpen}
-                interactive={true}
+                interactive
                 closeOnExternalClick={this.close}
                 dialogueBoxStyle={{ width: '500px', height: '350px', border: 'none', background: 'whitesmoke' }}
                 overlayStyle={{ background: 'black' }}
-                overlayDisplayedOpacity={0.6}
             />
         );
     }
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index acbd0c0b9..65dce34a5 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -67,7 +67,7 @@ export class CurrentUserUtils {
 
     // initializes experimental advanced template views - slideView,  headerView
     static setupUserDocumentCreatorButtons(doc: Doc, userDocTemplates: Opt<Doc>) {
-        const userTemplates = DocListCast(userDocTemplates?.data).filter(doc => !Doc.IsSystem(doc));
+        const userTemplates = DocListCast(userDocTemplates?.data).filter(fdoc => !Doc.IsSystem(fdoc));
         const reqdOpts:DocumentOptions = {
             title: "User Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true, hidden: false,
             _dragOnlyWithinContainer: true, layout_hideContextMenu: true, isSystem: true, _forceActive: true,
@@ -87,7 +87,7 @@ export class CurrentUserUtils {
             { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
-            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
+            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === opts.opts.title): undefined;
             return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, allOpts),allOpts);
         });
 
@@ -110,7 +110,7 @@ export class CurrentUserUtils {
         const reqdClickList = reqdTempOpts.map(opts => {
             const title = opts.opts.title?.toString();
             const allOpts = {...reqdClickOpts, ...opts.opts};
-            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === title): undefined;
+            const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === title): undefined;
             const script = ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name});
             const scriptDoc = Docs.Create.ScriptingDocument(script, allOpts, title)
             return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(scriptDoc);
@@ -130,7 +130,7 @@ export class CurrentUserUtils {
             { title: "Topic",  backgroundColor: "lightblue", icon: "book-open"  , _layout_showTitle: "title"}];
         const reqdNoteList = [...reqdTempOpts.map(opts => {
             const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true};
-            const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.title === opts.title): undefined;
+            const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(fdoc => fdoc.title === opts.title): undefined;
             return DocUtils.AssignOpts(noteTemp, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts));
         }), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))];
 
@@ -249,7 +249,7 @@ export class CurrentUserUtils {
                         Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true,  _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
                     ], {...opts, title: "Slide View Template"}));
         const plotlyApi = () => {
-            let plotly = Doc.MyPublishedDocs.find(doc => doc.title === "@plotly");
+            let plotly = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@plotly");
             if (!plotly) {
                 plotly = Docs.Create.TextDocument(
                     `await import("https://cdn.plot.ly/plotly-2.27.0.min.js");
@@ -289,7 +289,7 @@ export class CurrentUserUtils {
             return slide; 
         }
         const mermaidsApi = () => {
-            let mermaids = Doc.MyPublishedDocs.find(doc => doc.title === "@mermaids");
+            let mermaids = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@mermaids");
             if (!mermaids) {
                 mermaids = Docs.Create.TextDocument(
                 `const mdef = (await import("https://cdn.jsdelivr.net/npm/mermaid@10.8.0/dist/mermaid.esm.min.mjs")).default;
@@ -416,7 +416,7 @@ pie title Minerals in my tap water
     /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
     static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc {
         const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
-            const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
+            const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(fdoc => fdoc.title === reqdOpts.title): undefined;
             const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, 
                 _width: 60, _height: 60, _dragOnlyWithinContainer: true, 
                 btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, isSystem: true,
@@ -460,7 +460,7 @@ pie title Minerals in my tap water
         this.setupLeftSidebarPanel(doc);
         const myLeftSidebarMenu = DocCast(doc[field]);
         const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, toolTip, hidden, scripts, funcs }) => {
-            const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined;
+            const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(fdoc => fdoc.title === title) : undefined;
             const reqdBtnOpts:DocumentOptions = {
                 title, icon, target, toolTip, hidden, btnType: ButtonType.MenuButton, isSystem: true, undoIgnoreFields: new List<string>(['height', 'data_columnHeaders']), dontRegisterView: true,
                 _width: 60, _height: 60,  _dragOnlyWithinContainer: true,
@@ -614,7 +614,7 @@ pie title Minerals in my tap water
     static setupDockedButtons(doc: Doc, field="myDockedBtns") {
         const dockedBtns = DocCast(doc[field]);
         const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:string}) =>
-            DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? 
+            DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(fdoc => fdoc.title === opts.title), opts) ?? 
                             CurrentUserUtils.createToolButton(opts), scripts, funcs);
         
         const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
@@ -780,7 +780,7 @@ pie title Minerals in my tap water
             childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: !params.scripts?.onClick,
             linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc};
         
-        const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
+        const items = (menutBtn?:Doc) => !menutBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menutBtn) );
         const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList;
         const btnDoc = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title), 
                 (opts) => creator(opts, items(menuBtnDoc)), reqdSubMenuOpts, items(menuBtnDoc)), params.scripts, params.funcs);
@@ -802,7 +802,7 @@ pie title Minerals in my tap water
         Doc.UserDoc().workspaceReplayingState = undefined;
         const dockedBtns = DocCast(doc[field]);
         const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:any}) =>
-            DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? 
+            DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(fdoc => fdoc.title === opts.title), opts) ?? 
                             CurrentUserUtils.createToolButton(opts), scripts, funcs);
         
         const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
@@ -964,7 +964,7 @@ pie title Minerals in my tap water
                     case FInfoFieldType.Doc: opts.fieldValues = new List<Doc>(options.values as any); break;
                     default: opts.fieldValues = new List<string>(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType
                 }
-                DocUtils.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts);
+                DocUtils.AssignDocField(infos, pair[0], docOpts => Doc.assign(new Doc(), OmitKeys(docOpts,["values"]).omit), opts);
             }
         });
     }
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 207d3ea0b..08fd80882 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-use-before-define */
 import * as interpreter from 'words-to-numbers';
 // @ts-ignore  bcz: how are you supposed to include these definitions since dom-speech-recognition isn't a module?
 import type {} from '@types/dom-speech-recognition';
@@ -6,7 +7,7 @@ import { Doc, Opt } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
 import { listSpec } from '../../fields/Schema';
-import { Cast, CastCtor, DocCast } from '../../fields/Types';
+import { Cast, CastCtor } from '../../fields/Types';
 import { AudioField, ImageField } from '../../fields/URLField';
 import { DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
@@ -14,6 +15,7 @@ import { DictationOverlay } from '../views/DictationOverlay';
 import { DocumentView, OpenWhere } from '../views/nodes/DocumentView';
 import { SelectionManager } from './SelectionManager';
 import { UndoManager } from './UndoManager';
+import { DocData } from '../../fields/DocSymbols';
 
 /**
  * This namespace provides a singleton instance of a manager that
@@ -64,9 +66,10 @@ export namespace DictationManager {
         export let isListening = false;
         let isManuallyStopped = false;
 
-        let current: string | undefined = undefined;
+        let current: string | undefined;
         let sessionResults: string[] = [];
 
+        // eslint-disable-next-line new-cap
         const recognizer: Opt<SpeechRecognition> = webkitSpeechRecognition ? new webkitSpeechRecognition() : undefined;
 
         export type InterimResultHandler = (results: string) => any;
@@ -87,7 +90,7 @@ export namespace DictationManager {
         let pendingListen: Promise<string> | string | undefined;
 
         export const listen = async (options?: Partial<ListeningOptions>) => {
-            if (pendingListen instanceof Promise) return pendingListen.then(pl => innerListen(options));
+            if (pendingListen instanceof Promise) return pendingListen.then(() => innerListen(options));
             return innerListen(options);
         };
         const innerListen = async (options?: Partial<ListeningOptions>) => {
@@ -150,29 +153,29 @@ export namespace DictationManager {
 
             recognizer.start();
 
-            return new Promise<string>((resolve, reject) => {
+            return new Promise<string>(resolve => {
                 recognizer.onerror = (e: any) => {
                     // e is SpeechRecognitionError but where is that defined?
                     if (!(indefinite && e.error === 'no-speech')) {
                         recognizer.stop();
                         resolve(e);
-                        //reject(e);
                     }
                 };
 
                 recognizer.onresult = (e: SpeechRecognitionEvent) => {
                     current = synthesize(e, intra);
-                    let matchedTerminator: string | undefined;
-                    if (options?.terminators && (matchedTerminator = options.terminators.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false)))) {
+                    const matchedTerminator = options?.terminators?.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false));
+                    if (options?.terminators && matchedTerminator) {
                         current = matchedTerminator;
                         recognizer.abort();
                         return complete();
                     }
                     !isManuallyStopped && handler?.(current);
-                    //isManuallyStopped && complete();
+                    // isManuallyStopped && complete()
+                    return undefined;
                 };
 
-                recognizer.onend = (e: Event) => {
+                recognizer.onend = () => {
                     if (!indefinite || isManuallyStopped) {
                         return complete();
                     }
@@ -182,6 +185,7 @@ export namespace DictationManager {
                         current = undefined;
                     }
                     recognizer.start();
+                    return undefined;
                 };
 
                 const complete = () => {
@@ -202,7 +206,7 @@ export namespace DictationManager {
             });
         };
 
-        export const stop = (salvageSession = true) => {
+        export const stop = (/* salvageSession = true */) => {
             if (!isListening || !recognizer) {
                 return;
             }
@@ -212,7 +216,7 @@ export namespace DictationManager {
         };
 
         const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => {
-            const results = e.results;
+            const { results } = e;
             const transcripts: string[] = [];
             for (let i = 0; i < results.length; i++) {
                 transcripts.push(results.item(i).item(0).transcript.trim());
@@ -233,22 +237,25 @@ export namespace DictationManager {
         export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value);
         export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry);
 
-        export const execute = async (phrase: string) => {
-            return UndoManager.RunInBatch(async () => {
+        export const execute = async (phrase: string) =>
+            UndoManager.RunInBatch(async () => {
                 console.log('PHRASE: ' + phrase);
                 const targets = SelectionManager.Views;
                 if (!targets || !targets.length) {
-                    return;
+                    return undefined;
                 }
 
+                // eslint-disable-next-line no-param-reassign
                 phrase = phrase.toLowerCase();
                 const entry = Independent.get(phrase);
 
                 if (entry) {
                     let success = false;
-                    const restrictTo = entry.restrictTo;
+                    const { restrictTo } = entry;
+                    // eslint-disable-next-line no-restricted-syntax
                     for (const target of targets) {
                         if (!restrictTo || validate(target, restrictTo)) {
+                            // eslint-disable-next-line no-await-in-loop
                             await entry.action(target);
                             success = true;
                         }
@@ -256,16 +263,19 @@ export namespace DictationManager {
                     return success;
                 }
 
-                for (const entry of Dependent) {
-                    const regex = entry.expression;
+                // eslint-disable-next-line no-restricted-syntax
+                for (const depEntry of Dependent) {
+                    const regex = depEntry.expression;
                     const matches = regex.exec(phrase);
                     regex.lastIndex = 0;
                     if (matches !== null) {
                         let success = false;
-                        const restrictTo = entry.restrictTo;
+                        const { restrictTo } = depEntry;
+                        // eslint-disable-next-line no-restricted-syntax
                         for (const target of targets) {
                             if (!restrictTo || validate(target, restrictTo)) {
-                                await entry.action(target, matches);
+                                // eslint-disable-next-line no-await-in-loop
+                                await depEntry.action(target, matches);
                                 success = true;
                             }
                         }
@@ -275,7 +285,6 @@ export namespace DictationManager {
 
                 return false;
             }, 'Execute Command');
-        };
 
         const ConstructorMap = new Map<DocumentType, CastCtor>([
             [DocumentType.COL, listSpec(Doc)],
@@ -294,6 +303,7 @@ export namespace DictationManager {
         };
 
         const validate = (target: DocumentView, types: DocumentType[]) => {
+            // eslint-disable-next-line no-restricted-syntax
             for (const type of types) {
                 if (tryCast(target, type)) {
                     return true;
@@ -318,7 +328,9 @@ export namespace DictationManager {
             [
                 'clear',
                 {
-                    action: (target: DocumentView) => (Doc.GetProto(target.Document).data = new List()),
+                    action: (target: DocumentView) => {
+                        Doc.GetProto(target.Document).data = new List();
+                    },
                     restrictTo: [DocumentType.COL],
                 },
             ],
@@ -328,7 +340,7 @@ export namespace DictationManager {
                 {
                     action: (target: DocumentView) => {
                         const newBox = Docs.Create.TextDocument('', { _width: 400, _height: 200, title: 'My Outline', _layout_autoHeight: true });
-                        const proto = DocCast(newBox.proto);
+                        const proto = newBox[DocData];
                         const prompt = 'Press alt + r to start dictating here...';
                         const head = 3;
                         const anchor = head + prompt.length;
@@ -341,7 +353,7 @@ export namespace DictationManager {
             ],
         ]);
 
-        const Dependent = new Array<DependentEntry>(
+        const Dependent = [
             {
                 expression: /create (\w+) documents of type (image|nested collection)/g,
                 action: (target: DocumentView, matches: RegExpExecArray) => {
@@ -361,6 +373,7 @@ export namespace DictationManager {
                             case 'nested collection':
                                 created = Docs.Create.FreeformDocument([], {});
                                 break;
+                            default:
                         }
                         created && Doc.AddDocToList(dataDoc, fieldKey, created);
                     }
@@ -375,7 +388,7 @@ export namespace DictationManager {
                     mode && (target.Document._type_collection = mode);
                 },
                 restrictTo: [DocumentType.COL],
-            }
-        );
+            },
+        ];
     }
 }
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 894583711..c703c3f98 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -1,4 +1,7 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button, IconButton, Size, Type } from 'browndash-components';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -8,7 +11,6 @@ import { StrCast } from '../../fields/Types';
 import { MainViewModal } from '../views/MainViewModal';
 import { GroupManager, UserOptions } from './GroupManager';
 import './GroupMemberView.scss';
-import { Button, IconButton, Size, Type } from 'browndash-components';
 import { SettingsManager } from './SettingsManager';
 
 interface GroupMemberViewProps {
@@ -38,20 +40,23 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
                         className="group-title"
                         style={{ marginLeft: !hasEditAccess ? '-14%' : 0 }}
                         value={StrCast(this.group.title || this.group.groupName)}
-                        onChange={e => (this.group.title = e.currentTarget.value)}
-                        disabled={!hasEditAccess}></input>
-                    <div className={'memberView-closeButton'}>
-                        <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} />
+                        onChange={e => {
+                            this.group.title = e.currentTarget.value;
+                        }}
+                        disabled={!hasEditAccess}
+                    />
+                    <div className="memberView-closeButton">
+                        <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} />
                     </div>
                     {GroupManager.Instance.hasEditAccess(this.group) ? (
                         <div className="group-buttons">
                             <div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
                                 <Select
                                     className="add-member-dropdown"
-                                    isSearchable={true}
+                                    isSearchable
                                     options={options}
                                     onChange={selectedOption => GroupManager.Instance.addMemberToGroup(this.group, (selectedOption as UserOptions).value)}
-                                    placeholder={'Add members'}
+                                    placeholder="Add members"
                                     value={null}
                                     styles={{
                                         control: () => ({
@@ -80,7 +85,12 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
                             </div>
                         </div>
                     ) : null}
-                    <div className="sort-emails" style={{ paddingTop: hasEditAccess ? 0 : 35 }} onClick={action(() => (this.memberSort = this.memberSort === 'ascending' ? 'descending' : this.memberSort === 'descending' ? 'none' : 'ascending'))}>
+                    <div
+                        className="sort-emails"
+                        style={{ paddingTop: hasEditAccess ? 0 : 35 }}
+                        onClick={action(() => {
+                            this.memberSort = this.memberSort === 'ascending' ? 'descending' : this.memberSort === 'descending' ? 'none' : 'ascending';
+                        })}>
                         Emails {this.memberSort === 'ascending' ? '↑' : this.memberSort === 'descending' ? '↓' : ''} {/* → */}
                     </div>
                 </div>
@@ -90,8 +100,8 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
                         <div className="editing-row" key={member}>
                             <div className="user-email">{member}</div>
                             {hasEditAccess ? (
-                                <div className={'remove-button'} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}>
-                                    <IconButton icon={<FontAwesomeIcon icon={'trash-alt'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} />
+                                <div className="remove-button" onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}>
+                                    <IconButton icon={<FontAwesomeIcon icon="trash-alt" />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} />
                                 </div>
                             ) : null}
                         </div>
@@ -102,6 +112,6 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
     }
 
     render() {
-        return <MainViewModal isDisplayed={true} interactive={true} contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />;
+        return <MainViewModal isDisplayed interactive contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />;
     }
 }
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index b500a5af9..52d0223d5 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,3 +1,9 @@
+/* eslint-disable no-use-before-define */
+/* eslint-disable no-empty */
+/* eslint-disable no-continue */
+/* eslint-disable guard-for-in */
+/* eslint-disable no-restricted-syntax */
+/* eslint-disable no-param-reassign */
 import * as qs from 'query-string';
 import { Doc } from '../../fields/Doc';
 import { OmitKeys, ClientUtils } from '../../ClientUtils';
@@ -32,6 +38,7 @@ export namespace HistoryUtil {
                     case 'doc':
                         onDocUrl(url);
                         break;
+                    default:
                 }
             }
         }
@@ -124,11 +131,11 @@ export namespace HistoryUtil {
                 const val = customParser(pathname, opts, current);
                 if (val === null) {
                     return undefined;
-                } else if (val === undefined) {
+                }
+                if (val === undefined) {
                     return current;
-                } else {
-                    return val;
                 }
+                return val;
             }
             return current;
         };
@@ -142,26 +149,27 @@ export namespace HistoryUtil {
             }
             const queryObj = OmitKeys(state, keys).extract;
             const query: any = {};
-            Object.keys(queryObj).forEach(key => (query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key])));
+            Object.keys(queryObj).forEach(key => {
+                query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key]);
+            });
             const queryString = qs.stringify(query);
             return path + (queryString ? `?${queryString}` : '');
         };
     }
 
     addParser('doc', {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => {
-        if (pathname.length !== 2) return undefined;
-
-        current.initializers = current.initializers || {};
-        const docId = pathname[1];
-        current.docId = docId;
-    });
-    addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => {
-        return `${current}/${state.docId}`;
+        if (pathname.length === 2) {
+            current.initializers = current.initializers || {};
+            const docId = pathname[1];
+            current.docId = docId;
+        }
+        return undefined;
     });
+    addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => `${current}/${state.docId}`);
 
     export function parseUrl(location: Location | URL): ParsedUrl | undefined {
         const pathname = location.pathname.substring(1);
-        const search = location.search;
+        const { search } = location;
         const opts = search.length ? qs.parse(search, { sort: false }) : {};
         const pathnameSplit = pathname.split('/');
 
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
index 58a09b9c9..db1e3d6cd 100644
--- a/src/client/util/Import & Export/ImportMetadataEntry.tsx	
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx	
@@ -1,10 +1,13 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { EditableView } from '../../views/EditableView';
-import { action, computed } from 'mobx';
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable no-use-before-define */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../../fields/Doc';
-import { StrCast, BoolCast } from '../../../fields/Types';
+import { BoolCast, StrCast } from '../../../fields/Types';
+import { EditableView } from '../../views/EditableView';
 
 interface KeyValueProps {
     Document: Doc;
@@ -93,18 +96,27 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
                     alignItems: 'center',
                     alignContent: 'center',
                 }}>
-                <input onChange={e => (this.onDataDoc = e.target.checked)} ref={this.checkRef} style={{ margin: '0 10px 0 15px' }} type="checkbox" title={'Add to Data Document?'} checked={this.onDataDoc} />
-                <div className={'key_container'} style={keyValueStyle}>
-                    <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine={true} />
+                <input
+                    onChange={e => {
+                        this.onDataDoc = e.target.checked;
+                    }}
+                    ref={this.checkRef}
+                    style={{ margin: '0 10px 0 15px' }}
+                    type="checkbox"
+                    title="Add to Data Document?"
+                    checked={this.onDataDoc}
+                />
+                <div className="key_container" style={keyValueStyle}>
+                    <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine />
                 </div>
-                <div className={'value_container'} style={keyValueStyle}>
-                    <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine={true} />
+                <div className="value_container" style={keyValueStyle}>
+                    <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine />
                 </div>
-                <div onClick={() => this.props.remove(this)} title={'Delete Entry'}>
+                <div onClick={() => this.props.remove(this)} title="Delete Entry">
                     <FontAwesomeIcon
-                        icon={'plus'}
-                        color={'red'}
-                        size={'1x'}
+                        icon="plus"
+                        color="red"
+                        size="1x"
                         style={{
                             marginLeft: 15,
                             marginRight: 15,
diff --git a/src/client/util/KeyCodes.ts b/src/client/util/KeyCodes.ts
index de2457a5a..fb21f78ee 100644
--- a/src/client/util/KeyCodes.ts
+++ b/src/client/util/KeyCodes.ts
@@ -133,4 +133,4 @@ export class KeyCodes {
     public static NUM_9: number = 57;
     public static SUBTRACT: number = 189;
     public static ADD: number = 187;
-}
\ No newline at end of file
+}
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 85bada8c9..09262f5ac 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -44,7 +44,7 @@ export class LinkFollower {
 
     public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) {
         const getView = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc));
-        const isAnchor = (sourceDoc: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, sourceDoc) || Doc.AreProtosEqual(anchor.annotationOn as Doc, sourceDoc);
+        const isAnchor = (source: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(anchor.annotationOn as Doc, source);
         const linkDocs = link ? [link] : LinkManager.Links(sourceDoc);
         const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_1 as Doc)); // link docs where 'sourceDoc' is link_anchor_1
         const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_2 as Doc)); // link docs where 'sourceDoc' is link_anchor_2
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index 7638e2ce0..e5e69c5ac 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -1,8 +1,10 @@
 import { action, makeObservable, observable, runInAction } from 'mobx';
 import { Networking } from '../Network';
 import { CurrentUserUtils } from './CurrentUserUtils';
+
 export class PingManager {
     // create static instance and getter for global use
+    // eslint-disable-next-line no-use-before-define
     @observable static _instance: PingManager;
     @observable IsBeating = true;
     static get Instance(): PingManager {
@@ -29,7 +31,9 @@ export class PingManager {
     sendPing = async (): Promise<void> => {
         try {
             const res = await Networking.PostToServer('/ping', { date: new Date() });
-            runInAction(() => (CurrentUserUtils.ServerVersion = res.message));
+            runInAction(() => {
+                CurrentUserUtils.ServerVersion = res.message;
+            });
             !this.IsBeating && this.setIsBeating(true);
         } catch {
             if (this.IsBeating) {
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 35b1579df..05fb849fd 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -6,23 +6,16 @@ import { SettingsManager } from './SettingsManager';
 
 @observer
 export class RTFMarkup extends React.Component<{}> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: RTFMarkup;
     @observable private isOpen = false; // whether the SharingManager modal is open or not
 
-    @action
-    public open = () => (this.isOpen = true);
-
-    @action
-    public close = () => (this.isOpen = false);
-
     constructor(props: {}) {
         super(props);
         makeObservable(this);
         RTFMarkup.Instance = this;
     }
 
-    @observable _stats: { [key: string]: any } | undefined = undefined;
-
     /**
      * @returns the main interface of the SharingManager.
      */
@@ -30,11 +23,11 @@ export class RTFMarkup extends React.Component<{}> {
         return (
             <div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`(@wiki:phrase)`}</b>
+                    <b style={{ fontSize: 'larger' }}>(@wiki:phrase)</b>
                     {` display wikipedia page for entered text (terminate with carriage return)`}
                 </p>
                 <p>
-                    <b style={{ fontSize: 'larger' }}>{`(( any text ))`}</b>
+                    <b style={{ fontSize: 'larger' }}>(( any text ))</b>
                     {` submit text to Chat GPT to have results appended afterward`}
                 </p>
                 <p>
@@ -129,13 +122,23 @@ export class RTFMarkup extends React.Component<{}> {
         );
     }
 
+    @action
+    public open = () => {
+        this.isOpen = true;
+    };
+
+    @action
+    public close = () => {
+        this.isOpen = false;
+    };
+
     render() {
         return (
             <MainViewModal
                 dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, alignContent: 'normal', color: SettingsManager.userColor, padding: '16px' }}
                 contents={this.cheatSheet}
                 isDisplayed={this.isOpen}
-                interactive={true}
+                interactive
                 closeOnExternalClick={this.close}
             />
         );
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index b881f18b4..2c8fdf483 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -14,6 +14,7 @@ export class ReplayMovements {
     private isPlaying: boolean;
 
     // create static instance and getter for global use
+    // eslint-disable-next-line no-use-before-define
     @observable static _instance: ReplayMovements;
     static get Instance(): ReplayMovements {
         return ReplayMovements._instance;
@@ -90,7 +91,7 @@ export class ReplayMovements {
     loadPresentation = (presentation: Presentation) => {
         const { movements } = presentation;
         if (movements === null) {
-            throw '[recordingApi.ts] followMovements() failed: no presentation data';
+            throw new Error('[recordingApi.ts] followMovements() failed: no presentation data');
         }
 
         movements.forEach((movement, i) => {
@@ -106,9 +107,7 @@ export class ReplayMovements {
     // returns undefined if the docView isn't open on the screen
     getCollectionFFView = (doc: Doc) => {
         const isInView = DocumentManager.Instance.getDocumentView(doc);
-        if (isInView) {
-            return isInView.ComponentView as CollectionFreeFormView;
-        }
+        return isInView?.ComponentView as CollectionFreeFormView;
     };
 
     // will open the doc in a tab then return the CollectionFFView that holds it
@@ -136,9 +135,9 @@ export class ReplayMovements {
         if (movements === null) return new Map();
         // generate a set of all unique docIds
         const docIdtoFirstMove = new Map<Doc, Movement>();
-        for (const move of movements) {
+        movements.forEach(move => {
             if (!docIdtoFirstMove.has(move.doc)) docIdtoFirstMove.set(move.doc, move);
-        }
+        });
         return docIdtoFirstMove;
     };
 
@@ -151,10 +150,10 @@ export class ReplayMovements {
         // console.info('playMovements', presentation, timeViewed, docIdtoDoc);
 
         if (presentation.movements === null || presentation.movements.length === 0) {
-            //|| this.playFFView === null) {
-            return new Error('[recordingApi.ts] followMovements() failed: no presentation data');
+            // || this.playFFView === null) {
+            return '[recordingApi.ts] followMovements() failed: no presentation data';
         }
-        if (this.isPlaying) return;
+        if (this.isPlaying) return undefined;
 
         this.isPlaying = true;
         Doc.UserDoc().presentationMode = 'watching';
@@ -170,10 +169,10 @@ export class ReplayMovements {
 
             // for the open tabs, set it to the first move
             const docIdtoFirstMove = this.getFirstMovements(filteredMovements);
-            for (const [doc, firstMove] of docIdtoFirstMove) {
+            Array.from(docIdtoFirstMove).forEach(([doc, firstMove]) => {
                 const colFFView = this.getCollectionFFView(doc);
                 if (colFFView) this.zoomAndPan(firstMove, colFFView);
-            }
+            });
         };
         handleFirstMovements();
 
@@ -197,5 +196,6 @@ export class ReplayMovements {
                 }
             }, timeDiff);
         });
+        return undefined;
     };
 }
diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts
index 87509f2ea..9158f6c0b 100644
--- a/src/client/util/ScriptManager.ts
+++ b/src/client/util/ScriptManager.ts
@@ -7,8 +7,10 @@ import { ScriptingGlobals } from './ScriptingGlobals';
 
 export class ScriptManager {
     static _initialized = false;
+    // eslint-disable-next-line no-use-before-define
     private static _instance: ScriptManager;
     public static get Instance(): ScriptManager {
+        // eslint-disable-next-line no-return-assign
         return this._instance || (this._instance = new this());
     }
     private constructor() {
@@ -58,14 +60,16 @@ export class ScriptManager {
 
         const params = Cast(scriptDoc['data-params'], listSpec('string'), []);
         const paramNames = params.reduce((o: string, p: string) => {
+            let out = o;
             if (params.indexOf(p) === params.length - 1) {
-                o = o + p.split(':')[0].trim();
+                out += p.split(':')[0].trim();
             } else {
-                o = o + p.split(':')[0].trim() + ',';
+                out += p.split(':')[0].trim() + ',';
             }
-            return o;
+            return out;
         }, '' as string);
 
+        // eslint-disable-next-line no-new-func
         const f = new Function(paramNames, StrCast(scriptDoc.script));
 
         Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false });
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index de5e8b92e..f7d7ba6a4 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -124,7 +124,7 @@ class ScriptingCompilerHost {
         return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
     }
     writeFile(fileName: string, content: string) {
-        const file = this.files.find(file => file.fileName === fileName);
+        const file = this.files.find(f => f.fileName === fileName);
         if (file) {
             file.content = content;
         } else {
@@ -147,7 +147,7 @@ class ScriptingCompilerHost {
         return this.files.some(file => file.fileName === fileName);
     }
     readFile(fileName: string): string | undefined {
-        const file = this.files.find(file => file.fileName === fileName);
+        const file = this.files.find(f => f.fileName === fileName);
         if (file) {
             return file.content;
         }
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 36b926053..1328e90e9 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -11,6 +11,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
 import { UndoManager } from './UndoManager';
 
 export class SelectionManager {
+    // eslint-disable-next-line no-use-before-define
     private static _manager: SelectionManager;
     private static get Instance() {
         return SelectionManager._manager ?? new SelectionManager();
@@ -61,7 +62,9 @@ export class SelectionManager {
             dv.IsSelected = false;
             dv._props.whenChildContentsActiveChanged(false);
         });
-        runInAction(() => (this.Instance.SelectedViews.length = 0));
+        runInAction(() => {
+            this.Instance.SelectedViews.length = 0;
+        });
         if (found) this.SelectView(found, false);
     };
 
@@ -71,17 +74,20 @@ export class SelectionManager {
     public static get Docs() { return this.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore
 }
 
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
     if (Doc.noviceMode && expertMode) return false;
     if (type === 'tab') {
         return SelectionManager.Views.lastElement()?._props.renderDepth === 0;
     }
-    let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement());
+    const selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement());
     return selected?.type === type || selected?.type_collection === type || !type;
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function deselectAll() {
     SelectionManager.DeselectAll();
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function undo() {
     SelectionManager.DeselectAll();
     return UndoManager.Undo();
@@ -89,17 +95,19 @@ ScriptingGlobals.add(function undo() {
 
 export function ShowUndoStack() {
     SelectionManager.DeselectAll();
-    var buffer = '';
+    let buffer = '';
     UndoManager.undoStack.forEach((batch, i) => {
         buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n';
-        ///batch.forEach(undo => (buffer += '   ' + undo.prop + '\n'));
+        // /batch.forEach(undo => (buffer += '   ' + undo.prop + '\n'));
     });
     alert(buffer);
 }
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function redo() {
     SelectionManager.DeselectAll();
     return UndoManager.Redo();
 });
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
     const docs = SelectionManager.Views.map(dv => dv.Document).filter(
         d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index c8df9182d..891561245 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -1,44 +1,28 @@
-import { action, computed, makeObservable, observable } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { MainViewModal } from '../views/MainViewModal';
 import './SharingManager.scss';
 import { PingManager } from './PingManager';
-import { StrCast } from '../../fields/Types';
-import { Doc } from '../../fields/Doc';
 import { SettingsManager } from './SettingsManager';
 
 @observer
 export class ServerStats extends React.Component<{}> {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: ServerStats;
     @observable private isOpen = false; // whether the SharingManager modal is open or not
 
+    @observable _stats: { [key: string]: any } | undefined = undefined;
     // private get linkVisible() {
     //     return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
     // }
 
-    @action
-    public open = async () => {
-        /**
-         * Populates the list of users.
-         */
-        fetch('/stats').then((res: Response) => res.text().then(action(stats => (this._stats = JSON.parse(stats)))));
-
-        this.isOpen = true;
-    };
-
-    public close = action(() => {
-        this.isOpen = false;
-    });
-
     constructor(props: {}) {
         super(props);
         makeObservable(this);
         ServerStats.Instance = this;
     }
 
-    @observable _stats: { [key: string]: any } | undefined = undefined;
-
     /**
      * @returns the main interface of the SharingManager.
      */
@@ -63,7 +47,28 @@ export class ServerStats extends React.Component<{}> {
         );
     }
 
+    // eslint-disable-next-line react/sort-comp
+    public close = action(() => {
+        this.isOpen = false;
+    });
+    public open = async () => {
+        /**
+         * Populates the list of users.
+         */
+        fetch('/stats').then((res: Response) =>
+            res.text().then(
+                action(stats => {
+                    this._stats = JSON.parse(stats);
+                })
+            )
+        );
+
+        runInAction(() => {
+            this.isOpen = true;
+        });
+    };
+
     render() {
-        return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} />;
+        return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive closeOnExternalClick={this.close} />;
     }
 }
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 6676e4e03..5b4ac5aff 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -78,7 +78,6 @@ export class SharingManager extends React.Component<{}> {
     @observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the document being shared
     // @observable private copied = false;
     @observable private dialogueBoxOpacity = 1; // for the modal
-    @observable private overlayOpacity = 0.4; // for the modal
     @observable private selectedUsers: UserOptions[] | null = null; // users (individuals/groups) selected to share with
     @observable private permissions: SharingPermissions = SharingPermissions.Edit; // the permission with which to share with other users
     @observable private individualSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of individuals
@@ -661,14 +660,12 @@ export class SharingManager extends React.Component<{}> {
                     if (docs.length) {
                         docs.forEach(doc => doc && Doc.BrushDoc(doc));
                         this.dialogueBoxOpacity = 0.1;
-                        this.overlayOpacity = 0.1;
                     }
                 })}
                 onPointerLeave={action(() => {
                     if (docs.length) {
                         docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
                         this.dialogueBoxOpacity = 1;
-                        this.overlayOpacity = 0.4;
                     }
                 })}>
                 {contents}
@@ -742,6 +739,6 @@ export class SharingManager extends React.Component<{}> {
     }
 
     render() {
-        return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+        return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.close} />;
     }
 }
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index f9c2d522f..25a3c9ad8 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -1,8 +1,7 @@
-import { IReactionDisposer, makeObservable, observable, observe, reaction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { NumCast } from '../../fields/Types';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { Id } from '../../fields/FieldSymbols';
 import { CollectionViewType } from '../documents/DocumentTypes';
 
 export type Movement = {
@@ -33,6 +32,7 @@ export class TrackMovements {
     private tabChangeDisposeFunc: IReactionDisposer | null;
 
     // create static instance and getter for global use
+    // eslint-disable-next-line no-use-before-define
     @observable static _instance: TrackMovements;
     static get Instance(): TrackMovements {
         return TrackMovements._instance;
@@ -92,12 +92,13 @@ export class TrackMovements {
         // so that the size comparisons are correct, we must filter to only the FFViews
         const isFFView = (doc: Doc) => doc && doc._type_collection === CollectionViewType.Freeform;
         const tabbedFFViews = new Set<Doc>();
-        for (const DashDoc of tabbedDocs) {
+        tabbedDocs.forEach(DashDoc => {
             if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc);
-        }
+        });
 
         // new tab was added - need to add it
         if (tabbedFFViews.size > this.recordingFFViews.size) {
+            // eslint-disable-next-line no-restricted-syntax
             for (const DashDoc of tabbedDocs) {
                 if (!this.recordingFFViews.has(DashDoc)) {
                     if (isFFView(DashDoc)) {
@@ -111,6 +112,7 @@ export class TrackMovements {
         }
         // tab was removed - need to remove it from recordingFFViews
         else if (tabbedFFViews.size < this.recordingFFViews.size) {
+            // eslint-disable-next-line no-restricted-syntax
             for (const [doc] of this.recordingFFViews) {
                 if (!tabbedFFViews.has(doc)) {
                     this.removeRecordingFFView(doc);
@@ -208,11 +210,11 @@ export class TrackMovements {
             return;
         }
 
-        for (const [id, disposeFunc] of this.recordingFFViews) {
+        Array.from(this.recordingFFViews).forEach(([id, disposeFunc]) => {
             // console.info('calling dispose func : docId', id);
             disposeFunc();
-            this.recordingFFViews.delete(id);
-        }
+            this.recordingFFViews?.delete(id);
+        });
     };
 
     private trackMovement = (panX: number, panY: number, doc: Doc, scale: number = 0) => {
@@ -241,9 +243,9 @@ export class TrackMovements {
     // method that concatenates an array of presentatations into one
     public concatPresentations = (presentations: Presentation[]): Presentation => {
         // these three will lead to the combined presentation
-        let combinedMovements: Movement[] = [];
+        const combinedMovements: Movement[] = [];
         let sumTime = 0;
-        let combinedMetas: any[] = [];
+        const combinedMetas: any[] = [];
 
         presentations.forEach(presentation => {
             const { movements, totalTime, meta } = presentation;
@@ -251,9 +253,7 @@ export class TrackMovements {
             // update movements if they had one
             if (movements) {
                 // add the summed time to the movements
-                const addedTimeMovements = movements.map(move => {
-                    return { ...move, time: move.time + sumTime };
-                });
+                const addedTimeMovements = movements.map(move => ({ ...move, time: move.time + sumTime }));
                 // concat the movements already in the combined presentation with these new ones
                 combinedMovements.push(...addedTimeMovements);
             }
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index dca37c960..1a07dd6ae 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -116,20 +116,14 @@ export class Transform {
 
     preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform);
 
-    transformPoint = (x: number, y: number): [number, number] => {
-        x *= this._scale;
-        x += this._translateX;
-        y *= this._scale;
-        y += this._translateY;
-        return [x, y];
-    };
+    transformPoint = (x: number, y: number): [number, number] => [x * this._scale + this._translateX, y * this._scale + this._translateY];
 
     transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale];
 
     transformBounds(x: number, y: number, width: number, height: number): { x: number; y: number; width: number; height: number } {
-        [x, y] = this.transformPoint(x, y);
-        [width, height] = this.transformDirection(width, height);
-        return { x, y, width, height };
+        const [tx, ty] = this.transformPoint(x, y);
+        const [twidth, theight] = this.transformDirection(width, height);
+        return { x: tx, y: ty, width: twidth, height: theight };
     }
 
     inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale, -this._rotate);
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
index 90fd299c1..9ef2aa8d7 100644
--- a/src/client/util/TypedEvent.ts
+++ b/src/client/util/TypedEvent.ts
@@ -14,27 +14,27 @@ export class TypedEvent<T> {
     on = (listener: Listener<T>): Disposable => {
         this.listeners.push(listener);
         return {
-            dispose: () => this.off(listener)
+            dispose: () => this.off(listener),
         };
-    }
+    };
 
     once = (listener: Listener<T>): void => {
         this.listenersOncer.push(listener);
-    }
+    };
 
     off = (listener: Listener<T>) => {
         const callbackIndex = this.listeners.indexOf(listener);
         if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
-    }
+    };
 
     emit = (event: T) => {
         /** Update any general listeners */
-        this.listeners.forEach((listener) => listener(event));
+        this.listeners.forEach(listener => listener(event));
 
         /** Clear the `once` queue */
-        this.listenersOncer.forEach((listener) => listener(event));
+        this.listenersOncer.forEach(listener => listener(event));
         this.listenersOncer = [];
-    }
+    };
 
-    pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e));
-}
\ No newline at end of file
+    pipe = (te: TypedEvent<T>): Disposable => this.on(e => te.emit(e));
+}
diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index 6bbf55e5a..d6f3f2340 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-use-before-define */
 /* eslint-disable prefer-destructuring */
 /* eslint-disable no-param-reassign */
 /* eslint-disable camelcase */
@@ -242,6 +243,7 @@ function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
  * is robust: a near-tangential intersection will yield zero or two
  * intersections.
  */
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
 function recursively_intersect(a: Point[], t0: number, t1: number, deptha: number, b: Point[], u0: number, u1: number, depthb: number, parameters: number[][]) {
     if (deptha > 0) {
         const a1 = new Array<Point>(4);
@@ -538,8 +540,8 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
         for (let i = 0; i < maxIterations; i++) {
             const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve); //  Improved parameter values
             GenerateBezier(d, first, last, uPrime, tHat1, tHat2, bezCurve);
-            const { maxError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
-            if (maxError < error) {
+            const { maxError: maximumError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
+            if (maximumError < error) {
                 result.push(bezCurve[1]);
                 result.push(bezCurve[2]);
                 result.push(bezCurve[3]);
diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx
index 1e226bf6d..cecebc648 100644
--- a/src/client/util/reportManager/ReportManagerComponents.tsx
+++ b/src/client/util/reportManager/ReportManagerComponents.tsx
@@ -1,9 +1,15 @@
+/* eslint-disable react/require-default-props */
+/* eslint-disable prefer-destructuring */
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable no-use-before-define */
 import * as React from 'react';
-import { Issue } from './reportManagerSchema';
-import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils';
 import ReactMarkdown from 'react-markdown';
 import rehypeRaw from 'rehype-raw';
 import remarkGfm from 'remark-gfm';
+import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils';
+import { Issue } from './reportManagerSchema';
 import { StrCast } from '../../../fields/Types';
 import { Doc } from '../../../fields/Doc';
 
@@ -18,7 +24,7 @@ interface FilterProps<T> {
 }
 
 // filter ui for issues (horizontal list of tags)
-export const Filter = <T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) => {
+export function Filter<T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) {
     // establishing theme
     const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
     const colors = darkMode ? darkColors : lightColors;
@@ -28,7 +34,7 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }:
     return (
         <div className="issues-filter">
             <Tag
-                text={'All'}
+                text="All"
                 onClick={() => {
                     setActiveValue(null);
                 }}
@@ -38,25 +44,23 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }:
                 borderColor={activeValue === null ? StrCast(Doc.UserDoc().userColor) : colors.border}
                 border
             />
-            {items.map(item => {
-                return (
-                    <Tag
-                        key={item}
-                        text={item}
-                        onClick={() => {
-                            setActiveValue(item);
-                        }}
-                        fontSize="12px"
-                        backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'}
-                        color={activeValue === item ? activeTagTextColor : colors.textGrey}
-                        border
-                        borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border}
-                    />
-                );
-            })}
+            {items.map(item => (
+                <Tag
+                    key={item}
+                    text={item}
+                    onClick={() => {
+                        setActiveValue(item);
+                    }}
+                    fontSize="12px"
+                    backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'}
+                    color={activeValue === item ? activeTagTextColor : colors.textGrey}
+                    border
+                    borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border}
+                />
+            ))}
         </div>
     );
-};
+}
 
 interface IssueCardProps {
     issue: Issue;
@@ -64,7 +68,7 @@ interface IssueCardProps {
 }
 
 // Component for the issue cards list on the left
-export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
+export function IssueCard({ issue, onSelect }: IssueCardProps) {
     const [textColor, setTextColor] = React.useState('');
     const [bgColor, setBgColor] = React.useState('transparent');
     const [borderColor, setBorderColor] = React.useState('transparent');
@@ -103,14 +107,14 @@ export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
             <h3 className="issue-title">{issue.title}</h3>
         </div>
     );
-};
+}
 
 interface IssueViewProps {
     issue: Issue;
 }
 
 // Detailed issue view that displays on the right
-export const IssueView = ({ issue }: IssueViewProps) => {
+export function IssueView({ issue }: IssueViewProps) {
     const [issueBody, setIssueBody] = React.useState('');
 
     // Parses the issue body into a formatted markdown (main functionality is replacing urls with tags)
@@ -127,15 +131,16 @@ export const IssueView = ({ issue }: IssueViewProps) => {
             parts.map(async part => {
                 if (imgTagRegex.test(part) || videoTagRegex.test(part) || audioTagRegex.test(part)) {
                     return `\n${await parseFileTag(part)}\n`;
-                } else if (fileRegex.test(part)) {
+                }
+                if (fileRegex.test(part)) {
                     const tag = await parseDashFiles(part);
                     return tag;
-                } else if (localRegex.test(part)) {
+                }
+                if (localRegex.test(part)) {
                     const tag = await parseLocalFiles(part);
                     return tag;
-                } else {
-                    return part;
                 }
+                return part;
             })
         );
 
@@ -143,7 +148,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
     };
 
     // Extracts the src from an image tag and either returns the raw url if not accessible or a new image tag
-    const parseFileTag = async (tag: string): Promise<string> => {
+    const parseFileTag = async (tag: string): Promise<string | undefined> => {
         const regex = /src="([^"]+)"/;
         let url = '';
         const match = tag.match(regex);
@@ -160,18 +165,19 @@ export const IssueView = ({ issue }: IssueViewProps) => {
             case '.png':
             case '.jpeg':
             case '.gif':
-                return await getDisplayedFile(url, 'image');
+                return getDisplayedFile(url, 'image');
             // video
             case '.mp4':
             case '.mpeg':
             case '.webm':
             case '.mov':
-                return await getDisplayedFile(url, 'video');
-            //audio
+                return getDisplayedFile(url, 'video');
+            // audio
             case '.mp3':
             case '.wav':
             case '.ogg':
-                return await getDisplayedFile(url, 'audio');
+                return getDisplayedFile(url, 'audio');
+            default:
         }
         return tag;
     };
@@ -183,14 +189,15 @@ export const IssueView = ({ issue }: IssueViewProps) => {
         const dashAudioRegex = /https:\/\/browndash\.com\/files[/\\]audio/;
 
         if (dashImgRegex.test(url)) {
-            return await getDisplayedFile(url, 'image');
-        } else if (dashVideoRegex.test(url)) {
-            return await getDisplayedFile(url, 'video');
-        } else if (dashAudioRegex.test(url)) {
-            return await getDisplayedFile(url, 'audio');
-        } else {
-            return url;
+            return getDisplayedFile(url, 'image');
         }
+        if (dashVideoRegex.test(url)) {
+            return getDisplayedFile(url, 'video');
+        }
+        if (dashAudioRegex.test(url)) {
+            return getDisplayedFile(url, 'audio');
+        }
+        return url;
     };
 
     // Returns the corresponding HTML tag for a src url
@@ -200,31 +207,37 @@ export const IssueView = ({ issue }: IssueViewProps) => {
         const dashAudioRegex = /http:\/\/localhost:1050\.com\/files[/\\]audio/;
 
         if (imgRegex.test(url)) {
-            return await getDisplayedFile(url, 'image');
-        } else if (dashVideoRegex.test(url)) {
-            return await getDisplayedFile(url, 'video');
-        } else if (dashAudioRegex.test(url)) {
-            return await getDisplayedFile(url, 'audio');
-        } else {
-            return url;
+            return getDisplayedFile(url, 'image');
+        }
+        if (dashVideoRegex.test(url)) {
+            return getDisplayedFile(url, 'video');
+        }
+        if (dashAudioRegex.test(url)) {
+            return getDisplayedFile(url, 'audio');
         }
+        return url;
     };
 
-    const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string> => {
+    const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string | undefined> => {
         switch (fileType) {
-            case 'image':
+            case 'image': {
                 const imgValid = await isImgValid(url);
                 if (!imgValid) return `\n${url} (This image could not be loaded)\n`;
                 return `\n${url}\n<img width="100%" alt="Issue asset" src=${url} />\n`;
-            case 'video':
+            }
+            case 'video': {
                 const videoValid = await isVideoValid(url);
                 if (!videoValid) return `\n${url} (This video could not be loaded)\n`;
                 return `\n${url}\n<video class="report-default-video" width="100%" controls alt="Issue asset" src=${url} />\n`;
-            case 'audio':
+            }
+            case 'audio': {
                 const audioValid = await isAudioValid(url);
                 if (!audioValid) return `\n${url} (This audio could not be loaded)\n`;
                 return `\n${url}\n<audio src=${url} controls />\n`;
+            }
+            default:
         }
+        return undefined;
     };
 
     // Loads an image and returns a promise that resolves as whether the image is valid or not
@@ -270,7 +283,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
         <div className="issue-view">
             <span className="issue-label">
                 Issue{' '}
-                <a className="issue-link" href={issue.html_url} target="_blank">
+                <a className="issue-link" href={issue.html_url} target="_blank" rel="noreferrer">
                     #{issue.number}
                 </a>
             </span>
@@ -292,7 +305,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
             <ReactMarkdown children={issueBody} className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
         </div>
     );
-};
+}
 
 interface TagProps {
     text: string;
@@ -305,7 +318,7 @@ interface TagProps {
 }
 
 // Small tag for labels of the issue
-export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) => {
+export function Tag({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) {
     return (
         <div
             onClick={onClick ?? (() => {})}
@@ -314,14 +327,14 @@ export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColo
             {text}
         </div>
     );
-};
+}
 
 interface FormInputProps {
     value: string;
     placeholder: string;
     onChange: (val: string) => void;
 }
-export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => {
+export function FormInput({ value, placeholder, onChange }: FormInputProps) {
     const [inputBorderColor, setInputBorderColor] = React.useState('');
 
     return (
@@ -349,9 +362,9 @@ export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => {
             }}
         />
     );
-};
+}
 
-export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) => {
+export function FormTextArea({ value, placeholder, onChange }: FormInputProps) {
     const [textAreaBorderColor, setTextAreaBorderColor] = React.useState('');
 
     return (
@@ -378,4 +391,4 @@ export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) =
             }}
         />
     );
-};
+}
diff --git a/src/client/util/reportManager/reportManagerSchema.ts b/src/client/util/reportManager/reportManagerSchema.ts
index 9a1c7c3e9..171c24393 100644
--- a/src/client/util/reportManager/reportManagerSchema.ts
+++ b/src/client/util/reportManager/reportManagerSchema.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-use-before-define */
 /**
  * Issue interface schema from Github.
  */
diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts
index 22e5eebbb..f14967e0a 100644
--- a/src/client/util/reportManager/reportManagerUtils.ts
+++ b/src/client/util/reportManager/reportManagerUtils.ts
@@ -63,9 +63,8 @@ export const getAllIssues = async (octokit: Octokit): Promise<any[]> => {
     // 200 status means success
     if (res.status === 200) {
         return res.data;
-    } else {
-        throw new Error('Error getting issues');
     }
+    throw new Error('Error getting issues');
 };
 
 /**
@@ -104,9 +103,7 @@ export const fileLinktoServerLink = (fileLink: string): string => {
  * @param link response from file upload
  * @returns server file path
  */
-export const getServerPath = (link: any): string => {
-    return link.result.accessPaths.agnostic.server as string;
-};
+export const getServerPath = (link: any): string => link.result.accessPaths.agnostic.server as string;
 
 /**
  * Uploads media files to the server.
@@ -124,6 +121,7 @@ export const uploadFilesToServer = async (mediaFiles: FileData[]): Promise<strin
             alert(err);
         }
     }
+    return undefined;
 };
 
 // helper functions
@@ -141,18 +139,16 @@ export const passesTagFilter = (issue: Issue, priorityFilter: string | null, bug
         passesPriority = issue.labels.some(label => {
             if (typeof label === 'string') {
                 return label === priorityFilter;
-            } else {
-                return label.name === priorityFilter;
             }
+            return label.name === priorityFilter;
         });
     }
     if (bugFilter) {
         passesBug = issue.labels.some(label => {
             if (typeof label === 'string') {
                 return label === bugFilter;
-            } else {
-                return label.name === bugFilter;
             }
+            return label.name === bugFilter;
         });
     }
     return passesPriority && passesBug;
@@ -217,7 +213,8 @@ export const bugColors: { [key: string]: string[] } = {
 export const getLabelColors = (label: string): string[] => {
     if (prioritySet.has(label as Priority)) {
         return priorityColors[label];
-    } else if (bugSet.has(label as BugType)) {
+    }
+    if (bugSet.has(label as BugType)) {
         return bugColors[label];
     }
     return ['#0f73f6', '#ffffff'];
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index f03e316a2..57498f39f 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -47,13 +47,13 @@ import { Track } from './Track';
 
 @observer
 export class Timeline extends ObservableReactComponent<FieldViewProps> {
-    //readonly constants
+    // readonly constants
     private readonly DEFAULT_TICK_SPACING: number = 50;
     private readonly MAX_TITLE_HEIGHT = 75;
     private readonly MAX_CONTAINER_HEIGHT: number = 800;
     private readonly DEFAULT_TICK_INCREMENT: number = 1000;
 
-    //height variables
+    // height variables
     private DEFAULT_CONTAINER_HEIGHT: number = 330;
     private MIN_CONTAINER_HEIGHT: number = 205;
 
@@ -62,7 +62,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         makeObservable(this);
     }
 
-    //react refs
+    // react refs
     @observable private _trackbox = React.createRef<HTMLDivElement>();
     @observable private _titleContainer = React.createRef<HTMLDivElement>();
     @observable private _timelineContainer = React.createRef<HTMLDivElement>();
@@ -70,7 +70,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
     @observable private _roundToggleRef = React.createRef<HTMLDivElement>();
     @observable private _roundToggleContainerRef = React.createRef<HTMLDivElement>();
 
-    //boolean vars and instance vars
+    // boolean vars and instance vars
     @observable private _currentBarX: number = 0;
     @observable private _windSpeed: number = 1;
     @observable private _totalLength: number = 0;
@@ -79,11 +79,11 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
     @observable private _containerHeight: number = this.DEFAULT_CONTAINER_HEIGHT;
     @observable private _tickSpacing = this.DEFAULT_TICK_SPACING;
     @observable private _tickIncrement = this.DEFAULT_TICK_INCREMENT;
-    @observable private _time = 100000; //DEFAULT
+    @observable private _time = 100000; // DEFAULT
     @observable private _playButton = faPlayCircle;
     @observable private _titleHeight = 0;
 
-    @observable public IsPlaying: boolean = false; //scrubber playing
+    @observable public IsPlaying: boolean = false; // scrubber playing
 
     /**
      * collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit.
@@ -97,30 +97,30 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         return DocListCast(this._props.Document[this._props.fieldKey]);
     }
 
-    /////////lifecycle functions////////////
+    /// //////lifecycle functions////////////
     @action
     componentDidMount() {
-        const relativeHeight = window.innerHeight / 20; //sets height to arbitrary size, relative to innerHeight
-        this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //check if relHeight is less than Maxheight. Else, just set relheight to max
-        this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; //offset
-        this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; //twice the titleheight + offset
+        const relativeHeight = window.innerHeight / 20; // sets height to arbitrary size, relative to innerHeight
+        this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; // check if relHeight is less than Maxheight. Else, just set relheight to max
+        this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; // offset
+        this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; // twice the titleheight + offset
         if (!this._props.Document.AnimationLength) {
-            //if animation length did not exist
-            this._props.Document.AnimationLength = this._time; //set it to default time
+            // if animation length did not exist
+            this._props.Document.AnimationLength = this._time; // set it to default time
         } else {
-            this._time = NumCast(this._props.Document.AnimationLength); //else, set time to animationlength stored from before
+            this._time = NumCast(this._props.Document.AnimationLength); // else, set time to animationlength stored from before
         }
-        this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); //the entire length of the timeline div (actual div part itself)
-        this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see)
-        this._visibleStart = this._infoContainer.current!.scrollLeft; //where the div starts
-        this._props.Document.isATOn = !this._props.Document.isATOn; //turns the boolean on, saying AT (animation timeline) is on
+        this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); // the entire length of the timeline div (actual div part itself)
+        this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; // the visible length of the timeline (the length that you current see)
+        this._visibleStart = this._infoContainer.current!.scrollLeft; // where the div starts
+        this._props.Document.isATOn = !this._props.Document.isATOn; // turns the boolean on, saying AT (animation timeline) is on
         this.toggleHandle();
     }
 
     componentWillUnmount() {
-        this._props.Document.AnimationLength = this._time; //save animation length
+        this._props.Document.AnimationLength = this._time; // save animation length
     }
-    /////////////////////////////////////////////////
+    /// //////////////////////////////////////////////
 
     /**
      * React Functional Component
@@ -148,7 +148,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         pixel <= 0 ? (this._currentBarX = 0) : pixel >= this._totalLength ? (this._currentBarX = this._totalLength) : (this._currentBarX = pixel);
     };
 
-    //for playing
+    // for playing
     onPlay = (e: React.MouseEvent) => {
         e.stopPropagation();
         this.play();
@@ -181,7 +181,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         e.preventDefault();
         e.stopPropagation();
         if (this._windSpeed < 64) {
-            //max speed is 32
+            // max speed is 32
             this._windSpeed = this._windSpeed * 2;
         }
     };
@@ -215,7 +215,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         const scrubberbox = this._infoContainer.current!;
         const left = scrubberbox.getBoundingClientRect().left;
         const offsetX = Math.round(e.clientX - left) * this._props.ScreenToLocalTransform().Scale;
-        this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position
+        this.changeCurrentBarX(offsetX + this._visibleStart); // changes scrubber to clicked scrubber position
         return false;
     };
 
@@ -355,12 +355,12 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
      * tool box includes the toggle buttons at the top of the timeline (both editing mode and play mode)
      */
     private timelineToolBox = (scale: number, totalTime: number) => {
-        const size = 40 * scale; //50 is default
+        const size = 40 * scale; // 50 is default
         const iconSize = 25;
         const width: number = this._props.PanelWidth();
         const modeType = this._props.Document.isATOn ? 'Author' : 'Play';
 
-        //decides if information should be omitted because the timeline is very small
+        // decides if information should be omitted because the timeline is very small
         // if its less than 950 pixels then it's going to be overlapping
         let modeString = modeType,
             overviewString = '',
@@ -469,7 +469,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
 
         this._props.Document.isATOn = !this._props.Document.isATOn;
         if (!BoolCast(this._props.Document.isATOn)) {
-            //turning on playmode...
+            // turning on playmode...
             roundToggle.style.transform = 'translate(0px, 0px)';
             roundToggle.style.animationName = 'turnoff';
             roundToggleContainer.style.animationName = 'turnoff';
@@ -477,7 +477,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
             timelineContainer.style.top = `${-this._containerHeight}px`;
             this.toPlay();
         } else {
-            //turning on authoring mode...
+            // turning on authoring mode...
             roundToggle.style.transform = 'translate(20px, 0px)';
             roundToggle.style.animationName = 'turnon';
             roundToggleContainer.style.animationName = 'turnon';
@@ -490,8 +490,8 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
     @action.bound
     changeLengths() {
         if (this._infoContainer.current) {
-            this._visibleLength = this._infoContainer.current.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see)
-            this._visibleStart = this._infoContainer.current.scrollLeft; //where the div starts
+            this._visibleLength = this._infoContainer.current.getBoundingClientRect().width; // the visible length of the timeline (the length that you current see)
+            this._visibleStart = this._infoContainer.current.scrollLeft; // where the div starts
         }
     }
 
@@ -515,7 +515,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
                     }
                 }
             } else {
-                //TODO: remove undefineds and duplicates
+                // TODO: remove undefineds and duplicates
             }
         });
         return longestTime;
diff --git a/src/client/views/animationtimeline/TimelineOverview.tsx b/src/client/views/animationtimeline/TimelineOverview.tsx
index 489c4dcde..7bf685c9e 100644
--- a/src/client/views/animationtimeline/TimelineOverview.tsx
+++ b/src/client/views/animationtimeline/TimelineOverview.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/no-unused-prop-types */
 import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -32,7 +33,6 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
     @observable private visibleTime: number = 0;
     @observable private currentX: number = 0;
     @observable private visibleStart: number = 0;
-    private readonly DEFAULT_HEIGHT = 50;
     private readonly DEFAULT_WIDTH = 300;
 
     componentDidMount() {
@@ -50,9 +50,9 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
         );
     }
 
-    componentWillUnmount = () => {
+    componentWillUnmount() {
         this._authoringReaction && this._authoringReaction();
-    };
+    }
 
     @action
     setOverviewWidth() {
@@ -109,7 +109,7 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
         e.preventDefault();
         e.stopPropagation();
         const scrubberRef = this._scrubberRef.current!;
-        const left = scrubberRef.getBoundingClientRect().left;
+        const { left } = scrubberRef.getBoundingClientRect();
         const offsetX = Math.round(e.clientX - left);
         this.props.changeCurrentBarX((offsetX / this.activeOverviewWidth) * this.props.totalLength + this.props.currentBarX);
     };
@@ -152,17 +152,17 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
         const timeline = this.props.isAuthoring
             ? [
                   <div key="timeline-overview-container" className="timeline-overview-container overviewBar" id="timelineOverview" ref={this.authoringContainer}>
-                      <div ref={this._visibleRef} key="1" className="timeline-overview-visible" style={{ left: `${barStart}px`, width: `${visibleBarWidth}px` }} onPointerDown={this.onPointerDown}></div>,
+                      <div ref={this._visibleRef} key="1" className="timeline-overview-visible" style={{ left: `${barStart}px`, width: `${visibleBarWidth}px` }} onPointerDown={this.onPointerDown} />,
                       <div ref={this._scrubberRef} key="2" className="timeline-overview-scrubber-container" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown}>
-                          <div key="timeline-overview-scrubber-head" className="timeline-overview-scrubber-head"></div>
+                          <div key="timeline-overview-scrubber-head" className="timeline-overview-scrubber-head" />
                       </div>
                   </div>,
               ]
             : [
                   <div key="1" className="timeline-play-bar overviewBar" id="timelinePlay" ref={this.playbackContainer}>
-                      <div ref={this._scrubberRef} className="timeline-play-head" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown}></div>
+                      <div ref={this._scrubberRef} className="timeline-play-head" style={{ left: `${scrubberStart}px` }} onPointerDown={this.onScrubberDown} />
                   </div>,
-                  <div key="2" className="timeline-play-tail" style={{ width: `${playWidth}px` }}></div>,
+                  <div key="2" className="timeline-play-tail" style={{ width: `${playWidth}px` }} />,
               ];
         return (
             <div className="timeline-flex">
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index 578ce77a5..43e5a68eb 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -25,16 +25,13 @@ export class CollectionCalendarView extends CollectionSubView() {
 
     removeCalendar = () => {};
 
-    addCalendar = (doc: Doc | Doc[], annotationKey?: string | undefined): boolean => {
+    addCalendar = (/* doc: Doc | Doc[], annotationKey?: string | undefined */): boolean =>
         // bring up calendar modal with option to create a calendar
-        return true;
-    };
+        true;
 
     _stackRef = React.createRef<CollectionStackingView>();
 
-    panelHeight = () => {
-        return this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title
-    };
+    panelHeight = () => this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title
 
     // most recent calendar should come first
     sortByMostRecentDate = (calendarA: Doc, calendarB: Doc) => {
@@ -46,18 +43,18 @@ export class CollectionCalendarView extends CollectionSubView() {
 
         if (aFromDate > bFromDate) {
             return -1; // a comes first
-        } else if (aFromDate < bFromDate) {
+        }
+        if (aFromDate < bFromDate) {
+            return 1; // b comes first
+        }
+        // start dates are the same
+        if (aToDate > bToDate) {
+            return -1; // a comes first
+        }
+        if (aToDate < bToDate) {
             return 1; // b comes first
-        } else {
-            // start dates are the same
-            if (aToDate > bToDate) {
-                return -1; // a comes first
-            } else if (aToDate < bToDate) {
-                return 1; // b comes first
-            } else {
-                return 0; // same start and end dates
-            }
         }
+        return 0; // same start and end dates
     };
 
     screenToLocalTransform = () =>
@@ -74,6 +71,7 @@ export class CollectionCalendarView extends CollectionSubView() {
         return (
             <div className="collectionCalendarView">
                 <CollectionStackingView
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props}
                     setContentViewBox={emptyFunction}
                     ref={this._stackRef}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 6dda6e545..51352d3e2 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/jsx-props-no-spreading */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -107,10 +110,10 @@ export class CollectionCarouselView extends CollectionSubView() {
         return (
             <>
                 <div key="back" className="carouselView-back" onClick={this.goback}>
-                    <FontAwesomeIcon icon={'chevron-left'} size={'2x'} />
+                    <FontAwesomeIcon icon="chevron-left" size="2x" />
                 </div>
                 <div key="fwd" className="carouselView-fwd" onClick={this.advance}>
-                    <FontAwesomeIcon icon={'chevron-right'} size={'2x'} />
+                    <FontAwesomeIcon icon="chevron-right" size="2x" />
                 </div>
             </>
         );
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 9a1781b93..fefaf6591 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -120,7 +120,7 @@ export class CollectionDockingView extends CollectionSubView() {
     @undoBatch
     public static CloseSplit(document: Opt<Doc>, panelName?: string): boolean {
         if (CollectionDockingView.Instance) {
-            const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document));
+            const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tabView => (panelName ? tabView.contentItem.config.props.panelName === panelName : tabView.DashDoc === document));
             if (tab) {
                 const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
                 if (j !== -1) {
@@ -147,7 +147,7 @@ export class CollectionDockingView extends CollectionSubView() {
             stack.contentItems[activeContentItemIndex].remove();
             return instance.layoutChanged();
         }
-        const tab = Array.from(instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName);
+        const tab = Array.from(instance.tabMap.keys()).find(tabView => tabView.contentItem.config.props.panelName === panelName);
         if (tab) {
             const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
             if (newConfig.props.documentId !== tab.header.parent.contentItems[j].config.props.documentId) {
@@ -172,7 +172,7 @@ export class CollectionDockingView extends CollectionSubView() {
     public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) {
         if (document?._type_collection === CollectionViewType.Docking && !keyValue) return DashboardView.openDashboard(document);
         if (!CollectionDockingView.Instance) return false;
-        const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document && !tab.contentItem.config.props.keyValue && !keyValue);
+        const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tabView => tabView.DashDoc === document && !tabView.contentItem.config.props.keyValue && !keyValue);
         if (tab) {
             tab.header.parent.setActiveContentItem(tab.contentItem);
             return true;
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index e00fdb50c..95aecc7d0 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -14,7 +15,7 @@ import { DocUtils, Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { undoBatch } from '../../util/UndoManager';
+import { undoBatch, undoable } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
@@ -113,13 +114,15 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
     };
 
     @action
-    headingChanged = (value: string, shiftDown?: boolean) => {
+    headingChanged = (value: string /* , shiftDown?: boolean */) => {
         const castedValue = this.getValue(value);
         if (castedValue) {
             if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
                 return false;
             }
-            this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
+            this._props.docList.forEach(d => {
+                d[this._props.pivotField] = castedValue;
+            });
             if (this._props.headingObject) {
                 this._props.headingObject.setHeading(castedValue.toString());
                 this._heading = this._props.headingObject.heading;
@@ -129,10 +132,14 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
         return false;
     };
 
-    @action pointerEntered = () => (this._hover = true);
-    @action pointerLeave = () => (this._hover = false);
-    @undoBatch
-    addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);
+    @action pointerEntered = () => {
+        this._hover = true;
+    };
+    @action pointerLeave = () => {
+        this._hover = false;
+    };
+
+    addTextNote = undoable(() => this.addNewTextDoc('-typed text-', false, true), 'add text note');
 
     // addNewTextDoc is called when a user starts typing in a column to create a new node
     @action
@@ -195,6 +202,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
                             }
                             return this._props.addDocument?.(created);
                         }
+                        return undefined;
                     },
                     icon: 'compress-arrows-alt',
                 })
@@ -214,6 +222,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
                             }
                             return this._props.addDocument?.(created) || false;
                         }
+                        return undefined;
                     },
                     icon: 'compress-arrows-alt',
                 })
@@ -238,7 +247,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
         const key = this._props.pivotField;
         const heading = this._heading;
         const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin;
-        const evContents = heading ? heading : '25';
+        const evContents = heading || '25';
         const headingView = this._props.headingObject ? (
             <div
                 key={heading}
@@ -252,17 +261,16 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
                     className="collectionNoteTakingView-sectionHeader-subCont"
                     title={evContents === `No Value` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
                     style={{ background: evContents !== `No Value` ? this._color : 'inherit' }}>
-                    <EditableView GetValue={() => evContents} isEditingCallback={isEditing => isEditing && this._props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
+                    <EditableView GetValue={() => evContents} isEditingCallback={isEditing => isEditing && this._props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine />
                 </div>
                 {(this._props.colHeaderData?.length ?? 0) > 1 && (
-                    <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
+                    <button type="button" className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
                         <FontAwesomeIcon icon="trash" size="lg" />
                     </button>
                 )}
             </div>
         ) : null;
         const templatecols = this.columnWidth;
-        const type = this._props.Document.type;
         return (
             <>
                 {headingView}
@@ -270,7 +278,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
                     <div className="collectionNoteTakingView-columnStack">
                         <div
                             key={`${heading}-stack`}
-                            className={`collectionNoteTakingView-Nodes`}
+                            className="collectionNoteTakingView-Nodes"
                             style={{
                                 padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
                                 gridGap: this._props.gridGap,
@@ -282,10 +290,13 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
                         {!this._props.chromeHidden ? (
                             <div className="collectionNoteTakingView-DocumentButtons" style={{ display: this._props.isContentActive() ? 'flex' : 'none', marginBottom: 10 }}>
                                 <div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}>
-                                    <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents={'+ Node'} menuCallback={this.menuCallback} />
+                                    <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents="+ Node" menuCallback={this.menuCallback} />
                                 </div>
                                 <div className="collectionNoteTakingView-addDocumentButton" style={{ color: lightOrDark(this._props.backgroundColor?.()) }}>
-                                    <EditableView {...this._props.editableViewProps()} />
+                                    {
+                                        // eslint-disable-next-line react/jsx-props-no-spreading
+                                        <EditableView {...this._props.editableViewProps()} />
+                                    }
                                 </div>
                             </div>
                         ) : null}
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index 67070b24d..ddd4b8137 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -28,7 +28,7 @@ export class CollectionNoteTakingViewDivider extends ObservableReactComponent<Di
         setupMoveUpEvents(
             this,
             e,
-            (e, down, delta) => {
+            (moveEv, down, delta) => {
                 if (!batch) batch = UndoManager.StartBatch('resizing');
                 this._props.setColumnStartXCoords(delta[0], this._props.index);
                 return false;
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 35f330b44..cb463077c 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -157,7 +157,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         const key = this._props.pivotField;
         const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
         key && (newDoc[key] = this.getValue(this._props.heading));
-        const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
+        const maxHeading = this._props.docList.reduce((prevHeading, doc) => (NumCast(doc.heading) > prevHeading ? NumCast(doc.heading) : prevHeading), 0);
         const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
         newDoc.heading = heading;
         FormattedTextBox.SetSelectOnLoad(newDoc);
@@ -220,9 +220,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
     renderMenu = () => (
         <div className="collectionStackingView-optionPicker">
             <div className="optionOptions">
-                <div className={'optionPicker' + (true ? ' active' : '')} onClick={action(() => {})}>
-                    Add options here
-                </div>
+                <div className="optionPicker active">Add options here</div>
             </div>
         </div>
     );
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 538a6fd5e..bf81bdc7f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -177,24 +177,24 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight);
 
     @action
-    remove = (doc: Doc | Doc[]): boolean => {
-        const docs = doc instanceof Doc ? [doc] : doc;
+    remove = (docIn: Doc | Doc[]): boolean => {
+        const docs = docIn instanceof Doc ? [docIn] : docIn;
         const targetDataDoc = this.Document[DocData];
         const value = DocListCast(targetDataDoc[this._props.fieldKey]);
         const result = value.filter(v => !docs.includes(v));
-        if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
+        if (docs.some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
         if (result.length !== value.length) {
-            if (doc instanceof Doc) {
-                const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc);
+            if (docIn instanceof Doc) {
+                const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(docIn);
                 const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1];
-                this._props.removeDocument?.(doc);
+                this._props.removeDocument?.(docIn);
                 if (ind > 0 && prev) {
                     FormattedTextBox.SetSelectOnLoad(prev);
                     DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false);
                 }
                 return true;
             }
-            return this._props.removeDocument?.(doc) ?? false;
+            return this._props.removeDocument?.(docIn) ?? false;
         }
         return false;
     };
@@ -299,7 +299,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     @computed get treeViewElements() {
         TraceMobx();
         const dragAction = StrCast(this.Document.childDragAction) as any as dropActionType;
-        const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
+        const treeAddDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
         const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false;
         if (this._renderCount < this.treeChildren.length)
             setTimeout(
@@ -315,7 +315,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             this._props.TemplateDataDocument,
             undefined,
             undefined,
-            addDoc,
+            treeAddDoc,
             this.remove,
             moveDoc,
             dragAction,
@@ -419,8 +419,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
 
     addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this._props.fieldKey}_annotations`) || false;
     remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this._props.fieldKey}_annotations`) || false;
-    moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) =>
-        this.moveDocument(doc, targetCollection, addDocument, `${this._props.fieldKey}_annotations`) || false;
+    moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => this.moveDocument(doc, targetCollection, addDocument) || false;
 
     @observable _headerHeight = 0;
     @computed get content() {
diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx
index 4523a4f1e..7dc08389b 100644
--- a/src/client/views/collections/KeyRestrictionRow.tsx
+++ b/src/client/views/collections/KeyRestrictionRow.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/button-has-type */
 import { observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -35,11 +36,36 @@ export default class KeyRestrictionRow extends React.Component<IKeyRestrictionPr
 
         return (
             <div className="collectionViewBaseChrome-viewSpecsMenu-row">
-                <input className="collectionViewBaseChrome-viewSpecsMenu-rowLeft" value={this._key} onChange={e => runInAction(() => (this._key = e.target.value))} placeholder="KEY" />
-                <button className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle" style={{ background: this._contains ? '#77dd77' : '#ff6961' }} onClick={() => runInAction(() => (this._contains = !this._contains))}>
+                <input
+                    className="collectionViewBaseChrome-viewSpecsMenu-rowLeft"
+                    value={this._key}
+                    onChange={e =>
+                        runInAction(() => {
+                            this._key = e.target.value;
+                        })
+                    }
+                    placeholder="KEY"
+                />
+                <button
+                    className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
+                    style={{ background: this._contains ? '#77dd77' : '#ff6961' }}
+                    onClick={() =>
+                        runInAction(() => {
+                            this._contains = !this._contains;
+                        })
+                    }>
                     {this._contains ? 'CONTAINS' : 'DOES NOT CONTAIN'}
                 </button>
-                <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight" value={this._value} onChange={e => runInAction(() => (this._value = e.target.value))} placeholder="VALUE" />
+                <input
+                    className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
+                    value={this._value}
+                    onChange={e =>
+                        runInAction(() => {
+                            this._value = e.target.value;
+                        })
+                    }
+                    placeholder="VALUE"
+                />
             </div>
         );
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
index 0acc99360..d2ce17f99 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
@@ -10,6 +10,7 @@ export interface CollectionFreeFormViewBackgroundGridProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     color: () => string;
+    // eslint-disable-next-line react/require-default-props
     isAnnotationOverlay?: boolean;
     nativeDimScaling: () => number;
     zoomScaling: () => number;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index 73dd7fea3..fc39cafaa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -5,13 +5,13 @@ import * as React from 'react';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import './CollectionFreeFormView.scss';
-import { Doc } from '../../../../fields/Doc';
 
 /**
  * An Fsa Arc.  The first array element is a test condition function that will be observed.
  * The second array element is a function that will be invoked when the first test function
  * returns a truthy value
  */
+// eslint-disable-next-line no-use-before-define
 export type infoArc = [() => any, (res?: any) => infoState];
 
 export const StateMessage = Symbol('StateMessage');
@@ -46,6 +46,7 @@ export function InfoState(
     gif?: string,
     entryFunc?: () => any
 ) {
+    // eslint-disable-next-line new-cap
     return new infoState(msg, arcs, gif, entryFunc);
 }
 
@@ -73,14 +74,15 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
     }
 
     clearState = () => this._disposers.map(disposer => disposer());
-    initState = () => (this._disposers = 
-        this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(
-            arc => reaction(
-                arc.test,
-                res => res && this._props.next(arc.act(res)),
-                { fireImmediately: true }
-            )
-        )); // prettier-ignore
+    initState = () => {
+        this._disposers = this.Arcs
+            .map(arc => ({ test: arc[0], act: arc[1] }))
+            .map(arc => reaction(
+                    arc.test,
+                    res => res && this._props.next(arc.act(res)),
+                    { fireImmediately: true }
+                )
+        )}; // prettier-ignore
 
     componentDidMount() {
         this.initState();
@@ -97,10 +99,15 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
     render() {
         const gif = this.State?.[StateMessageGIF];
         return (
-            <div className={'collectionFreeform-infoUI'}>
+            <div className="collectionFreeform-infoUI">
                 <p className="collectionFreeform-infoUI-msg">
                     {this.State?.[StateMessage]}
-                    <button className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')} onClick={action(() => (this._expanded = !this._expanded))}>
+                    <button
+                        type="button"
+                        className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')}
+                        onClick={action(() => {
+                            this._expanded = !this._expanded;
+                        })}>
                         {this._expanded ? 'Less...' : 'More...'}
                     </button>
                 </p>
@@ -108,7 +115,7 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
                     <img src={`/assets/${gif}`} alt="state message gif" />
                 </div>
                 <div className="collectionFreeform-infoUI-close">
-                    <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(e => this.props.close())} />
+                    <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(() => this.props.close())} />
                 </div>
             </div>
         );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 3970c6807..a4496a417 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -147,10 +147,10 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
         }
         const val = Field.toString(pair.layout[pivotFieldKey] as FieldType);
         if (listValue) {
-            listValue.forEach((val, i) => {
-                !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
-                pivotColumnGroups.get(val)!.docs.push(pair.layout);
-                pivotColumnGroups.get(val)!.replicas.push(i.toString());
+            listValue.forEach((lval, i) => {
+                !pivotColumnGroups.get(lval) && pivotColumnGroups.set(lval, { docs: [], filters: [lval], replicas: [] });
+                pivotColumnGroups.get(lval)!.docs.push(pair.layout);
+                pivotColumnGroups.get(lval)!.replicas.push(i.toString());
             });
         } else if (val) {
             !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 35394016d..f64c6715b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -4,9 +4,7 @@ import * as mobxUtils from 'mobx-utils';
 import * as React from 'react';
 import * as uuid from 'uuid';
 import CursorField from '../../../../fields/CursorField';
-import { Doc, FieldResult } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
-import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { Cast } from '../../../../fields/Types';
 import { CollectionViewProps } from '../CollectionSubView';
@@ -16,14 +14,12 @@ import './CollectionFreeFormView.scss';
 export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
     @computed protected get cursors(): CursorField[] {
         const { Document } = this.props;
-
-        let cursors: FieldResult<List<CursorField>>;
-        const id = Doc.UserDoc()[Id];
-        if (!id || !(cursors = Cast(Document.cursors, listSpec(CursorField)))) {
+        const cursors = Cast(Document.cursors, listSpec(CursorField));
+        if (!cursors) {
             return [];
         }
         const now = mobxUtils.now();
-        return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== id && now - metadata.timestamp < 1000);
+        return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== Document[Id] && now - metadata.timestamp < 1000);
     }
 
     @computed get renderedCursors() {
@@ -33,46 +29,44 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
                     metadata,
                     position: { x, y },
                 },
-            }) => {
-                return (
-                    <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont" style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}>
-                        <canvas
-                            className="collectionFreeFormRemoteCursors-canvas"
-                            ref={el => {
-                                if (el) {
-                                    const ctx = el.getContext('2d');
-                                    if (ctx) {
-                                        ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22';
-                                        ctx.fillRect(0, 0, 20, 20);
+            }) => (
+                <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont" style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}>
+                    <canvas
+                        className="collectionFreeFormRemoteCursors-canvas"
+                        ref={el => {
+                            if (el) {
+                                const ctx = el.getContext('2d');
+                                if (ctx) {
+                                    ctx.fillStyle = '#' + uuid.v5(metadata.id, uuid.v5.URL).substring(0, 6).toUpperCase() + '22';
+                                    ctx.fillRect(0, 0, 20, 20);
 
-                                        ctx.fillStyle = 'black';
-                                        ctx.lineWidth = 0.5;
+                                    ctx.fillStyle = 'black';
+                                    ctx.lineWidth = 0.5;
 
-                                        ctx.beginPath();
+                                    ctx.beginPath();
 
-                                        ctx.moveTo(10, 0);
-                                        ctx.lineTo(10, 8);
+                                    ctx.moveTo(10, 0);
+                                    ctx.lineTo(10, 8);
 
-                                        ctx.moveTo(10, 20);
-                                        ctx.lineTo(10, 12);
+                                    ctx.moveTo(10, 20);
+                                    ctx.lineTo(10, 12);
 
-                                        ctx.moveTo(0, 10);
-                                        ctx.lineTo(8, 10);
+                                    ctx.moveTo(0, 10);
+                                    ctx.lineTo(8, 10);
 
-                                        ctx.moveTo(20, 10);
-                                        ctx.lineTo(12, 10);
+                                    ctx.moveTo(20, 10);
+                                    ctx.lineTo(12, 10);
 
-                                        ctx.stroke();
-                                    }
+                                    ctx.stroke();
                                 }
-                            }}
-                            width={20}
-                            height={20}
-                        />
-                        <p className="collectionFreeFormRemoteCursors-symbol">{metadata.identifier[0].toUpperCase()}</p>
-                    </div>
-                );
-            }
+                            }
+                        }}
+                        width={20}
+                        height={20}
+                    />
+                    <p className="collectionFreeFormRemoteCursors-symbol">{metadata.identifier[0].toUpperCase()}</p>
+                </div>
+            )
         );
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 79cc534dc..adac5a102 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -9,6 +9,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 
 @observer
 export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: MarqueeOptionsMenu;
 
     public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => void = unimplementedFunction;
@@ -29,14 +30,13 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
     }
 
     render() {
-        const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ width: 19 }} />;
         const buttons = (
             <>
-                <IconButton tooltip={'Create a Collection'} onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} />
-                <IconButton tooltip={'Create a Grouping'} onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} />
-                <IconButton tooltip={'Summarize Documents'} onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} />
-                <IconButton tooltip={'Delete Documents'} onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} />
-                <IconButton tooltip={'Pin selected region'} onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} />
+                <IconButton tooltip="Create a Collection" onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} />
+                <IconButton tooltip="Create a Grouping" onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} />
+                <IconButton tooltip="Summarize Documents" onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} />
+                <IconButton tooltip="Delete Documents" onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} />
+                <IconButton tooltip="Pin selected region" onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} />
             </>
         );
         return this.getElement(buttons);
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index ab6788e6f..1634daaf7 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -17,6 +17,7 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionGridView.scss';
 import Grid, { Layout } from './Grid';
+
 @observer
 export class CollectionGridView extends CollectionSubView() {
     private _containerRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -77,15 +78,13 @@ export class CollectionGridView extends CollectionSubView() {
                 pairs.forEach((pair, i) => {
                     const existing = oldLayouts.find(l => l.i === pair.layout[Id]);
                     if (existing) newLayouts.push(existing);
-                    else {
-                        if (Object.keys(this.dropLocation).length) {
-                            // external drop
-                            this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid));
-                            this.dropLocation = {};
-                        } else {
-                            // internal drop
-                            this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
-                        }
+                    else if (Object.keys(this.dropLocation).length) {
+                        // external drop
+                        this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number; y: number }, !this.flexGrid));
+                        this.dropLocation = {};
+                    } else {
+                        // internal drop
+                        this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
                     }
                 });
                 pairs?.length && this.setLayoutList(newLayouts);
@@ -140,9 +139,7 @@ export class CollectionGridView extends CollectionSubView() {
     /**
      * Creates a layout object for a grid item
      */
-    makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => {
-        return { i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static };
-    };
+    makeLayoutItem = (doc: Doc, pos: { x: number; y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static });
 
     /**
      * Adds a layout to the list of layouts.
@@ -190,6 +187,7 @@ export class CollectionGridView extends CollectionSubView() {
     getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
         return (
             <DocumentView
+                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
@@ -219,7 +217,7 @@ export class CollectionGridView extends CollectionSubView() {
         if (this.flexGrid) {
             const savedLayouts = this.savedLayoutList;
             this.childLayoutPairs.forEach(({ layout: doc }) => {
-                const gridLayout = savedLayouts.find(gridLayout => gridLayout.i === doc[Id]);
+                const gridLayout = savedLayouts.find(layout => layout.i === doc[Id]);
                 if (gridLayout) Object.assign(gridLayout, layoutArray.find(layout => layout.i === doc[Id]) || gridLayout);
             });
 
@@ -318,7 +316,9 @@ export class CollectionGridView extends CollectionSubView() {
             e,
             returnFalse,
             action(() => {
-                undoBatch(() => (this.Document.gridRowHeight = this._rowHeight))();
+                undoBatch(() => {
+                    this.Document.gridRowHeight = this._rowHeight;
+                })();
                 this._rowHeight = undefined;
             }),
             emptyFunction,
@@ -332,8 +332,20 @@ export class CollectionGridView extends CollectionSubView() {
      */
     onContextMenu = () => {
         const displayOptionsMenu: ContextMenuProps[] = [];
-        displayOptionsMenu.push({ description: 'Toggle Content Display Style', event: () => (this.Document.display = this.Document.display ? undefined : 'contents'), icon: 'copy' });
-        displayOptionsMenu.push({ description: 'Toggle Vertical Centering', event: () => (this.Document.centerY = !this.Document.centerY), icon: 'copy' });
+        displayOptionsMenu.push({
+            description: 'Toggle Content Display Style',
+            event: () => {
+                this.Document.display = this.Document.display ? undefined : 'contents';
+            },
+            icon: 'copy',
+        });
+        displayOptionsMenu.push({
+            description: 'Toggle Vertical Centering',
+            event: () => {
+                this.Document.centerY = !this.Document.centerY;
+            },
+            icon: 'copy',
+        });
         ContextMenu.Instance.addItem({ description: 'Display', subitems: displayOptionsMenu, icon: 'tv' });
     };
 
@@ -347,14 +359,14 @@ export class CollectionGridView extends CollectionSubView() {
                 e,
                 returnFalse,
                 returnFalse,
-                (e: PointerEvent, doubleTap?: boolean) => {
-                    if (doubleTap && !e.button) {
+                (clickEv: PointerEvent, doubleTap?: boolean) => {
+                    if (doubleTap && !clickEv.button) {
                         undoBatch(
                             action(() => {
                                 const text = Docs.Create.TextDocument('', { _width: 150, _height: 50 });
                                 FormattedTextBox.SetSelectOnLoad(text); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
                                 Doc.AddDocToList(this.Document, this._props.fieldKey, text);
-                                this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
+                                this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(clickEv.clientX, clickEv.clientY))));
                             })
                         )();
                     }
@@ -387,7 +399,7 @@ export class CollectionGridView extends CollectionSubView() {
                         width={this._props.PanelWidth()}
                         nodeList={this.contents.length ? this.contents : null}
                         layout={this.contents.length ? this.renderedLayoutList : undefined}
-                        childrenDraggable={this._props.isSelected() ? true : false}
+                        childrenDraggable={!!this._props.isSelected()}
                         numCols={this.numCols}
                         rowHeight={this.rowHeight}
                         setLayout={this.setLayout}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 6635ab222..e68ed0e17 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -15,7 +15,8 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
 import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
 import { UndoStack } from '../../UndoStack';
diff --git a/src/client/views/collections/collectionLinear/index.ts b/src/client/views/collections/collectionLinear/index.ts
index ff73e14ae..ab3b4b0b5 100644
--- a/src/client/views/collections/collectionLinear/index.ts
+++ b/src/client/views/collections/collectionLinear/index.ts
@@ -1 +1 @@
-export * from "./CollectionLinearView";
\ No newline at end of file
+export * from './CollectionLinearView';
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 125dd2781..b8509a005 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { Button, IconButton } from 'browndash-components';
@@ -7,7 +9,7 @@ import * as React from 'react';
 import { FaChevronRight } from 'react-icons/fa';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch, undoable } from '../../../util/UndoManager';
@@ -16,6 +18,7 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionMulticolumnView.scss';
 import ResizeBar from './MulticolumnResizer';
 import WidthLabel from './MulticolumnWidthLabel';
+import { dropActionType } from '../../../util/DropActionTypes';
 
 interface WidthSpecifier {
     magnitude: number;
@@ -80,7 +83,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
     private get resolvedLayoutInformation(): LayoutData {
         let starSum = 0;
         const widthSpecifiers: WidthSpecifier[] = [];
-        this.childLayouts.map(layout => {
+        this.childLayouts.forEach(layout => {
             const unit = StrCast(layout._dimUnit, '*');
             const magnitude = NumCast(layout._dimMagnitude, this.minimumDim);
             if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
@@ -140,6 +143,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
             return this._props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._xMargin);
         }
+        return undefined;
     }
 
     /**
@@ -159,6 +163,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
             return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
         }
+        return undefined;
     }
 
     /**
@@ -175,7 +180,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
      * or the ratio width evaluated to a pixel value
      */
     private lookupPixels = (layout: Doc): number => {
-        const columnUnitLength = this.columnUnitLength;
+        const { columnUnitLength } = this;
         if (columnUnitLength === undefined) {
             return 0; // we're still waiting on promises to resolve
         }
@@ -193,19 +198,19 @@ export class CollectionMulticolumnView extends CollectionSubView() {
      * documents before the target.
      */
     private lookupIndividualTransform = (layout: Doc) => {
-        const columnUnitLength = this.columnUnitLength;
+        const { columnUnitLength } = this;
         if (columnUnitLength === undefined) {
             return Transform.Identity(); // we're still waiting on promises to resolve
         }
         let offset = 0;
-        var xf = Transform.Identity();
-        this.childLayouts.map(candidate => {
+        // eslint-disable-next-line no-restricted-syntax
+        for (const { layout: candidate } of this.childLayoutPairs) {
             if (candidate === layout) {
-                return (xf = this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0));
+                return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
             }
             offset += this.lookupPixels(candidate) + resizerWidth;
-        });
-        return xf;
+        }
+        return Transform.Identity();
     };
 
     @undoBatch
@@ -213,7 +218,9 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         let dropInd = -1;
         if (de.complete.docDragData && this._mainCont) {
             let curInd = -1;
-            de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d)));
+            de.complete.docDragData?.droppedDocuments.forEach(d => {
+                curInd = this.childDocs.indexOf(d);
+            });
             Array.from(this._mainCont.children).forEach((child, index) => {
                 const brect = child.getBoundingClientRect();
                 if (brect.x < de.x && brect.x + brect.width > de.x) {
@@ -265,7 +272,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
             this.lookupIndividualTransform(childLayout)
                 .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
                 .scale(this._props.NativeDimScaling?.() || 1);
-        const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(childLayout.freeform_fitContentsToBox);
         return (
             <DocumentView
                 Document={childLayout}
@@ -281,11 +287,11 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
                 onClickScript={this.onChildClickHandler}
                 onDoubleClickScript={this.onChildDoubleClickHandler}
-                suppressSetHeight={true}
+                suppressSetHeight
                 ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
-                hideResizeHandles={childLayout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
+                hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)}
                 hideDecorationTitle={this._props.childHideDecorationTitle}
                 fitContentsToBox={this._props.fitContentsToBox}
                 focus={this._props.focus}
@@ -312,17 +318,19 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         const collector: JSX.Element[] = [];
         this.childLayouts.forEach((layout, i) => {
             collector.push(
+                // eslint-disable-next-line react/no-array-index-key
                 <Tooltip title={'Tab: ' + StrCast(layout.title)} key={'wrapper' + i}>
                     <div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}>
                         {this.getDisplayDoc(layout)}
                         {this.layoutDoc._chromeHidden ? null : (
-                            <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
+                            <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(() => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
                         )}
                         <WidthLabel layout={layout} collectionDoc={this.Document} />
                     </div>
                 </Tooltip>,
                 <ResizeBar
                     width={resizerWidth}
+                    // eslint-disable-next-line react/no-array-index-key
                     key={'resizer' + i}
                     styleProvider={this._props.styleProvider}
                     isContentActive={this._props.isContentActive}
@@ -353,14 +361,29 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 {this.contents}
                 {!this._startIndex ? null : (
                     <Tooltip title="scroll back">
-                        <div style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
-                            <Button tooltip="Scroll back" icon={<FontAwesomeIcon icon="chevron-left" size="lg" />} onClick={action(e => (this._startIndex = Math.max(0, this._startIndex - this.maxShown)))} color={SettingsManager.userColor} />
+                        <div
+                            style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }}
+                            onClick={action(() => {
+                                this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown);
+                            })}>
+                            <Button
+                                tooltip="Scroll back"
+                                icon={<FontAwesomeIcon icon="chevron-left" size="lg" />}
+                                onClick={action(() => {
+                                    this._startIndex = Math.max(0, this._startIndex - this.maxShown);
+                                })}
+                                color={SettingsManager.userColor}
+                            />
                         </div>
                     </Tooltip>
                 )}
                 {this._startIndex > this.childLayoutPairs.length - 1 || !this.maxShown ? null : (
                     <Tooltip title="scroll forward">
-                        <div style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
+                        <div
+                            style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }}
+                            onClick={action(() => {
+                                this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown);
+                            })}>
                             <IconButton icon={<FaChevronRight />} color={SettingsManager.userColor} />
                         </div>
                     </Tooltip>
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 17bf3e50c..3fe3d5343 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -3,7 +3,8 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { DragManager, dropActionType } from '../../../util/DragManager';
+import { DragManager } from '../../../util/DragManager';
+import { dropActionType } from '../../../util/DropActionTypes';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
 import { DocumentView } from '../../nodes/DocumentView';
@@ -11,6 +12,7 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionMultirowView.scss';
 import HeightLabel from './MultirowHeightLabel';
 import ResizeBar from './MultirowResizer';
+
 interface HeightSpecifier {
     magnitude: number;
     unit: string;
@@ -63,7 +65,7 @@ export class CollectionMultirowView extends CollectionSubView() {
     private get resolvedLayoutInformation(): LayoutData {
         let starSum = 0;
         const heightSpecifiers: HeightSpecifier[] = [];
-        this.childLayoutPairs.map(pair => {
+        this.childLayoutPairs.forEach(pair => {
             const unit = StrCast(pair.layout._dimUnit, '*');
             const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim);
             if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
@@ -123,6 +125,7 @@ export class CollectionMultirowView extends CollectionSubView() {
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
             return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._yMargin);
         }
+        return undefined;
     }
 
     /**
@@ -142,6 +145,7 @@ export class CollectionMultirowView extends CollectionSubView() {
         if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
             return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
         }
+        return undefined;
     }
 
     /**
@@ -158,13 +162,12 @@ export class CollectionMultirowView extends CollectionSubView() {
      * or the ratio width evaluated to a pixel value
      */
     private lookupPixels = (layout: Doc): number => {
-        const rowUnitLength = this.rowUnitLength;
-        if (rowUnitLength === undefined) {
+        if (this.rowUnitLength === undefined) {
             return 0; // we're still waiting on promises to resolve
         }
         let height = NumCast(layout._dimMagnitude, this.minimumDim);
         if (StrCast(layout._dimUnit, '*') === DimUnit.Ratio) {
-            height *= rowUnitLength;
+            height *= this.rowUnitLength;
         }
         return height;
     };
@@ -176,11 +179,11 @@ export class CollectionMultirowView extends CollectionSubView() {
      * documents before the target.
      */
     private lookupIndividualTransform = (layout: Doc) => {
-        const rowUnitLength = this.rowUnitLength;
-        if (rowUnitLength === undefined) {
+        if (this.rowUnitLength === undefined) {
             return Transform.Identity(); // we're still waiting on promises to resolve
         }
         let offset = 0;
+        // eslint-disable-next-line no-restricted-syntax
         for (const { layout: candidate } of this.childLayoutPairs) {
             if (candidate === layout) {
                 return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
@@ -195,7 +198,9 @@ export class CollectionMultirowView extends CollectionSubView() {
         let dropInd = -1;
         if (de.complete.docDragData && this._mainCont) {
             let curInd = -1;
-            de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d)));
+            de.complete.docDragData?.droppedDocuments.forEach(d => {
+                curInd = this.childDocs.indexOf(d);
+            });
             Array.from(this._mainCont.children).forEach((child, index) => {
                 const brect = child.getBoundingClientRect();
                 if (brect.y < de.y && brect.y + brect.height > de.y) {
@@ -247,7 +252,6 @@ export class CollectionMultirowView extends CollectionSubView() {
             this.lookupIndividualTransform(layout)
                 .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
                 .scale(this._props.NativeDimScaling?.() || 1);
-        const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
         return (
             <DocumentView
                 Document={layout}
@@ -266,7 +270,7 @@ export class CollectionMultirowView extends CollectionSubView() {
                 ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
-                hideResizeHandles={layout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
+                hideResizeHandles={!!(layout.layout_fitWidth || this._props.childHideResizeHandles)}
                 hideDecorationTitle={this._props.childHideDecorationTitle}
                 fitContentsToBox={this._props.fitContentsToBox}
                 focus={this._props.focus}
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index d580d9c52..49ba85524 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -52,27 +53,6 @@ export default class ResizeBar extends React.Component<ResizerProps> {
         }
     };
 
-    private get isActivated() {
-        const { toLeft, toRight } = this.props;
-        if (toLeft && toRight) {
-            if (StrCast(toLeft._dimUnit, '*') === DimUnit.Pixel && StrCast(toRight._dimUnit, '*') === DimUnit.Pixel) {
-                return false;
-            }
-            return true;
-        } else if (toLeft) {
-            if (StrCast(toLeft._dimUnit, '*') === DimUnit.Pixel) {
-                return false;
-            }
-            return true;
-        } else if (toRight) {
-            if (StrCast(toRight._dimUnit, '*') === DimUnit.Pixel) {
-                return false;
-            }
-            return true;
-        }
-        return false;
-    }
-
     @action
     private onPointerUp = () => {
         window.removeEventListener('pointermove', this.onPointerMove);
@@ -90,7 +70,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
                     width: this.props.width,
                     backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor),
                 }}>
-                <div className={'multiColumnResizer-hdl'} onPointerDown={e => this.registerResizing(e)} />
+                <div className="multiColumnResizer-hdl" onPointerDown={e => this.registerResizing(e)} />
             </div>
         );
     }
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
index a9579d931..a7a0b3457 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
@@ -19,7 +19,7 @@ export default class WidthLabel extends React.Component<WidthLabelProps> {
         const getUnit = () => StrCast(layout.dimUnit);
         const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(3));
         return (
-            <div className={'label-wrapper'}>
+            <div className="label-wrapper">
                 <EditableView
                     GetValue={getMagnitude}
                     SetValue={value => {
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
index 878c7ff3c..66215f109 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -20,7 +21,7 @@ export default class HeightLabel extends React.Component<HeightLabelProps> {
         const getUnit = () => StrCast(layout.dimUnit);
         const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3));
         return (
-            <div className={'label-wrapper'}>
+            <div className="label-wrapper">
                 <EditableView
                     GetValue={getMagnitude}
                     SetValue={value => {
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index 73d08d5ef..ad77c327d 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -33,7 +34,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
     };
 
     private onPointerMove = ({ movementY }: PointerEvent) => {
-        const { toTop: toTop, toBottom: toBottom, columnUnitLength } = this.props;
+        const { toTop, toBottom, columnUnitLength } = this.props;
         const movingDown = movementY > 0;
         const toNarrow = movingDown ? toBottom : toTop;
         const toWiden = movingDown ? toTop : toBottom;
@@ -50,27 +51,6 @@ export default class ResizeBar extends React.Component<ResizerProps> {
         }
     };
 
-    private get isActivated() {
-        const { toTop, toBottom } = this.props;
-        if (toTop && toBottom) {
-            if (StrCast(toTop._dimUnit, '*') === DimUnit.Pixel && StrCast(toBottom._dimUnit, '*') === DimUnit.Pixel) {
-                return false;
-            }
-            return true;
-        } else if (toTop) {
-            if (StrCast(toTop._dimUnit, '*') === DimUnit.Pixel) {
-                return false;
-            }
-            return true;
-        } else if (toBottom) {
-            if (StrCast(toBottom._dimUnit, '*') === DimUnit.Pixel) {
-                return false;
-            }
-            return true;
-        }
-        return false;
-    }
-
     @action
     private onPointerUp = () => {
         window.removeEventListener('pointermove', this.onPointerMove);
@@ -88,7 +68,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
                     height: this.props.height,
                     backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor),
                 }}>
-                <div className={'multiRowResizer-hdl'} onPointerDown={e => this.registerResizing(e)} />
+                <div className="multiRowResizer-hdl" onPointerDown={e => this.registerResizing(e)} />
             </div>
         );
     }
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 2823e1936..389fc66b3 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -1,5 +1,6 @@
+/* eslint-disable react/no-unused-prop-types */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
+import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { setupMoveUpEvents } from '../../../../ClientUtils';
@@ -25,8 +26,6 @@ export interface SchemaColumnHeaderProps {
 
 @observer
 export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> {
-    @observable _ref: HTMLDivElement | null = null;
-
     get fieldKey() {
         return this.props.columnKeys[this.props.columnIndex];
     }
@@ -35,9 +34,9 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
     sortClicked = (e: React.PointerEvent) => {
         e.stopPropagation();
         e.preventDefault();
-        if (this.props.sortField == this.fieldKey && this.props.sortDesc) {
+        if (this.props.sortField === this.fieldKey && this.props.sortDesc) {
             this.props.setSort(undefined);
-        } else if (this.props.sortField == this.fieldKey) {
+        } else if (this.props.sortField === this.fieldKey) {
             this.props.setSort(this.fieldKey, true);
         } else {
             this.props.setSort(this.fieldKey, false);
@@ -46,7 +45,7 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
 
     @action
     onPointerDown = (e: React.PointerEvent) => {
-        this.props.isContentActive(true) && setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction, false);
+        this.props.isContentActive(true) && setupMoveUpEvents(this, e, moveEv => this.props.dragColumn(moveEv, this.props.columnIndex), emptyFunction, emptyFunction, false);
     };
 
     render() {
@@ -59,19 +58,18 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
                 onPointerDown={this.onPointerDown}
                 ref={col => {
                     if (col) {
-                        this._ref = col;
                         this.props.setColRef(this.props.columnIndex, col);
                     }
                 }}>
-                <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)}></div>
+                <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)} />
                 <div className="schema-column-title">{this.fieldKey}</div>
 
                 <div className="schema-header-menu">
                     <div className="schema-header-button" onPointerDown={e => this.props.openContextMenu(e.clientX, e.clientY, this.props.columnIndex)}>
                         <FontAwesomeIcon icon="ellipsis-h" />
                     </div>
-                    <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField == this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}>
-                        <FontAwesomeIcon icon="caret-right" style={this.props.sortField == this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} />
+                    <div className="schema-sort-button" onPointerDown={this.sortClicked} style={this.props.sortField === this.fieldKey ? { backgroundColor: Colors.MEDIUM_BLUE } : {}}>
+                        <FontAwesomeIcon icon="caret-right" style={this.props.sortField === this.fieldKey ? { transform: `rotate(${this.props.sortDesc ? '270deg' : '90deg'})` } : {}} />
                     </div>
                 </div>
             </div>
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 27a4493cb..61afe08cf 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -63,7 +63,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
         }
     };
 
-    onPointerEnter = (e: any) => {
+    onPointerEnter = () => {
         if (SnappingManager.IsDragging && this._props.isContentActive()) {
             document.removeEventListener('pointermove', this.onPointerMove);
             document.addEventListener('pointermove', this.onPointerMove);
@@ -75,7 +75,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
 
         if (this._ref && dragIsRow) {
             const rect = this._ref.getBoundingClientRect();
-            const y = e.clientY - rect.top; //y position within the element.
+            const y = e.clientY - rect.top; // y position within the element.
             const height = this._ref.clientHeight;
             const halfLine = height / 2;
             if (y <= halfLine) {
@@ -90,7 +90,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
         }
     };
 
-    onPointerLeave = (e: any) => {
+    onPointerLeave = () => {
         if (this._ref) {
             this._ref.style.borderTop = '0px';
             this._ref.style.borderBottom = '0px';
@@ -131,15 +131,16 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
                                 e,
                                 returnFalse,
                                 emptyFunction,
-                                undoable(e => {
-                                    e.stopPropagation();
+                                undoable(clickEv => {
+                                    clickEv.stopPropagation();
                                     Doc.toggleLockedPosition(this.Document);
                                 }, 'Delete Row')
                             )
-                        }></IconButton>
+                        }
+                    />
                     <IconButton
                         tooltip="close"
-                        icon={<CgClose size={'16px'} />}
+                        icon={<CgClose size="16px" />}
                         size={Size.XSMALL}
                         onPointerDown={e =>
                             setupMoveUpEvents(
@@ -147,8 +148,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
                                 e,
                                 returnFalse,
                                 emptyFunction,
-                                undoable(e => {
-                                    e.stopPropagation();
+                                undoable(clickEv => {
+                                    clickEv.stopPropagation();
                                     this._props.removeDocument?.(this.Document);
                                 }, 'Delete Row')
                             )
@@ -164,8 +165,8 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
                                 e,
                                 returnFalse,
                                 emptyFunction,
-                                undoable(e => {
-                                    e.stopPropagation();
+                                undoable(clickEv => {
+                                    clickEv.stopPropagation();
                                     this._props.addDocTab(this.Document, OpenWhere.addRight);
                                 }, 'Open schema Doc preview')
                             )
diff --git a/src/client/views/global/globalEnums.tsx b/src/client/views/global/globalEnums.tsx
index 610c2b102..2cf9e4162 100644
--- a/src/client/views/global/globalEnums.tsx
+++ b/src/client/views/global/globalEnums.tsx
@@ -1,44 +1,44 @@
 export enum Colors {
-    BLACK = "#000000",
-    DARK_GRAY = "#323232",
-    MEDIUM_GRAY = "#9F9F9F",
-    LIGHT_GRAY = "#DFDFDF",
-    WHITE = "#FFFFFF",
-    MEDIUM_BLUE = "#4476F7",
-    MEDIUM_BLUE_ALT = "#4476f73d", // REDUCED OPACITY
-    LIGHT_BLUE = "#BDDDF5",
-    PINK = "#E0217D",
-    ERROR_RED = "#ff0033",
-    YELLOW = "#F5D747",
-    DROP_SHADOW = "#32323215",
+    BLACK = '#000000',
+    DARK_GRAY = '#323232',
+    MEDIUM_GRAY = '#9F9F9F',
+    LIGHT_GRAY = '#DFDFDF',
+    WHITE = '#FFFFFF',
+    MEDIUM_BLUE = '#4476F7',
+    MEDIUM_BLUE_ALT = '#4476f73d', // REDUCED OPACITY
+    LIGHT_BLUE = '#BDDDF5',
+    PINK = '#E0217D',
+    ERROR_RED = '#ff0033',
+    YELLOW = '#F5D747',
+    DROP_SHADOW = '#32323215',
 }
 
 export enum FontSizes {
-    //Bolded
-    LARGE_HEADER = "16px",
+    // Bolded
+    LARGE_HEADER = '16px',
 
-    //Bolded or unbolded
-    BODY_TEXT = "12px",
+    // Bolded or unbolded
+    BODY_TEXT = '12px',
 
-    //Bolded
-    SMALL_TEXT = "9px",
+    // Bolded
+    SMALL_TEXT = '9px',
 }
 
 export enum Padding {
-    MINIMUM_PADDING = "4px",
-    SMALL_PADDING = "8px",
-    MEDIUM_PADDING = "16px",
-    LARGE_PADDING = "32px",
+    MINIMUM_PADDING = '4px',
+    SMALL_PADDING = '8px',
+    MEDIUM_PADDING = '16px',
+    LARGE_PADDING = '32px',
 }
 
 export enum IconSizes {
-    ICON_SIZE = "28px",
+    ICON_SIZE = '28px',
 }
 
 export enum Borders {
-    STANDARD = "solid 1px #9F9F9F"
+    STANDARD = 'solid 1px #9F9F9F',
 }
 
 export enum Shadows {
-    STANDARD_SHADOW = "0px 3px 4px rgba(0, 0, 0, 0.3)"
-}
\ No newline at end of file
+    STANDARD_SHADOW = '0px 3px 4px rgba(0, 0, 0, 0.3)',
+}
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 60def5d45..f99a18db2 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,14 +1,17 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/require-default-props */
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import * as React from 'react';
 import { Doc, StrListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { Cast, DocCast } from '../../../fields/Types';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { LinkManager } from '../../util/LinkManager';
 import { DocumentView } from '../nodes/DocumentView';
 import './LinkMenu.scss';
 import { LinkMenuItem } from './LinkMenuItem';
-import * as React from 'react';
-import { DocumentType } from '../../documents/DocumentTypes';
 
 interface LinkMenuGroupProps {
     sourceDoc: Doc;
@@ -22,25 +25,24 @@ interface LinkMenuGroupProps {
 @observer
 export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
     private _menuRef = React.createRef<HTMLDivElement>();
+    @observable _collapsed = false;
 
     getBackgroundColor = (): string | undefined => {
-        const link_relationshipList = StrListCast(Doc.UserDoc().link_relationshipList);
+        const linkRelationshipList = StrListCast(Doc.UserDoc().link_relationshipList);
         const linkColorList = StrListCast(Doc.UserDoc().link_ColorList);
         let color: string | undefined;
         // if this link's relationship property is not default "link", set its color
-        if (link_relationshipList) {
-            const relationshipIndex = link_relationshipList.indexOf(this.props.groupType);
+        if (linkRelationshipList) {
+            const relationshipIndex = linkRelationshipList.indexOf(this.props.groupType);
             const RGBcolor: string = linkColorList[relationshipIndex];
             if (RGBcolor) {
-                //set opacity to 0.25 by modifiying the rgb string
+                // set opacity to 0.25 by modifiying the rgb string
                 color = RGBcolor.slice(0, RGBcolor.length - 1) + ', 0.25)';
             }
         }
         return color;
     };
 
-    @observable _collapsed = false;
-
     render() {
         const set = new Set<Doc>(this.props.group);
         const groupItems = Array.from(set.keys()).map(linkDoc => {
@@ -76,7 +78,12 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
 
         return (
             <div className="linkMenu-group" ref={this._menuRef}>
-                <div className="linkMenu-group-name" onClick={action(() => (this._collapsed = !this._collapsed))} style={{ background: this.getBackgroundColor() }}>
+                <div
+                    className="linkMenu-group-name"
+                    onClick={action(() => {
+                        this._collapsed = !this._collapsed;
+                    })}
+                    style={{ background: this.getBackgroundColor() }}>
                     <p className={this.props.groupType === '*' || this.props.groupType === '' ? '' : 'expand-one'}> {this.props.groupType}:</p>
                 </div>
                 {this._collapsed ? null : <div className="linkMenu-group-wrapper">{groupItems}</div>}
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 9be78a6cb..303ff4b98 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -92,10 +92,10 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
         setupMoveUpEvents(
             this,
             e,
-            e => {
+            moveEv => {
                 const dragData = new DragManager.DocumentDragData([this._props.linkDoc], dropActionType.embed);
                 dragData.dropPropertiesToRemove = ['hidden'];
-                DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
+                DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y);
                 return true;
             },
             emptyFunction,
@@ -125,10 +125,10 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
         setupMoveUpEvents(
             this,
             e,
-            e => {
+            moveEv => {
                 const eleClone: any = this._drag.current!.cloneNode(true);
-                eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
-                StartLinkTargetsDrag(eleClone, this._props.docView, e.x, e.y, this._props.sourceDoc, [this._props.linkDoc]);
+                eleClone.style.transform = `translate(${moveEv.x}px, ${moveEv.y}px)`;
+                StartLinkTargetsDrag(eleClone, this._props.docView, moveEv.x, moveEv.y, this._props.sourceDoc, [this._props.linkDoc]);
                 this._props.clearLinkEditor?.();
                 return true;
             },
diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx
deleted file mode 100644
index 0902d53b2..000000000
--- a/src/client/views/linking/LinkRelationshipSearch.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import './LinkEditor.scss';
-
-interface link_relationshipSearchProps {
-    results: string[] | undefined;
-    display: string;
-    //callback fn to set rel + hide dropdown upon setting
-    handleRelationshipSearchChange: (result: string) => void;
-    toggleSearch: () => void;
-}
-@observer
-export class link_relationshipSearch extends React.Component<link_relationshipSearchProps> {
-    handleResultClick = (e: React.MouseEvent) => {
-        const relationship = (e.target as HTMLParagraphElement).textContent;
-        if (relationship) {
-            this.props.handleRelationshipSearchChange(relationship);
-        }
-    };
-
-    handleMouseEnter = () => {
-        this.props.toggleSearch();
-    };
-
-    handleMouseLeave = () => {
-        this.props.toggleSearch();
-    };
-
-    /**
-     * Render an empty div to increase the height of LinkEditor to accommodate 2+ results
-     */
-    emptyDiv = () => {
-        if (this.props.results && this.props.results.length > 2 && this.props.display === 'block') {
-            return <div style={{ height: '50px' }} />;
-        }
-    };
-
-    render() {
-        return (
-            <div className="linkEditor-relationship-dropdown-container">
-                <div className="linkEditor-relationship-dropdown" style={{ display: this.props.display }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
-                    {
-                        // return a dropdown of relationship results if there exist results
-                        this.props.results ? (
-                            this.props.results.map(result => {
-                                return (
-                                    <p key={result} onClick={this.handleResultClick}>
-                                        {result}
-                                    </p>
-                                );
-                            })
-                        ) : (
-                            <p>No matching relationships</p>
-                        )
-                    }
-                </div>
-
-                {/*Render an empty div to increase the height of LinkEditor to accommodate 2+ results */}
-                {this.emptyDiv()}
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index c37466914..9251dca6d 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -532,7 +532,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 e,
                 returnFalse,
                 returnFalse,
-                action((e: PointerEvent, doubleTap?: boolean) => {
+                action((moveEv: PointerEvent, doubleTap?: boolean) => {
                     if (doubleTap) {
                         this.startTrim(TrimScope.All);
                     } else if (this.timeline) {
@@ -572,14 +572,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     setupTimelineDrop = (r: HTMLDivElement | null) => {
         if (r && this.timeline) {
             this._dropDisposer?.();
-            this._dropDisposer = DragManager.MakeDropTarget(
-                r,
-                (e, de) => {
-                    const [xp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
-                    de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
-                },
-                this.layoutDoc
-            );
+            this._dropDisposer = DragManager.MakeDropTarget(r, (e, de) => de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData), this.layoutDoc);
         }
     };
 
diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
index 0084d7394..60bc8df18 100644
--- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
+++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/alt-text */
 import { IconButton } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -15,56 +17,52 @@ interface SchemaCSVPopUpProps {}
 
 @observer
 export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: SchemaCSVPopUp;
 
-    @observable
-    public dataVizDoc: Doc | undefined = undefined;
+    @observable public dataVizDoc: Doc | undefined = undefined;
+    @observable public view: DocumentView | undefined = undefined;
+    @observable public target: Doc | undefined = undefined;
+    @observable public visible: boolean = false;
+
+    constructor(props: SchemaCSVPopUpProps) {
+        super(props);
+        makeObservable(this);
+        SchemaCSVPopUp.Instance = this;
+    }
+
     @action
     public setDataVizDoc = (doc: Doc) => {
         this.dataVizDoc = doc;
     };
 
-    @observable
-    public view: DocumentView | undefined = undefined;
     @action
     public setView = (docView: DocumentView) => {
         this.view = docView;
     };
 
-    @observable
-    public target: Doc | undefined = undefined;
     @action
     public setTarget = (doc: Doc) => {
         this.target = doc;
     };
 
-    @observable
-    public visible: boolean = false;
     @action
     public setVisible = (vis: boolean) => {
         this.visible = vis;
     };
 
-    constructor(props: SchemaCSVPopUpProps) {
-        super(props);
-        makeObservable(this);
-        SchemaCSVPopUp.Instance = this;
-    }
-
-    dataBox = () => {
-        return (
-            <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
-                {this.heading('Schema Table as Data Visualization Doc')}
-                <div className="image-content-wrapper">
-                    <div className="img-wrapper">
-                        <div className="img-container" onPointerDown={e => this.drag(e)}>
-                            <img width={150} height={150} src={'/assets/dataVizBox.png'} />
-                        </div>
+    dataBox = () => (
+        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+            {this.heading('Schema Table as Data Visualization Doc')}
+            <div className="image-content-wrapper">
+                <div className="img-wrapper">
+                    <div className="img-container" onPointerDown={e => this.drag(e)}>
+                        <img width={150} height={150} src="/assets/dataVizBox.png" />
                     </div>
                 </div>
             </div>
-        );
-    };
+        </div>
+    );
 
     heading = (headingText: string) => (
         <div className="summary-heading">
@@ -79,24 +77,22 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
         setupMoveUpEvents(
             {},
             e,
-            e => {
+            moveEv => {
                 const sourceAnchorCreator = () => this.dataVizDoc!;
-                const targetCreator = (annotationOn: Doc | undefined) => {
+                const targetCreator = () => {
                     const embedding = Doc.MakeEmbedding(this.dataVizDoc!);
                     return embedding;
                 };
-                if (this.view && sourceAnchorCreator && !ClientUtils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
-                    DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
-                        dragComplete: e => {
-                            this.setVisible(false);
-                        },
+                if (this.view && sourceAnchorCreator && !ClientUtils.isClick(moveEv.clientX, moveEv.clientY, downX, downY, Date.now())) {
+                    DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
+                        dragComplete: () => this.setVisible(false),
                     });
                     return true;
                 }
                 return false;
             },
             emptyFunction,
-            action(e => {})
+            action(() => {})
         );
     };
 
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 2735a40d5..910504e7c 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -75,8 +75,8 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
     }
 
     @computed get defaultGraphTitle() {
-        var ax0 = this._props.axes[0];
-        var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+        const ax0 = this._props.axes[0];
+        const ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
         if (this._props.axes.length < 2 || !/\d/.test(this._props.records[0][ax0]) || !ax1) {
             return ax0 + ' Pie Chart';
         }
@@ -101,8 +101,6 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
         );
     }
 
-    @action
-    restoreView = (data: Doc) => {};
     // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc)
     getAnchor = (pinProps?: PinProps) => {
         const anchor = Docs.Create.ConfigDocument({
@@ -130,23 +128,23 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
             : validData.map((d: { [x: string]: any }) =>
                   this.byCategory
                       ? d[field] //
-                      : +d[field].replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '')
+                      : +d[field].replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '')
               );
     };
 
     // outlines the slice selected / hovered over
     highlightSelectedSlice = (changeSelectedVariables: boolean, svg: any, arc: any, radius: any, pointer: any, pieDataSet: any) => {
-        var index = -1;
-        var sameAsCurrent: boolean;
+        let index = -1;
+        let sameAsCurrent: boolean;
         const selected = svg.selectAll('.slice').filter((d: any) => {
             index++;
-            var p1 = [0, 0]; // center of pie
-            var p3 = [arc.centroid(d)[0] * 2, arc.centroid(d)[1] * 2]; // outward peak of arc
-            var p2 = [radius * Math.sin(d.startAngle), -radius * Math.cos(d.startAngle)]; // start of arc
-            var p4 = [radius * Math.sin(d.endAngle), -radius * Math.cos(d.endAngle)]; // end of arc
+            const p1 = [0, 0]; // center of pie
+            const p3 = [arc.centroid(d)[0] * 2, arc.centroid(d)[1] * 2]; // outward peak of arc
+            const p2 = [radius * Math.sin(d.startAngle), -radius * Math.cos(d.startAngle)]; // start of arc
+            const p4 = [radius * Math.sin(d.endAngle), -radius * Math.cos(d.endAngle)]; // end of arc
 
             // draw an imaginary horizontal line from the pointer to see how many times it crosses a slice edge
-            var lineCrossCount = 0;
+            let lineCrossCount = 0;
             // if for all 4 lines
             if (Math.min(p1[1], p2[1]) <= pointer[1] && pointer[1] <= Math.max(p1[1], p2[1])) {
                 // within y bounds
@@ -161,13 +159,13 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
             if (Math.min(p4[1], p1[1]) <= pointer[1] && pointer[1] <= Math.max(p4[1], p1[1])) {
                 if (pointer[0] <= ((pointer[1] - p4[1]) * (p1[0] - p4[0])) / (p1[1] - p4[1]) + p4[0]) lineCrossCount++;
             }
-            if (lineCrossCount % 2 != 0) {
+            if (lineCrossCount % 2 !== 0) {
                 // inside the slice of it crosses an odd number of edges
-                var showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index];
+                const showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index];
                 if (changeSelectedVariables) {
                     // for when a bar is selected - not just hovered over
                     sameAsCurrent = this._currSelected
-                        ? showSelected[Object.keys(showSelected)[0]] == this._currSelected![Object.keys(showSelected)[0]] && showSelected[Object.keys(showSelected)[1]] == this._currSelected![Object.keys(showSelected)[1]]
+                        ? showSelected[Object.keys(showSelected)[0]] === this._currSelected![Object.keys(showSelected)[0]] && showSelected[Object.keys(showSelected)[1]] === this._currSelected![Object.keys(showSelected)[1]]
                         : this._currSelected === showSelected;
                     this._currSelected = sameAsCurrent ? undefined : showSelected;
                     this.selectedData = sameAsCurrent ? undefined : d;
@@ -187,104 +185,100 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
         d3.select(this._piechartRef.current).select('svg').remove();
         d3.select(this._piechartRef.current).select('.tooltip').remove();
 
-        var percentField = Object.keys(dataSet[0])[0];
-        var descriptionField = Object.keys(dataSet[0])[1]!;
-        var radius = Math.min(width, height - this._props.margin.top - this._props.margin.bottom) / 2;
+        let percentField = Object.keys(dataSet[0])[0];
+        let descriptionField = Object.keys(dataSet[0])[1]!;
+        const radius = Math.min(width, height - this._props.margin.top - this._props.margin.bottom) / 2;
 
         // converts data into Objects
-        var data = this.data(dataSet);
-        var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
+        let data = this.data(dataSet);
+        let pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || isNaN(d[key])));
         if (this.byCategory) {
-            let uniqueCategories = [...new Set(data)];
-            var pieStringDataSet: { frequency: number }[] = [];
+            const uniqueCategories = [...new Set(data)];
+            const pieStringDataSet: { frequency: number }[] = [];
             for (let i = 0; i < uniqueCategories.length; i++) {
                 pieStringDataSet.push({ frequency: 0, [percentField]: uniqueCategories[i] });
             }
             for (let i = 0; i < data.length; i++) {
-                let sliceData = pieStringDataSet.filter((each: any) => each[percentField] == data[i]);
-                sliceData[0].frequency = sliceData[0].frequency + 1;
+                // eslint-disable-next-line no-loop-func
+                const sliceData = pieStringDataSet.filter((each: any) => each[percentField] === data[i]);
+                sliceData[0].frequency += 1;
             }
             pieDataSet = pieStringDataSet;
-            percentField = Object.keys(pieDataSet[0])[0];
-            descriptionField = Object.keys(pieDataSet[0])[1]!;
+            [percentField, descriptionField] = Object.keys(pieDataSet[0]);
             data = this.data(pieStringDataSet);
         }
-        var trackDuplicates: { [key: string]: any } = {};
-        data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null));
+        let trackDuplicates: { [key: string]: any } = {};
+        data.forEach((eachData: any) => {
+            !trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null;
+        });
 
         // initial chart
-        var svg = (this._piechartSvg = d3
+        const svg = (this._piechartSvg = d3
             .select(this._piechartRef.current)
             .append('svg')
             .attr('class', 'graph')
             .attr('width', width + this._props.margin.right + this._props.margin.left)
             .attr('height', height + this._props.margin.top + this._props.margin.bottom)
             .append('g'));
-        let g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this._props.margin.left) + ',' + height / 2 + ')');
-        var pie = d3.pie();
-        var arc = d3.arc().innerRadius(0).outerRadius(radius);
+        const g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this._props.margin.left) + ',' + height / 2 + ')');
+        const pie = d3.pie();
+        const arc = d3.arc().innerRadius(0).outerRadius(radius);
 
+        const updateHighlights = () => {
+            const hoverOverSlice = this.hoverOverData;
+            const { selectedData } = this;
+            svg.selectAll('path').attr('class', (d: any) =>
+                (selectedData && d.startAngle === selectedData.startAngle && d.endAngle === selectedData.endAngle) || (hoverOverSlice && d.startAngle === hoverOverSlice.startAngle && d.endAngle === hoverOverSlice.endAngle) ? 'slice hover' : 'slice'
+            );
+        };
         // click/hover
         const onPointClick = action((e: any) => this.highlightSelectedSlice(true, svg, arc, radius, d3.pointer(e), pieDataSet));
         const onHover = action((e: any) => {
             this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet);
             updateHighlights();
         });
-        const mouseOut = action((e: any) => {
+        const mouseOut = action(() => {
             this.hoverOverData = undefined;
             updateHighlights();
         });
-        const updateHighlights = () => {
-            const hoverOverSlice = this.hoverOverData;
-            const selectedData = this.selectedData;
-            svg.selectAll('path').attr('class', function (d: any) {
-                return (selectedData && d.startAngle == selectedData.startAngle && d.endAngle == selectedData.endAngle) || (hoverOverSlice && d.startAngle == hoverOverSlice.startAngle && d.endAngle == hoverOverSlice.endAngle)
-                    ? 'slice hover'
-                    : 'slice';
-            });
-        };
 
         // drawing the slices
-        var selected = this.selectedData;
-        var arcs = g.selectAll('arc').data(pie(data)).enter().append('g');
+        const selected = this.selectedData;
+        const arcs = g.selectAll('arc').data(pie(data)).enter().append('g');
         const possibleDataPointVals: { [x: string]: any }[] = [];
         pieDataSet.forEach((each: { [x: string]: any | { valueOf(): number } }) => {
-            var dataPointVal: { [x: string]: any } = {};
+            const dataPointVal: { [x: string]: any } = {};
             dataPointVal[percentField] = each[percentField];
             if (descriptionField) dataPointVal[descriptionField] = each[descriptionField];
             try {
-                dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, ''));
-            } catch (error) {}
+                dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, ''));
+            } catch (error) {
+                /* empty */
+            }
             possibleDataPointVals.push(dataPointVal);
         });
         const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
         arcs.append('path')
             .attr('fill', (d, i) => {
-                var dataPoint;
+                let dataPoint;
                 const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
-                if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0];
+                if (possibleDataPoints.length === 1) [dataPoint] = possibleDataPoints;
                 else {
                     dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]];
                     trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1;
                 }
-                var sliceColor;
+                let sliceColor;
                 if (dataPoint) {
                     const sliceTitle = dataPoint[this._props.axes[0]];
-                    const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
-                    sliceColors.forEach(each => each[0] == accessByName && (sliceColor = each[1]));
+                    const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
+                    sliceColors.forEach(each => {
+                        // eslint-disable-next-line prefer-destructuring
+                        each[0] === accessByName && (sliceColor = each[1]);
+                    });
                 }
                 return sliceColor ? StrCast(sliceColor) : d3.schemeSet3[i] ? d3.schemeSet3[i] : d3.schemeSet3[i % d3.schemeSet3.length];
             })
-            .attr(
-                'class',
-                selected
-                    ? function (d) {
-                          return selected && d.startAngle == selected.startAngle && d.endAngle == selected.endAngle ? 'slice hover' : 'slice';
-                      }
-                    : function (d) {
-                          return 'slice';
-                      }
-            )
+            .attr('class', selected ? d => (selected && d.startAngle === selected.startAngle && d.endAngle === selected.endAngle ? 'slice hover' : 'slice') : () => 'slice')
             // @ts-ignore
             .attr('d', arc)
             .on('click', onPointClick)
@@ -293,20 +287,22 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
 
         // adding labels
         trackDuplicates = {};
-        data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null));
+        data.forEach((eachData: any) => {
+            !trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null;
+        });
         arcs.size() < 100 &&
             arcs
                 .append('text')
-                .attr('transform', function (d) {
-                    var centroid = arc.centroid(d as unknown as d3.DefaultArcObject);
-                    var heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]);
+                .attr('transform', d => {
+                    const centroid = arc.centroid(d as unknown as d3.DefaultArcObject);
+                    const heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]);
                     return 'translate(' + (centroid[0] + centroid[0] / (radius * 0.02)) + ',' + (centroid[1] + heightOffset) + ')';
                 })
                 .attr('text-anchor', 'middle')
-                .text(function (d) {
-                    var dataPoint;
+                .text(d => {
+                    let dataPoint;
                     const possibleDataPoints = possibleDataPointVals.filter((pval: any) => pval[percentField] === Number(d.data));
-                    if (possibleDataPoints.length == 1) dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[0])];
+                    if (possibleDataPoints.length === 1) dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[0])];
                     else {
                         dataPoint = pieDataSet[possibleDataPointVals.indexOf(possibleDataPoints[trackDuplicates[d.data.toString()]])];
                         trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1;
@@ -318,11 +314,11 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
     @action changeSelectedColor = (color: string) => {
         this.curSliceSelected.attr('fill', color);
         const sliceTitle = this._currSelected[this._props.axes[0]];
-        const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
+        const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
 
         const sliceColors = Cast(this._props.layoutDoc.dataViz_pie_sliceColors, listSpec('string'), null);
-        sliceColors.map(each => {
-            if (each.split('::')[0] == sliceName) sliceColors.splice(sliceColors.indexOf(each), 1);
+        sliceColors.forEach(each => {
+            if (each.split('::')[0] === sliceName) sliceColors.splice(sliceColors.indexOf(each), 1);
         });
         sliceColors.push(StrCast(sliceName + '::' + color));
     };
@@ -333,38 +329,39 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
     };
 
     render() {
-        var titleAccessor: any = 'dataViz_pie_title';
-        if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
-        else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
+        let titleAccessor: any = 'dataViz_pie_title';
+        if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+        else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
         if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
         if (!this._props.layoutDoc.dataViz_pie_sliceColors) this._props.layoutDoc.dataViz_pie_sliceColors = new List<string>();
-        var selected: string;
-        var curSelectedSliceName = '';
+        let selected: string;
+        let curSelectedSliceName = '';
         if (this._currSelected) {
             selected = '{ ';
             const sliceTitle = this._currSelected[this._props.axes[0]];
-            curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
-            Object.keys(this._currSelected).map(key => {
-                key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
+            curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
+            Object.keys(this._currSelected).forEach(key => {
+                key !== '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
             });
             selected = selected.substring(0, selected.length - 2);
             selected += ' }';
-            if (this._props.titleCol != '' && (!this._currSelected['frequency'] || this._currSelected['frequency'] < 10)) {
+            if (this._props.titleCol !== '' && (!this._currSelected.frequency || this._currSelected.frequency < 10)) {
                 selected += '\n' + this._props.titleCol + ': ';
                 this._tableData.forEach(each => {
-                    if (this._currSelected[this._props.axes[0]] == each[this._props.axes[0]]) {
+                    if (this._currSelected[this._props.axes[0]] === each[this._props.axes[0]]) {
                         if (this._props.axes[1]) {
-                            if (this._currSelected[this._props.axes[1]] == each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', ';
+                            if (this._currSelected[this._props.axes[1]] === each[this._props.axes[1]]) selected += each[this._props.titleCol] + ', ';
                         } else selected += each[this._props.titleCol] + ', ';
                     }
                 });
                 selected = selected.slice(0, -1).slice(0, -1);
             }
         } else selected = 'none';
-        var selectedSliceColor;
-        var sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
+        let selectedSliceColor;
+        const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
         sliceColors.forEach(each => {
-            if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1];
+            // eslint-disable-next-line prefer-destructuring
+            if (each[0] === curSelectedSliceName!) selectedSliceColor = each[1];
         });
 
         if (this._pieChartData.length > 0 || !this.parentViz) {
@@ -374,30 +371,32 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
                         <EditableText
                             val={StrCast(this._props.layoutDoc[titleAccessor])}
                             setVal={undoable(
-                                action(val => (this._props.layoutDoc[titleAccessor] = val as string)),
+                                action(val => {
+                                    this._props.layoutDoc[titleAccessor] = val as string;
+                                }),
                                 'Change Graph Title'
                             )}
-                            color={'black'}
+                            color="black"
                             size={Size.LARGE}
                             fillWidth
                         />
                     </div>
                     {this._props.axes.length === 1 && /\d/.test(this._props.records[0][this._props.axes[0]]) ? (
-                        <div className={'asHistogram-checkBox'} style={{ width: this._props.width }}>
+                        <div className="asHistogram-checkBox" style={{ width: this._props.width }}>
                             <Checkbox color="primary" onChange={this.changeHistogramCheckBox} checked={this._props.layoutDoc.dataViz_pie_asHistogram as boolean} />
                             Organize data as histogram
                         </div>
                     ) : null}
                     <div ref={this._piechartRef} />
-                    {selected != 'none' ? (
-                        <div className={'selected-data'}>
+                    {selected !== 'none' ? (
+                        <div className="selected-data">
                             Selected: {selected}
                             &nbsp; &nbsp;
                             <ColorPicker
-                                tooltip={'Change Slice Color'}
+                                tooltip="Change Slice Color"
                                 type={Type.SEC}
                                 icon={<FaFillDrip />}
-                                selectedColor={selectedSliceColor ? selectedSliceColor : this.curSliceSelected.attr('fill')}
+                                selectedColor={selectedSliceColor || this.curSliceSelected.attr('fill')}
                                 setFinalColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Slice Color')}
                                 setSelectedColor={undoable(color => this.changeSelectedColor(color), 'Change Selected Slice Color')}
                                 size={Size.XSMALL}
@@ -406,12 +405,12 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
                     ) : null}
                 </div>
             ) : (
-                <span className="chart-container"> {'first use table view to select a column to graph'}</span>
-            );
-        } else
-            return (
-                // when it is a brushed table and the incoming table doesn't have any rows selected
-                <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+                <span className="chart-container"> first use table view to select a column to graph</span>
             );
+        }
+        return (
+            // when it is a brushed table and the incoming table doesn't have any rows selected
+            <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
+        );
     }
 }
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
index 336935d23..be05c3529 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -5,11 +5,11 @@ import { DataPoint } from '../components/LineChart';
 
 export const minMaxRange = (dataPts: DataPoint[][]) => {
     // find the max and min of all the data points
-    const yMin = d3.min(dataPts, d => d3.min(d, d => Number(d.y)));
-    const yMax = d3.max(dataPts, d => d3.max(d, d => Number(d.y)));
+    const yMin = d3.min(dataPts, d => d3.min(d, m => Number(m.y)));
+    const yMax = d3.max(dataPts, d => d3.max(d, m => Number(m.y)));
 
-    const xMin = d3.min(dataPts, d => d3.min(d, d => Number(d.x)));
-    const xMax = d3.max(dataPts, d => d3.max(d, d => Number(d.x)));
+    const xMin = d3.min(dataPts, d => d3.min(d, m => Number(m.x)));
+    const xMax = d3.max(dataPts, d => d3.max(d, m => Number(m.x)));
 
     return { xMin, xMax, yMin, yMax };
 };
@@ -20,18 +20,15 @@ export const scaleCreatorCategorical = (labels: string[], range: number[]) => {
     return scale;
 };
 
-export const scaleCreatorNumerical = (domA: number, domB: number, rangeA: number, rangeB: number) => {
-    return d3.scaleLinear().domain([domA, domB]).range([rangeA, rangeB]);
-};
+export const scaleCreatorNumerical = (domA: number, domB: number, rangeA: number, rangeB: number) => d3.scaleLinear().domain([domA, domB]).range([rangeA, rangeB]);
 
-export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) => {
+export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) =>
     // TODO: nda - look into the different types of curves
-    return d3
+    d3
         .line<DataPoint>()
         .x(d => xScale(d.x))
         .y(d => yScale(d.y))
         .curve(d3.curveMonotoneX);
-};
 
 export const xAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, height: number, xScale: d3.ScaleLinear<number, number, never>) => {
     g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15));
@@ -48,7 +45,7 @@ export const xGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, he
             d3
                 .axisBottom(scale)
                 .tickSize(-height)
-                .tickFormat((a, b) => '')
+                .tickFormat((/* a, b */) => '')
         );
 };
 
@@ -57,10 +54,16 @@ export const yGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, wi
         d3
             .axisLeft(scale)
             .tickSize(-width)
-            .tickFormat((a, b) => '')
+            .tickFormat((/* a, b */) => '')
     );
 };
 
 export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>, extra: boolean) => {
-    p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('stroke', extra? 'blue' : 'black').attr('class', 'line').attr('d', lineGen);
+    p.datum(dataPts)
+        .attr('fill', 'none')
+        .attr('stroke', 'rgba(53, 162, 235, 0.5)')
+        .attr('stroke-width', 2)
+        .attr('stroke', extra ? 'blue' : 'black')
+        .attr('class', 'line')
+        .attr('d', lineGen);
 };
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index a029b3761..9b4e36509 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -92,7 +92,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
             e,
             this.onLinkButtonMoved,
             emptyFunction,
-            action((e, doubleTap) => {
+            action((clickEv, doubleTap) => {
                 doubleTap && DocumentView.showBackLinks(this._props.View.Document);
             }),
             undefined,
@@ -109,7 +109,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
             e,
             this.onLinkButtonMoved,
             emptyFunction,
-            action((e, doubleTap) => {
+            action((clickEv, doubleTap) => {
                 if (doubleTap && this._props.InMenu && this._props.StartLink) {
                     // action(() => Doc.BrushDoc(this._props.View.Document));
                     if (DocumentLinksButton.StartLink === this._props.View.Document) {
@@ -146,7 +146,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
             e,
             returnFalse,
             emptyFunction,
-            action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View))
+            action(clickEv => DocumentLinksButton.finishLinkClick(clickEv.clientX, clickEv.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View))
         );
     };
 
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 9be66ba4a..9c216cba4 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { action, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -42,7 +43,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     this._ref.current!.mathField.latex(text);
                 }
             }
-            //{ fireImmediately: true }
+            // { fireImmediately: true }
         );
         reaction(
             () => this._props.isSelected(),
@@ -88,7 +89,9 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (e.key === 'Backspace' && !this.dataDoc.text) this._props.removeDocument?.(this.Document);
     };
     @undoBatch
-    onChange = (str: string) => (this.dataDoc.text = str);
+    onChange = (str: string) => {
+        this.dataDoc.text = str;
+    };
 
     updateSize = () => {
         const style = this._ref.current && getComputedStyle(this._ref.current.element.current);
@@ -111,7 +114,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
         return (
             <div
-                ref={r => this.updateSize()}
+                ref={() => this.updateSize()}
                 className="equationBox-cont"
                 onPointerDown={e => !e.ctrlKey && e.stopPropagation()}
                 style={{
@@ -122,7 +125,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     fontSize: StrCast(this.layoutDoc._text_fontSize),
                 }}
                 onKeyDown={e => e.stopPropagation()}>
-                <EquationEditor ref={this._ref} value={StrCast(this.dataDoc.text, 'x')} spaceBehavesLikeTab={true} onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" />
+                <EquationEditor ref={this._ref} value={StrCast(this.dataDoc.text, 'x')} spaceBehavesLikeTab onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" />
             </div>
         );
     }
diff --git a/src/client/views/nodes/FaceRectangle.tsx b/src/client/views/nodes/FaceRectangle.tsx
index 46bc6eb03..2b66b83fe 100644
--- a/src/client/views/nodes/FaceRectangle.tsx
+++ b/src/client/views/nodes/FaceRectangle.tsx
@@ -8,11 +8,17 @@ export default class FaceRectangle extends React.Component<{ rectangle: Rectangl
     @observable private opacity = 0;
 
     componentDidMount() {
-        setTimeout(() => runInAction(() => (this.opacity = 1)), 500);
+        setTimeout(
+            () =>
+                runInAction(() => {
+                    this.opacity = 1;
+                }),
+            500
+        );
     }
 
     render() {
-        const rectangle = this.props.rectangle;
+        const { rectangle } = this.props;
         return (
             <div
                 style={{
diff --git a/src/client/views/nodes/FontIconBox/ButtonInterface.ts b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
index 1c034bfbe..0d0d7b1c3 100644
--- a/src/client/views/nodes/FontIconBox/ButtonInterface.ts
+++ b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
@@ -1,5 +1,5 @@
-import { Doc } from '../../../../fields/Doc';
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { Doc } from '../../../../fields/Doc';
 import { ButtonType } from './FontIconBox';
 
 export interface IButtonProps {
diff --git a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx
index 09fd6e3ae..76f00b2f4 100644
--- a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx
+++ b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx
@@ -1,10 +1,11 @@
 import * as React from 'react';
 
-const TrailsIcon = (fill: string) => (
-    <svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 1080.000000 1080.000000" preserveAspectRatio="xMidYMid meet">
-        <g transform="translate(0.000000,1080.000000) scale(0.100000,-0.100000)" fill={fill} stroke="none">
-            <path
-                d="M665 9253 c-74 -10 -157 -38 -240 -81 -74 -37 -107 -63 -186 -141
+function TrailsIcon(fill: string) {
+    return (
+        <svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 1080.000000 1080.000000" preserveAspectRatio="xMidYMid meet">
+            <g transform="translate(0.000000,1080.000000) scale(0.100000,-0.100000)" fill={fill} stroke="none">
+                <path
+                    d="M665 9253 c-74 -10 -157 -38 -240 -81 -74 -37 -107 -63 -186 -141
 -104 -104 -156 -191 -201 -334 l-23 -72 0 -3215 c0 -3072 1 -3218 18 -3280 10
 -36 39 -108 64 -160 40 -82 59 -107 142 -190 81 -81 111 -103 191 -143 52 -26
 122 -55 155 -65 57 -16 322 -17 4775 -20 3250 -2 4736 1 4784 8 256 39 486
@@ -14,68 +15,69 @@ const TrailsIcon = (fill: string) => (
 -62 -101 -108 -126 l-42 -22 -4435 -3 c-3954 -2 -4440 0 -4481 13 -26 9 -63
 33 -87 56 -79 79 -72 -205 -72 3012 0 2156 3 2889 12 2918 20 70 91 136 168
 160 14 4 2010 8 4436 8 3710 1 4418 -1 4456 -13z"
-            />
-            <path
-                d="M7692 7839 c-46 -14 -109 -80 -122 -128 -7 -27 -9 -472 -8 -1443 l3
+                />
+                <path
+                    d="M7692 7839 c-46 -14 -109 -80 -122 -128 -7 -27 -9 -472 -8 -1443 l3
 -1403 24 -38 c13 -21 42 -50 64 -65 l41 -27 816 0 816 0 41 27 c22 15 51 44
 64 65 l24 38 0 1425 0 1425 -24 38 c-13 21 -42 50 -64 65 l-41 27 -800 2
 c-488 1 -814 -2 -834 -8z"
-            />
-            <path
-                d="M1982 7699 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -308 -8 -893 l3
+                />
+                <path
+                    d="M1982 7699 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -308 -8 -893 l3
 -853 24 -38 c13 -21 42 -50 64 -65 l41 -27 1386 0 1386 0 41 27 c22 15 51 44
 64 65 l24 38 0 876 0 875 -27 41 c-15 22 -44 51 -65 64 l-38 24 -1370 2 c-847
 1 -1383 -2 -1403 -8z"
-            />
-            <path
-                d="M6413 7093 c-13 -2 -23 -9 -23 -15 0 -24 21 -307 26 -343 l5 -40 182
+                />
+                <path
+                    d="M6413 7093 c-13 -2 -23 -9 -23 -15 0 -24 21 -307 26 -343 l5 -40 182
 -1 c200 -1 307 -15 484 -65 57 -16 107 -29 112 -29 5 0 36 75 69 168 33 92 63
 175 67 184 6 14 -10 22 -92 48 -126 39 -308 76 -447 89 -106 11 -337 13 -383
 4z"
-            />
-            <path
-                d="M5840 7033 c-63 -8 -238 -29 -388 -47 -150 -18 -274 -35 -276 -37 -2
+                />
+                <path
+                    d="M5840 7033 c-63 -8 -238 -29 -388 -47 -150 -18 -274 -35 -276 -37 -2
 -2 8 -89 23 -194 22 -163 29 -190 44 -193 10 -2 91 6 180 17 89 12 258 32 376
 46 118 14 216 27 218 28 7 8 -43 391 -52 392 -5 1 -62 -4 -125 -12z"
-            />
-            <path
-                d="M4762 4789 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -323 -8 -943 l3
+                />
+                <path
+                    d="M4762 4789 c-46 -14 -109 -80 -122 -128 -7 -27 -10 -323 -8 -943 l3
 -903 24 -38 c13 -21 42 -50 64 -65 l41 -27 926 0 926 0 41 27 c22 15 51 44 64
 65 l24 38 0 926 0 925 -27 41 c-15 22 -44 51 -65 64 l-38 24 -910 2 c-557 1
 -923 -2 -943 -8z"
-            />
-            <path
-                d="M8487 4297 c-26 -215 -161 -474 -307 -585 -27 -20 -49 -40 -49 -44
+                />
+                <path
+                    d="M8487 4297 c-26 -215 -161 -474 -307 -585 -27 -20 -49 -40 -49 -44
 -1 -3 49 -79 110 -167 l110 -161 44 31 c176 126 333 350 418 594 30 86 77 282
 77 320 0 8 -57 19 -167 34 -93 13 -182 25 -199 28 -31 5 -31 5 -37 -50z"
-            />
-            <path
-                d="M3965 4233 c-106 -9 -348 -36 -415 -47 -55 -8 -75 -15 -74 -26 1 -20
+                />
+                <path
+                    d="M3965 4233 c-106 -9 -348 -36 -415 -47 -55 -8 -75 -15 -74 -26 1 -20
 56 -374 59 -377 1 -2 46 4 101 12 159 24 409 45 526 45 l108 0 0 200 0 200
 -132 -2 c-73 -1 -151 -3 -173 -5z"
-            />
-            <path
-                d="M3020 4079 c-85 -23 -292 -94 -368 -125 -97 -40 -298 -140 -305 -151
+                />
+                <path
+                    d="M3020 4079 c-85 -23 -292 -94 -368 -125 -97 -40 -298 -140 -305 -151
 -5 -7 172 -315 192 -336 4 -4 41 10 82 32 103 55 272 123 414 165 66 20 125
 38 132 41 11 4 -4 70 -78 348 -10 39 -14 41 -69 26z"
-            />
-            <path
-                d="M6955 3538 c-21 -91 -74 -362 -72 -364 7 -7 260 -44 367 -54 146 -13
+                />
+                <path
+                    d="M6955 3538 c-21 -91 -74 -362 -72 -364 7 -7 260 -44 367 -54 146 -13
 359 -13 475 0 49 6 90 12 91 13 2 1 -12 90 -29 197 -26 155 -36 194 -47 192
 -8 -2 -85 -6 -170 -9 -160 -6 -357 7 -505 33 -103 18 -104 18 -110 -8z"
-            />
-            <path
-                d="M1993 3513 c-52 -67 -71 -106 -98 -198 -35 -122 -44 -284 -21 -415 9
+                />
+                <path
+                    d="M1993 3513 c-52 -67 -71 -106 -98 -198 -35 -122 -44 -284 -21 -415 9
 -51 18 -96 21 -98 4 -5 360 79 375 88 7 4 7 24 0 60 -21 109 -7 244 31 307
 l20 31 -146 131 c-80 72 -147 131 -149 131 -2 0 -17 -17 -33 -37z"
-            />
-            <path
-                d="M2210 2519 c-91 -50 -166 -92 -168 -94 -2 -1 11 -26 28 -54 l32 -51
+                />
+                <path
+                    d="M2210 2519 c-91 -50 -166 -92 -168 -94 -2 -1 11 -26 28 -54 l32 -51
 244 0 c134 0 244 2 244 5 0 3 -23 33 -51 67 -28 35 -72 98 -97 140 -26 43 -51
 77 -57 77 -5 0 -84 -41 -175 -90z"
-            />
-        </g>
-    </svg>
-);
+                />
+            </g>
+        </svg>
+    );
+}
 
 export default TrailsIcon;
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index bff6d53da..d43241de0 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -45,7 +45,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     onPointerDown = (e: React.PointerEvent) => {
         const { linkSource } = this;
         linkSource &&
-            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
+            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (clickEv, doubleTap) => {
                 if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false);
                 else this._props.select(false);
             });
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 2593491cc..f01905ee1 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -50,15 +51,19 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this._props.setContentViewBox?.(this);
         this._disposers.deleting = reaction(
             () => !this.anchor1 && !this.anchor2 && this.DocumentView?.() && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView!())),
-            empty => empty && ((this._hackToSeeIfDeleted = setTimeout(() => 
-                (!this.anchor1 && !this.anchor2) && this._props.removeDocument?.(this.Document)
-            )), 1000) // prettier-ignore
+            empty => {
+                if (empty) {
+                    this._hackToSeeIfDeleted = setTimeout(() => {
+                        !this.anchor1 && !this.anchor2 && this._props.removeDocument?.(this.Document);
+                    }, 1000);
+                }
+            }
         );
         this._disposers.dragging = reaction(
             () => SnappingManager.IsDragging,
             () => setTimeout( action(() => {// need to wait for drag manager to set 'hidden' flag on dragged DOM elements
-                const a = this.anchor1,
-                      b = this.anchor2;
+                const a = this.anchor1;
+                const b = this.anchor2;
                 let a1 = a && document.getElementById(a.ViewGuid);
                 let a2 = b && document.getElementById(b.ViewGuid);
                 // test whether the anchors themselves are hidden,...
@@ -67,7 +72,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     // .. or whether any of their DOM parents are hidden
                     for (; a1 && !a1.hidden; a1 = a1.parentElement);
                     for (; a2 && !a2.hidden; a2 = a2.parentElement);
-                    this._hide = a1 || a2 ? true : false;
+                    this._hide = !!(a1 || a2);
                 }
             })) // prettier-ignore
         );
@@ -92,24 +97,25 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             a.Document[DocCss];
             b.Document[DocCss];
 
+            // eslint-disable-next-line @typescript-eslint/no-unused-vars
             const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove)
             const bxf = b.screenToViewTransform();
             const scale = docView?.screenToViewTransform().Scale ?? 1;
             const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
             const bt = b.getBounds?.transition; //    inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
 
-            var foundParent = false;
+            let foundParent = false;
             const getAnchor = (field: FieldResult): Element[] => {
                 const docField = DocCast(field);
                 const doc = docField?.layout_unrendered ? DocCast(docField.annotationOn, docField) : docField;
                 const ele = document.getElementById(DocumentView.UniquifyId(LightboxView.Contains(this.DocumentView?.()), doc[Id]));
                 if (ele?.className === 'linkBox-label') foundParent = true;
                 if (ele?.getBoundingClientRect().width) return [ele];
-                const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(ele => ele?.getBoundingClientRect().width);
+                const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(el => el?.getBoundingClientRect().width);
                 const annoOn = DocCast(doc.annotationOn);
                 if (eles.length || !annoOn) return eles;
                 const pareles = getAnchor(annoOn);
-                foundParent = pareles.length ? true : false;
+                foundParent = !!pareles.length;
                 return pareles;
             };
             //   if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>),
@@ -122,26 +128,38 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const aid = targetAhyperlinks?.find(alink => container?.contains(alink))?.id ?? targetAhyperlinks?.lastElement()?.id;
             const bid = targetBhyperlinks?.find(blink => container?.contains(blink))?.id ?? targetBhyperlinks?.lastElement()?.id;
             if (!aid || !bid) {
-                setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
+                setTimeout(
+                    action(() => {
+                        this._forceAnimate += 0.01;
+                    })
+                );
                 return null;
             }
             if (foundParent) {
                 setTimeout(
-                    action(() => (this._forceAnimate = this._forceAnimate + 0.01)),
+                    action(() => {
+                        this._forceAnimate += 0.01;
+                    }),
                     1
                 );
             }
 
-            if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
+            if (at || bt)
+                setTimeout(
+                    action(() => {
+                        this._forceAnimate += 0.01;
+                    })
+                ); // this forces an update during a transition animation
             const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
             const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
             const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
             const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
             const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
             const fontColor = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor));
-            const { stroke_markerScale, stroke_width, stroke_startMarker, stroke_endMarker, stroke_dash } = this.Document;
+            // eslint-disable-next-line camelcase
+            const { stroke_markerScale: strokeMarkerScale, stroke_width: strokeRawWidth, stroke_startMarker: strokeStartMarker, stroke_endMarker: strokeEndMarker, stroke_dash: strokeDash } = this.Document;
 
-            const strokeWidth = NumCast(stroke_width, 4);
+            const strokeWidth = NumCast(strokeRawWidth, 4);
             const linkDesc = StrCast(this.dataDoc.link_description) || ' ';
             const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '');
             return (
@@ -152,12 +170,12 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             start={aid}
                             end={bid} //
                             strokeWidth={strokeWidth + Math.max(2, strokeWidth * 0.1)}
-                            showHead={stroke_startMarker ? true : false}
-                            showTail={stroke_endMarker ? true : false}
-                            headSize={NumCast(stroke_markerScale, 3)}
-                            tailSize={NumCast(stroke_markerScale, 3)}
-                            tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
-                            headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+                            showHead={!!strokeStartMarker}
+                            showTail={!!strokeEndMarker}
+                            headSize={NumCast(strokeMarkerScale, 3)}
+                            tailSize={NumCast(strokeMarkerScale, 3)}
+                            tailShape={strokeEndMarker === 'dot' ? 'circle' : 'arrow1'}
+                            headShape={strokeStartMarker === 'dot' ? 'circle' : 'arrow1'}
                             color={highlightColor}
                         />
                     )}
@@ -166,23 +184,23 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         start={aid}
                         end={bid} //
                         strokeWidth={strokeWidth}
-                        dashness={Number(stroke_dash) ? true : false}
-                        showHead={stroke_startMarker ? true : false}
-                        showTail={stroke_endMarker ? true : false}
-                        headSize={NumCast(stroke_markerScale, 3)}
-                        tailSize={NumCast(stroke_markerScale, 3)}
-                        tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
-                        headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+                        dashness={!!Number(strokeDash)}
+                        showHead={!!strokeStartMarker}
+                        showTail={!!strokeEndMarker}
+                        headSize={NumCast(strokeMarkerScale, 3)}
+                        tailSize={NumCast(strokeMarkerScale, 3)}
+                        tailShape={strokeEndMarker === 'dot' ? 'circle' : 'arrow1'}
+                        headShape={strokeStartMarker === 'dot' ? 'circle' : 'arrow1'}
                         color={color}
                         labels={
                             <div
                                 id={this.DocumentView?.().DocUniqueId}
-                                className={'linkBox-label'}
+                                className="linkBox-label"
                                 style={{
                                     borderRadius: '8px',
                                     pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
                                     fontSize,
-                                    fontFamily /*, fontStyle: 'italic'*/,
+                                    fontFamily /* , fontStyle: 'italic' */,
                                     color: fontColor || lightOrDark(DashColor(color).fade(0.5).toString()),
                                     paddingLeft: 4,
                                     paddingRight: 4,
@@ -223,16 +241,19 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
 
         setTimeout(
-            action(() => (this._forceAnimate = this._forceAnimate + 1)),
+            action(() => {
+                this._forceAnimate += 1;
+            }),
             2
         );
         return (
             <div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
                 <ComparisonBox
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this.props} //
                     fieldKey="link_anchor"
                     setHeight={emptyFunction}
-                    dontRegisterView={true}
+                    dontRegisterView
                     renderDepth={this._props.renderDepth + 1}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 2a96ce458..23cb25962 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -9,6 +9,7 @@ import { StrCast } from '../../../fields/Types';
 
 @observer
 export class LinkDescriptionPopup extends React.Component<{}> {
+    // eslint-disable-next-line no-use-before-define
     public static Instance: LinkDescriptionPopup;
     @observable public display: boolean = false;
     @observable public showDescriptions: string = 'ON';
@@ -23,6 +24,20 @@ export class LinkDescriptionPopup extends React.Component<{}> {
         LinkDescriptionPopup.Instance = this;
     }
 
+    componentDidMount() {
+        document.addEventListener('pointerdown', this.onClick, true);
+        reaction(
+            () => this.display,
+            display => {
+                display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description));
+            }
+        );
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener('pointerdown', this.onClick, true);
+    }
+
     @action
     descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.description = e.currentTarget.value;
@@ -39,25 +54,13 @@ export class LinkDescriptionPopup extends React.Component<{}> {
 
     @action
     onClick = (e: PointerEvent) => {
-        if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
+        if (this.popupRef && !this.popupRef.current?.contains(e.target as any)) {
             this.display = false;
             this.description = '';
             TaskCompletionBox.taskCompleted = false;
         }
     };
 
-    componentDidMount() {
-        document.addEventListener('pointerdown', this.onClick, true);
-        reaction(
-            () => this.display,
-            display => display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description))
-        );
-    }
-
-    componentWillUnmount() {
-        document.removeEventListener('pointerdown', this.onClick, true);
-    }
-
     render() {
         return !this.display ? null : (
             <div
@@ -78,11 +81,11 @@ export class LinkDescriptionPopup extends React.Component<{}> {
                     onChange={e => this.descriptionChanged(e)}
                 />
                 <div className="linkDescriptionPopup-btn">
-                    <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={e => this.onDismiss(false)}>
+                    <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={() => this.onDismiss(false)}>
                         {' '}
                         Dismiss{' '}
                     </div>
-                    <div className="linkDescriptionPopup-btn-add" onPointerDown={e => this.onDismiss(true)}>
+                    <div className="linkDescriptionPopup-btn-add" onPointerDown={() => this.onDismiss(true)}>
                         {' '}
                         Add{' '}
                     </div>
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 539daf0bd..a9cfe6c0e 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -23,35 +23,41 @@ import { DocumentView, OpenWhere } from './DocumentView';
 import { StyleProviderFuncType } from './FieldView';
 import './LinkDocPreview.scss';
 
+interface LinkDocPreviewProps {
+    linkDoc?: Doc;
+    linkSrc?: Doc;
+    DocumentView?: () => DocumentView;
+    styleProvider?: StyleProviderFuncType;
+    location: number[];
+    hrefs?: string[];
+    showHeader?: boolean;
+    noPreview?: boolean;
+}
 export class LinkInfo {
+    // eslint-disable-next-line no-use-before-define
     private static _instance: Opt<LinkInfo>;
     constructor() {
         LinkInfo._instance = this;
         makeObservable(this);
     }
+    // eslint-disable-next-line no-use-before-define
     @observable public LinkInfo: Opt<LinkDocPreviewProps> = undefined;
 
     public static get Instance() {
         return LinkInfo._instance ?? new LinkInfo();
     }
     public static Clear() {
-        runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = undefined));
+        runInAction(() => {
+            LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = undefined);
+        });
     }
     public static SetLinkInfo(info?: LinkDocPreviewProps) {
-        runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info));
+        runInAction(() => {
+            LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info);
+        });
     }
 }
 
-interface LinkDocPreviewProps {
-    linkDoc?: Doc;
-    linkSrc?: Doc;
-    DocumentView?: () => DocumentView;
-    styleProvider?: StyleProviderFuncType;
-    location: number[];
-    hrefs?: string[];
-    showHeader?: boolean;
-    noPreview?: boolean;
-}
 @observer
 export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps> {
     _infoRef = React.createRef<HTMLDivElement>();
@@ -69,13 +75,13 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
 
     @action
     init() {
-        var linkTarget = this._props.linkDoc;
+        let linkTarget = this._props.linkDoc;
         this._linkSrc = this._props.linkSrc;
         this._linkDoc = this._props.linkDoc;
-        const link_anchor_1 = DocCast(this._linkDoc?.link_anchor_1);
-        const link_anchor_2 = DocCast(this._linkDoc?.link_anchor_2);
-        if (link_anchor_1 && link_anchor_2) {
-            linkTarget = Doc.AreProtosEqual(link_anchor_1, this._linkSrc) || Doc.AreProtosEqual(link_anchor_1?.annotationOn as Doc, this._linkSrc) ? link_anchor_2 : link_anchor_1;
+        const linkAnchor1 = DocCast(this._linkDoc?.link_anchor_1);
+        const linkAnchor2 = DocCast(this._linkDoc?.link_anchor_2);
+        if (linkAnchor1 && linkAnchor2) {
+            linkTarget = Doc.AreProtosEqual(linkAnchor1, this._linkSrc) || Doc.AreProtosEqual(linkAnchor1?.annotationOn as Doc, this._linkSrc) ? linkAnchor2 : linkAnchor1;
         }
         if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) {
             linkTarget = DocCast(linkTarget.annotationOn); // want to show annotation embedContainer document if annotation is not text
@@ -111,7 +117,13 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                 if (href.startsWith('https://en.wikipedia.org/wiki/')) {
                     wiki()
                         .page(href.replace('https://en.wikipedia.org/wiki/', ''))
-                        .then(page => page.summary().then(action(summary => (this._toolTipText = summary.substring(0, 500)))));
+                        .then(page =>
+                            page.summary().then(
+                                action(summary => {
+                                    this._toolTipText = summary.substring(0, 500);
+                                })
+                            )
+                        );
                 } else {
                     this._toolTipText = 'url => ' + href;
                 }
@@ -133,7 +145,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                                 this._linkSrc = anchor;
                                 const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
                                 this._markerTargetDoc = linkTarget;
-                                this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+                                this._targetDoc = /* linkTarget?.type === DocumentType.MARKER && */ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
                             }
                             if (LinkInfo.Instance?.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink();
                         }
@@ -194,7 +206,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                 willPan: true,
                 zoomTime: 500,
             });
-            //this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
+            // this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
         }
     };
 
@@ -249,9 +261,9 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                         setupMoveUpEvents(
                             this,
                             e,
-                            (e, down, delta) => {
-                                if (Math.abs(e.clientX - down[0]) + Math.abs(e.clientY - down[1]) > 100) {
-                                    DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY);
+                            (moveEv, down) => {
+                                if (Math.abs(moveEv.clientX - down[0]) + Math.abs(moveEv.clientY - down[1]) > 100) {
+                                    DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), moveEv.pageX, moveEv.pageY);
                                     LinkInfo.Clear();
                                     return true;
                                 }
@@ -284,18 +296,18 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                             removeDocument={returnFalse}
                             addDocTab={returnFalse}
                             pinToPres={returnFalse}
-                            dontRegisterView={true}
+                            dontRegisterView
                             childFilters={returnEmptyFilter}
                             childFiltersByRanges={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
                             renderDepth={0}
-                            suppressSetHeight={true}
+                            suppressSetHeight
                             PanelWidth={this.width}
                             PanelHeight={this.height}
                             pointerEvents={returnNone}
                             focus={emptyFunction}
                             whenChildContentsActiveChanged={returnFalse}
-                            ignoreAutoHeight={true} // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size.
+                            ignoreAutoHeight // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size.
                             NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
                             NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
                         />
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index adccc9db6..501831bca 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -60,7 +60,9 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         } else {
             const updateFunc = async () => {
                 const result = await Networking.QueryYoutubeProgress(StrCast(this.Document[Id])); // We use the guid of the overwriteDoc to track file uploads.
-                runInAction(() => (this.progress = result.progress));
+                runInAction(() => {
+                    this.progress = result.progress;
+                });
                 !this.Document.loadingError && this._timer && (this._timer = setTimeout(updateFunc, 1000));
             };
             this._timer = setTimeout(updateFunc, 1000);
diff --git a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
index d54a175b2..f4ece627f 100644
--- a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
+++ b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx
@@ -1,35 +1,44 @@
-import * as React from "react";
+import * as React from 'react';
 
 export const slowSpeedIcon: JSX.Element = (
     <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 435.62">
         <defs>
-        <style type="text/css">
-            {`
+            <style type="text/css">
+                {`
                 .fil0 { fill: black; fill-rule: nonzero; }
                 .fil1 { fill: #FE0000; fill-rule: nonzero; }
             `}
-        </style>
+            </style>
         </defs>
-        <path className="fil0" d="M174.84 343.06c-7.31,-13.12 -13.03,-27.28 -16.89,-42.18 -3.76,-14.56 -5.77,-29.71 -5.77,-45.17 0,-11.94 1.19,-23.66 3.43,-35.03 2.29,-11.57 5.74,-22.83 10.2,-33.63 13.7,-33.14 37.01,-61.29 66.42,-80.96 25.38,-16.96 55.28,-27.66 87.45,-29.87l0 -30.17c0,-0.46 0.02,-0.92 0.06,-1.37l-33.7 0c-5.53,0 -10.05,-4.52 -10.05,-10.04l0 -24.59c0,-5.53 4.52,-10.05 10.05,-10.05l101.27 0c5.53,0 10.05,4.52 10.05,10.05l0 24.59c0,5.52 -4.52,10.04 -10.05,10.04l-33.69 0c0.03,0.45 0.05,0.91 0.05,1.37l0 31.03 -0.1 0c41.1,4.89 77.94,23.63 105.73,51.42 32.56,32.55 52.7,77.54 52.7,127.21 0,49.67 -20.14,94.66 -52.7,127.21 -32.55,32.55 -77.54,52.7 -127.21,52.7 -33.16,0 -64.29,-9.04 -91.05,-24.78 -27.66,-16.27 -50.59,-39.73 -66.2,-67.78zm148.42 -36.62l-80.33 0 0 -25.71 28.6 0 0 -42.57 -28.6 1.93 0 -25.71 36.95 -8.35 25.38 0 0 74.7 18 0 0 25.71zm44.34 -100.41l11.08 26.83 1.61 0 11.09 -26.83 34.86 0 -22.33 48.52 22.33 51.89 -35.67 0 -12.05 -28.92 -1.44 0 -11.89 28.92 -34.06 0 21.85 -50.93 -21.85 -49.48 36.47 0zm126.08 -74.6c6.98,-16.66 6.15,-34.13 -3.84,-45.82 -12,-14.03 -33.67,-15.64 -53.8,-5.77 21.32,14.62 40.68,31.63 57.64,51.59zm-323.17 0c-6.98,-16.66 -6.16,-34.13 3.84,-45.82 11.99,-14.03 33.67,-15.64 53.79,-5.77 -21.32,14.62 -40.68,31.63 -57.63,51.59zm15.31 162.23c3.23,12.5 8.04,24.39 14.18,35.42 13.13,23.58 32.39,43.29 55.6,56.94 22.37,13.16 48.52,20.71 76.49,20.71 41.71,0 79.47,-16.9 106.8,-44.23 27.32,-27.32 44.23,-65.08 44.23,-106.79 0,-41.71 -16.91,-79.47 -44.23,-106.8 -27.33,-27.32 -65.09,-44.23 -106.8,-44.23 -31.07,0 -59.91,9.34 -83.84,25.33 -24.74,16.54 -44.33,40.19 -55.82,67.98 -3.68,8.91 -6.56,18.35 -8.5,28.22 -1.87,9.49 -2.86,19.36 -2.86,29.5 0,13.24 1.65,25.96 4.75,37.95z"/>
-        <path className="fil1" d="M55.23 188.52c-7.98,0 -14.45,-6.47 -14.45,-14.44 0,-7.98 6.47,-14.45 14.45,-14.45l63.94 0c7.98,0 14.45,6.47 14.45,14.45 0,7.97 -6.47,14.44 -14.45,14.44l-63.94 0zm0.72 167.68c-7.97,0 -14.44,-6.47 -14.44,-14.45 0,-7.97 6.47,-14.45 14.44,-14.45l64.58 0c7.97,0 14.45,6.48 14.45,14.45 0,7.98 -6.48,14.45 -14.45,14.45l-64.58 0zm-41.5 -84.94c-7.98,0 -14.45,-6.47 -14.45,-14.45 0,-7.97 6.47,-14.44 14.45,-14.44l89.12 0c7.98,0 14.45,6.47 14.45,14.44 0,7.98 -6.47,14.45 -14.45,14.45l-89.12 0z"/>
+        <path
+            className="fil0"
+            d="M174.84 343.06c-7.31,-13.12 -13.03,-27.28 -16.89,-42.18 -3.76,-14.56 -5.77,-29.71 -5.77,-45.17 0,-11.94 1.19,-23.66 3.43,-35.03 2.29,-11.57 5.74,-22.83 10.2,-33.63 13.7,-33.14 37.01,-61.29 66.42,-80.96 25.38,-16.96 55.28,-27.66 87.45,-29.87l0 -30.17c0,-0.46 0.02,-0.92 0.06,-1.37l-33.7 0c-5.53,0 -10.05,-4.52 -10.05,-10.04l0 -24.59c0,-5.53 4.52,-10.05 10.05,-10.05l101.27 0c5.53,0 10.05,4.52 10.05,10.05l0 24.59c0,5.52 -4.52,10.04 -10.05,10.04l-33.69 0c0.03,0.45 0.05,0.91 0.05,1.37l0 31.03 -0.1 0c41.1,4.89 77.94,23.63 105.73,51.42 32.56,32.55 52.7,77.54 52.7,127.21 0,49.67 -20.14,94.66 -52.7,127.21 -32.55,32.55 -77.54,52.7 -127.21,52.7 -33.16,0 -64.29,-9.04 -91.05,-24.78 -27.66,-16.27 -50.59,-39.73 -66.2,-67.78zm148.42 -36.62l-80.33 0 0 -25.71 28.6 0 0 -42.57 -28.6 1.93 0 -25.71 36.95 -8.35 25.38 0 0 74.7 18 0 0 25.71zm44.34 -100.41l11.08 26.83 1.61 0 11.09 -26.83 34.86 0 -22.33 48.52 22.33 51.89 -35.67 0 -12.05 -28.92 -1.44 0 -11.89 28.92 -34.06 0 21.85 -50.93 -21.85 -49.48 36.47 0zm126.08 -74.6c6.98,-16.66 6.15,-34.13 -3.84,-45.82 -12,-14.03 -33.67,-15.64 -53.8,-5.77 21.32,14.62 40.68,31.63 57.64,51.59zm-323.17 0c-6.98,-16.66 -6.16,-34.13 3.84,-45.82 11.99,-14.03 33.67,-15.64 53.79,-5.77 -21.32,14.62 -40.68,31.63 -57.63,51.59zm15.31 162.23c3.23,12.5 8.04,24.39 14.18,35.42 13.13,23.58 32.39,43.29 55.6,56.94 22.37,13.16 48.52,20.71 76.49,20.71 41.71,0 79.47,-16.9 106.8,-44.23 27.32,-27.32 44.23,-65.08 44.23,-106.79 0,-41.71 -16.91,-79.47 -44.23,-106.8 -27.33,-27.32 -65.09,-44.23 -106.8,-44.23 -31.07,0 -59.91,9.34 -83.84,25.33 -24.74,16.54 -44.33,40.19 -55.82,67.98 -3.68,8.91 -6.56,18.35 -8.5,28.22 -1.87,9.49 -2.86,19.36 -2.86,29.5 0,13.24 1.65,25.96 4.75,37.95z"
+        />
+        <path
+            className="fil1"
+            d="M55.23 188.52c-7.98,0 -14.45,-6.47 -14.45,-14.44 0,-7.98 6.47,-14.45 14.45,-14.45l63.94 0c7.98,0 14.45,6.47 14.45,14.45 0,7.97 -6.47,14.44 -14.45,14.44l-63.94 0zm0.72 167.68c-7.97,0 -14.44,-6.47 -14.44,-14.45 0,-7.97 6.47,-14.45 14.44,-14.45l64.58 0c7.97,0 14.45,6.48 14.45,14.45 0,7.98 -6.48,14.45 -14.45,14.45l-64.58 0zm-41.5 -84.94c-7.98,0 -14.45,-6.47 -14.45,-14.45 0,-7.97 6.47,-14.44 14.45,-14.44l89.12 0c7.98,0 14.45,6.47 14.45,14.44 0,7.98 -6.47,14.45 -14.45,14.45l-89.12 0z"
+        />
     </svg>
 );
 
 export const mediumSpeedIcon: JSX.Element = (
     <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 104.55">
-        <defs><style>{`.cls-1{fill:#fe0000;}`}</style></defs>
-        <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.45l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.09l5.36,12.45H93.74L90.85,66.6H90.5l-2.85,6.94H79.47l5.25-12.22L79.47,49.45ZM58.65,56.08l-1-5.75a33.58,33.58,0,0,1,9.68-1.46c1.28,0,2.35,0,3.22.11a11.77,11.77,0,0,1,2.67.58,5.41,5.41,0,0,1,2.2,1.28c1.24,1.23,1.85,3.12,1.85,5.66s-.72,4.42-2.16,5.63S70.64,64.73,66,66.3v1.08H76.89v6.16H57.11V68.72a10.73,10.73,0,0,1,.81-4.12,8.4,8.4,0,0,1,2.43-2.7,12.13,12.13,0,0,1,2.79-1.7l3.32-1.52c1-.47,1.88-.87,2.52-1.17V55.42a28.59,28.59,0,0,0-3.2-.19,30.66,30.66,0,0,0-7.13.85Zm59.83-24.54c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z"/>
-        <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Z"/>
-        <path className="cls-1" d="M3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z"/>
-        <path className="cls-1" d="M13.43,85.49a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94Z"/>
+        <defs>
+            <style>{`.cls-1{fill:#fe0000;}`}</style>
+        </defs>
+        <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.45l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.09l5.36,12.45H93.74L90.85,66.6H90.5l-2.85,6.94H79.47l5.25-12.22L79.47,49.45ZM58.65,56.08l-1-5.75a33.58,33.58,0,0,1,9.68-1.46c1.28,0,2.35,0,3.22.11a11.77,11.77,0,0,1,2.67.58,5.41,5.41,0,0,1,2.2,1.28c1.24,1.23,1.85,3.12,1.85,5.66s-.72,4.42-2.16,5.63S70.64,64.73,66,66.3v1.08H76.89v6.16H57.11V68.72a10.73,10.73,0,0,1,.81-4.12,8.4,8.4,0,0,1,2.43-2.7,12.13,12.13,0,0,1,2.79-1.7l3.32-1.52c1-.47,1.88-.87,2.52-1.17V55.42a28.59,28.59,0,0,0-3.2-.19,30.66,30.66,0,0,0-7.13.85Zm59.83-24.54c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z" />
+        <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Z" />
+        <path className="cls-1" d="M3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z" />
+        <path className="cls-1" d="M13.43,85.49a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94Z" />
     </svg>
 );
 
 export const fastSpeedIcon: JSX.Element = (
     <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 104.55">
-        <defs><style>{`.cls-1{fill:#fe0000;`}</style></defs>
-        <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.61l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.26l5.36,12.45H93.74l-2.9-6.94H90.5l-2.86,6.94H79.47l5.24-12.22L79.47,49.61Zm-19,8.48v-2.5a24.92,24.92,0,0,0-3.74-.2A33.25,33.25,0,0,0,59,56.2l-1-5.7A30.47,30.47,0,0,1,67.13,49a22.86,22.86,0,0,1,5.48.47,6.91,6.91,0,0,1,2.5,1.11,5.62,5.62,0,0,1,1.78,4.55,5.84,5.84,0,0,1-3.2,5.56v.19a5.73,5.73,0,0,1,3.81,5.74,8.67,8.67,0,0,1-.63,3.49,6,6,0,0,1-1.6,2.24,7.15,7.15,0,0,1-2.55,1.25,25.64,25.64,0,0,1-6.61.66,37.78,37.78,0,0,1-8.54-1l1.08-6.37a27.22,27.22,0,0,0,6.21.89,35.79,35.79,0,0,0,4.35-.23V65.11l-6.63-.65V58.87l6.63-.78Zm49.27-26.55c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z"/>
-        <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Zm.18,40.24a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94ZM3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z"/>
+        <defs>
+            <style>{`.cls-1{fill:#fe0000;`}</style>
+        </defs>
+        <path d="M42,82.34a42.82,42.82,0,0,1-4.05-10.13A43.2,43.2,0,0,1,76.72,18.29V11.05c0-.11,0-.22,0-.33H68.65a2.41,2.41,0,0,1-2.41-2.41V2.41A2.41,2.41,0,0,1,68.65,0H93a2.42,2.42,0,0,1,2.42,2.41v5.9A2.42,2.42,0,0,1,93,10.72H84.87c0,.11,0,.22,0,.33V18.5h0A43.17,43.17,0,1,1,42,82.34ZM88.22,49.61l2.66,6.44h.39l2.66-6.44h8.37L96.94,61.26l5.36,12.45H93.74l-2.9-6.94H90.5l-2.86,6.94H79.47l5.24-12.22L79.47,49.61Zm-19,8.48v-2.5a24.92,24.92,0,0,0-3.74-.2A33.25,33.25,0,0,0,59,56.2l-1-5.7A30.47,30.47,0,0,1,67.13,49a22.86,22.86,0,0,1,5.48.47,6.91,6.91,0,0,1,2.5,1.11,5.62,5.62,0,0,1,1.78,4.55,5.84,5.84,0,0,1-3.2,5.56v.19a5.73,5.73,0,0,1,3.81,5.74,8.67,8.67,0,0,1-.63,3.49,6,6,0,0,1-1.6,2.24,7.15,7.15,0,0,1-2.55,1.25,25.64,25.64,0,0,1-6.61.66,37.78,37.78,0,0,1-8.54-1l1.08-6.37a27.22,27.22,0,0,0,6.21.89,35.79,35.79,0,0,0,4.35-.23V65.11l-6.63-.65V58.87l6.63-.78Zm49.27-26.55c1.68-4,1.48-8.19-.92-11-2.88-3.37-8.08-3.76-12.91-1.39a69.74,69.74,0,0,1,13.83,12.38Zm-77.56,0c-1.67-4-1.48-8.19.92-11,2.88-3.37,8.08-3.76,12.91-1.39A70,70,0,0,0,40.92,31.54ZM44.6,70.48A36,36,0,0,0,48,79a35.91,35.91,0,1,0-3.4-8.5Z" />
+        <path className="cls-1" d="M13.25,45.25a3.47,3.47,0,0,1,0-6.94H28.6a3.47,3.47,0,0,1,0,6.94Zm.18,40.24a3.47,3.47,0,1,1,0-6.94h15.5a3.47,3.47,0,0,1,0,6.94ZM3.47,65.1a3.47,3.47,0,1,1,0-6.93H24.86a3.47,3.47,0,0,1,0,6.93Z" />
     </svg>
 );
-
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
index 35153f439..cf5315da3 100644
--- a/src/client/views/nodes/MapBox/AnimationUtility.ts
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -87,25 +87,24 @@ export class AnimationUtility {
     @computed get currentPitch(): number {
         if (!this.isStreetViewAnimation) return 50;
         if (!this.terrainDisplayed) return 80;
-        else {
-            // const groundElevation = 0;
-            const heightAboveGround = this.currentAnimationAltitude;
-            const horizontalDistance = 500;
-
-            let pitch;
-            if (heightAboveGround >= 0) {
-                pitch = 90 - Math.atan(heightAboveGround / horizontalDistance) * (180 / Math.PI);
-            } else {
-                pitch = 80;
-            }
 
-            console.log(Math.max(50, Math.min(pitch, 85)));
+        // const groundElevation = 0;
+        const heightAboveGround = this.currentAnimationAltitude;
+        const horizontalDistance = 500;
 
-            if (this.previousPitch) {
-                return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02);
-            }
-            return Math.max(50, Math.min(pitch, 85));
+        let pitch;
+        if (heightAboveGround >= 0) {
+            pitch = 90 - Math.atan(heightAboveGround / horizontalDistance) * (180 / Math.PI);
+        } else {
+            pitch = 80;
+        }
+
+        console.log(Math.max(50, Math.min(pitch, 85)));
+
+        if (this.previousPitch) {
+            return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02);
         }
+        return Math.max(50, Math.min(pitch, 85));
     }
 
     @computed get flyInEndPitch() {
@@ -214,8 +213,8 @@ export class AnimationUtility {
         currentAnimationPhase: number;
         updateAnimationPhase: (newAnimationPhase: number) => void;
         updateFrameId: (newFrameId: number) => void;
-    }) => {
-        return new Promise<void>(async resolve => {
+    }) =>
+        new Promise<void>(async resolve => {
             let startTime: number | null = null;
 
             const frame = async (currentTime: number) => {
@@ -257,7 +256,7 @@ export class AnimationUtility {
                 updateAnimationPhase(animationPhase);
 
                 // compute corrected camera ground position, so that he leading edge of the path is in view
-                var correctedPosition = this.computeCameraPosition(
+                const correctedPosition = this.computeCameraPosition(
                     this.isStreetViewAnimation,
                     this.currentPitch,
                     bearing,
@@ -277,7 +276,7 @@ export class AnimationUtility {
                 map.setFreeCameraOptions(camera);
 
                 this.previousAltitude = this.currentAnimationAltitude;
-                this.previousPitch = this.previousPitch;
+                // this.previousPitch = this.previousPitch;
 
                 // repeat!
                 const innerFrameId = await window.requestAnimationFrame(frame);
@@ -287,15 +286,14 @@ export class AnimationUtility {
             const outerFrameId = await window.requestAnimationFrame(frame);
             updateFrameId(outerFrameId);
         });
-    };
 
-    public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) => {
-        return new Promise<{ bearing: number; altitude: number }>(async resolve => {
+    public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) =>
+        new Promise<{ bearing: number; altitude: number }>(async resolve => {
             let start: number | null;
 
-            var currentAltitude;
-            var currentBearing;
-            var currentPitch;
+            let currentAltitude;
+            let currentBearing;
+            let currentPitch;
 
             // the animation frame will run as many times as necessary until the duration has been reached
             const frame = async (time: number) => {
@@ -319,7 +317,7 @@ export class AnimationUtility {
                 currentPitch = this.FLY_IN_START_PITCH + (this.flyInEndPitch - this.FLY_IN_START_PITCH) * d3.easeCubicOut(animationPhase);
 
                 // compute corrected camera ground position, so the start of the path is always in view
-                var correctedPosition = this.computeCameraPosition(false, currentPitch, currentBearing, this.FIRST_LNG_LAT, currentAltitude);
+                const correctedPosition = this.computeCameraPosition(false, currentPitch, currentBearing, this.FIRST_LNG_LAT, currentAltitude);
 
                 // set the pitch and bearing of the camera
                 const camera = map.getFreeCameraOptions();
@@ -349,13 +347,10 @@ export class AnimationUtility {
             const outerFrameId = await window.requestAnimationFrame(frame);
             updateFrameId(outerFrameId);
         });
-    };
 
     previousCameraPosition: { lng: number; lat: number } | null = null;
 
-    lerp = (start: number, end: number, amt: number) => {
-        return (1 - amt) * start + amt * end;
-    };
+    lerp = (start: number, end: number, amt: number) => (1 - amt) * start + amt * end;
 
     computeCameraPosition = (isStreetViewAnimation: boolean, pitch: number, bearing: number, targetPosition: { lng: number; lat: number }, altitude: number, smooth = false) => {
         const bearingInRadian = (bearing * Math.PI) / 180;
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
index fe7f8d8b3..f176317d2 100644
--- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -14,6 +14,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 
 @observer
 export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: DirectionsAnchorMenu;
 
     private _disposer: IReactionDisposer | undefined;
@@ -24,8 +25,8 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public OnClick: (e: PointerEvent) => void = unimplementedFunction;
     // public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
-    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
-    public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = () => undefined;
+    public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
     public Delete: () => void = unimplementedFunction;
     // public MakeTargetToggle: () => void = unimplementedFunction;
     // public ShowTargetTrail: () => void = unimplementedFunction;
@@ -56,7 +57,7 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     componentDidMount() {
         this._disposer = reaction(
             () => SelectionManager.Views.slice(),
-            sel => DirectionsAnchorMenu.Instance.fadeOut(true)
+            () => DirectionsAnchorMenu.Instance.fadeOut(true)
         );
     }
     // audioDown = (e: React.PointerEvent) => {
@@ -104,8 +105,8 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                     color={SettingsManager.userColor}
                 />
 
-                <IconButton tooltip="Animate route" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />
-                <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
+                <IconButton tooltip="Animate route" onPointerDown={this.Delete} /* *TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />
+                <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /* *TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
             </div>
         );
 
diff --git a/src/client/views/nodes/MapBox/GeocoderControl.tsx b/src/client/views/nodes/MapBox/GeocoderControl.tsx
index e4ba51316..e118c57d9 100644
--- a/src/client/views/nodes/MapBox/GeocoderControl.tsx
+++ b/src/client/views/nodes/MapBox/GeocoderControl.tsx
@@ -3,8 +3,6 @@
 // import { ControlPosition, MarkerProps, useControl } from "react-map-gl";
 
 // import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css'
-
-
 // export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
 //     mapboxAccessToken: string;
 //     marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
@@ -31,7 +29,6 @@
 //           ctrl.on('results', props.onResults);
 //           ctrl.on('result', evt => {
 //             props.onResult(evt);
-    
 //             // const {result} = evt;
 //             // const location =
 //             //   result &&
@@ -49,8 +46,6 @@
 //           position: props.position
 //         }
 //       );
-
-
 //     // @ts-ignore (TS2339) private member
 //     if (geocoder._map) {
 //         if (geocoder.getProximity() !== props.proximity && props.proximity !== undefined) {
@@ -104,4 +99,4 @@
 //   onLoading: noop,
 //   onResults: noop,
 //   onError: noop
-// };
\ No newline at end of file
+// };
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index d17c4298c..174511e1a 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/button-has-type */
 import { IconLookup, faAdd, faArrowDown, faArrowLeft, faArrowsRotate, faBicycle, faCalendarDays, faCar, faDiamondTurnRight, faEdit, faPersonWalking, faRoute } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
@@ -24,6 +25,7 @@ type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize'
 
 @observer
 export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: MapAnchorMenu;
 
     private _disposer: IReactionDisposer | undefined;
@@ -36,8 +38,8 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public OnClick: (e: PointerEvent) => void = unimplementedFunction;
     // public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
-    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
-    public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
+    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = () => undefined;
+    public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
     public Delete: () => void = unimplementedFunction;
     // public MakeTargetToggle: () => void = unimplementedFunction;
     // public ShowTargetTrail: () => void = unimplementedFunction;
@@ -125,7 +127,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     componentDidMount() {
         this._disposer = reaction(
             () => SelectionManager.Views.slice(),
-            sel => MapAnchorMenu.Instance.fadeOut(true)
+            () => MapAnchorMenu.Instance.fadeOut(true)
         );
     }
     // audioDown = (e: React.PointerEvent) => {
@@ -148,12 +150,12 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         setupMoveUpEvents(
             this,
             e,
-            (e: PointerEvent) => {
-                this.StartDrag(e, this._commentRef.current!);
+            moveEv => {
+                this.StartDrag(moveEv, this._commentRef.current!);
                 return true;
             },
             returnFalse,
-            e => this.OnClick(e)
+            clickEv => this.OnClick(clickEv)
         );
     };
 
@@ -275,7 +277,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     HandleAddRouteClick = () => {
         if (this.currentRouteInfoMap && this.selectedTransportationType && this.selectedDestinationFeature) {
-            const coordinates = this.currentRouteInfoMap[this.selectedTransportationType].coordinates;
+            const { coordinates } = this.currentRouteInfoMap[this.selectedTransportationType];
             console.log(coordinates);
             console.log(this.selectedDestinationFeature);
             this.AddNewRouteToMap(coordinates, this.title ?? '', this.selectedDestinationFeature, this.createPinForDestination);
@@ -294,34 +296,30 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     getDirectionsButton: JSX.Element = (<IconButton tooltip="Get directions" onPointerDown={this.DirectionsClick} icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} color={SettingsManager.userColor} />);
 
-    getAddToCalendarButton = (docType: string): JSX.Element => {
-        return (
-            <IconButton
-                tooltip="Add to calendar"
-                onPointerDown={() => {
-                    CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc);
-                }}
-                icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />}
-                color={SettingsManager.userColor}
-            />
-        );
-    };
+    getAddToCalendarButton = (docType: string): JSX.Element => (
+        <IconButton
+            tooltip="Add to calendar"
+            onPointerDown={() => {
+                CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc);
+            }}
+            icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />}
+            color={SettingsManager.userColor}
+        />
+    );
     addToCalendarButton: JSX.Element = (
         <IconButton tooltip="Add to calendar" onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)} icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
     );
 
-    getLinkNoteToDocButton = (docType: string): JSX.Element => {
-        return (
-            <div ref={this._commentRef}>
-                <IconButton
-                    tooltip={`Link Note to ${docType === 'pin' ? 'Pin' : 'Route'}`} //
-                    onPointerDown={this.notePointerDown}
-                    icon={<FontAwesomeIcon icon="sticky-note" />}
-                    color={SettingsManager.userColor}
-                />
-            </div>
-        );
-    };
+    getLinkNoteToDocButton = (docType: string): JSX.Element => (
+        <div ref={this._commentRef}>
+            <IconButton
+                tooltip={`Link Note to ${docType === 'pin' ? 'Pin' : 'Route'}`} //
+                onPointerDown={this.notePointerDown}
+                icon={<FontAwesomeIcon icon="sticky-note" />}
+                color={SettingsManager.userColor}
+            />
+        </div>
+    );
 
     linkNoteToPinOrRoutenButton: JSX.Element = (
         <div ref={this._commentRef}>
@@ -363,16 +361,14 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         />
     );
 
-    getDeleteButton = (type: string) => {
-        return (
-            <IconButton
-                tooltip={`Delete ${type === 'pin' ? 'Pin' : 'Route'}`} //
-                onPointerDown={this.Delete}
-                icon={<FontAwesomeIcon icon="trash-alt" />}
-                color={SettingsManager.userColor}
-            />
-        );
-    };
+    getDeleteButton = (type: string) => (
+        <IconButton
+            tooltip={`Delete ${type === 'pin' ? 'Pin' : 'Route'}`} //
+            onPointerDown={this.Delete}
+            icon={<FontAwesomeIcon icon="trash-alt" />}
+            color={SettingsManager.userColor}
+        />
+    );
 
     animateRouteButton: JSX.Element = (<IconButton tooltip="Animate route" onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />);
 
@@ -453,18 +449,17 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             }}
                             options={this.destinationFeatures.filter(feature => feature.place_name).map(feature => feature)}
                             getOptionLabel={(feature: any) => feature.place_name}
+                            // eslint-disable-next-line react/jsx-props-no-spreading
                             renderInput={(params: any) => <TextField {...params} placeholder="Enter a destination" />}
                         />
-                        {this.selectedDestinationFeature && (
-                            <>
-                                {!this.allMapPinDocs.some(pinDoc => pinDoc.title === this.selectedDestinationFeature.place_name) && (
-                                    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '5px' }}>
-                                        <FormControlLabel label="Create pin for destination?" control={<Checkbox color="success" checked={this.createPinForDestination} onChange={this.toggleCreatePinForDestinationCheckbox} />} />
-                                    </div>
-                                )}
-                            </>
-                        )}
-                        <button id="get-routes-button" disabled={this.selectedDestinationFeature ? false : true} onClick={() => this.getRoutes(this.selectedDestinationFeature)}>
+                        {!this.selectedDestinationFeature
+                            ? null
+                            : !this.allMapPinDocs.some(pinDoc => pinDoc.title === this.selectedDestinationFeature.place_name) && (
+                                  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '5px' }}>
+                                      <FormControlLabel label="Create pin for destination?" control={<Checkbox color="success" checked={this.createPinForDestination} onChange={this.toggleCreatePinForDestinationCheckbox} />} />
+                                  </div>
+                              )}
+                        <button id="get-routes-button" disabled={!this.selectedDestinationFeature} onClick={() => this.getRoutes(this.selectedDestinationFeature)}>
                             Get routes
                         </button>
 
@@ -517,7 +512,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                                 </div>
                             ))}
                         </div>
-                        <div style={{ width: '100%', height: '3px', color: 'white' }}></div>
+                        <div style={{ width: '100%', height: '3px', color: 'white' }} />
                     </div>
                 )}
                 {this.menuType === 'route' && this.routeDoc && <div>{StrCast(this.routeDoc.title)}</div>}
diff --git a/src/client/views/nodes/MapBox/MapboxApiUtility.ts b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
index 592330ac2..5c5192372 100644
--- a/src/client/views/nodes/MapBox/MapboxApiUtility.ts
+++ b/src/client/views/nodes/MapBox/MapboxApiUtility.ts
@@ -1,4 +1,3 @@
-
 const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
 const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
 const MAPBOX_DIRECTIONS_BASE_URL = 'https://api.mapbox.com/directions/v5/mapbox';
@@ -7,92 +6,79 @@ const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3
 export type TransportationType = 'driving' | 'cycling' | 'walking';
 
 export class MapboxApiUtility {
-
     static forwardGeocodeForFeatures = async (searchText: string) => {
         try {
-            const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`;
+            const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) + `.json?access_token=${MAPBOX_ACCESS_TOKEN}`;
             const response = await fetch(url);
             const data = await response.json();
             return data.features;
-        } catch (error: any){
-            // TODO: handle error in better way 
+        } catch (error: any) {
+            // TODO: handle error in better way
             return null;
         }
-    }
+    };
 
     static reverseGeocodeForFeatures = async (longitude: number, latitude: number) => {
         try {
-            const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + "," + latitude.toString()) + '.json' +
-                `?access_token=${MAPBOX_ACCESS_TOKEN}`;
+            const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + ',' + latitude.toString()) + `.json?access_token=${MAPBOX_ACCESS_TOKEN}`;
             const response = await fetch(url);
             const data = await response.json();
             return data.features;
-        } catch (error: any){
+        } catch (error: any) {
             return null;
         }
-    }
+    };
 
     static getDirections = async (origin: number[], destination: number[]): Promise<Record<TransportationType, any> | undefined> => {
         try {
-
             const directionsPromises: Promise<any>[] = [];
             const transportationTypes: TransportationType[] = ['driving', 'cycling', 'walking'];
 
-            transportationTypes.forEach((type) => {
-                directionsPromises.push(
-                  fetch(
-                    `${MAPBOX_DIRECTIONS_BASE_URL}/${type}/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`
-                  ).then((response) => response.json())
-                );
-              });
+            transportationTypes.forEach(type => {
+                directionsPromises.push(fetch(`${MAPBOX_DIRECTIONS_BASE_URL}/${type}/${origin[0]},${origin[1]};${destination[0]},${destination[1]}?steps=true&geometries=geojson&access_token=${MAPBOX_ACCESS_TOKEN}`).then(response => response.json()));
+            });
 
             const results = await Promise.all(directionsPromises);
 
             const routeInfoMap: Record<TransportationType, any> = {
-                'driving': {},
-                'cycling': {},
-                'walking': {},
+                driving: {},
+                cycling: {},
+                walking: {},
             };
 
             transportationTypes.forEach((type, index) => {
                 const routeData = results[index].routes[0];
                 if (routeData) {
-                  const geometry = routeData.geometry;
-                  const coordinates = geometry.coordinates;
-          
-                  routeInfoMap[type] = {
-                    duration: this.secondsToMinutesHours(routeData.duration),
-                    distance: this.metersToMiles(routeData.distance),
-                    coordinates: coordinates,
-                  };
+                    const { geometry } = routeData;
+                    const { coordinates } = geometry;
+
+                    routeInfoMap[type] = {
+                        duration: this.secondsToMinutesHours(routeData.duration),
+                        distance: this.metersToMiles(routeData.distance),
+                        coordinates: coordinates,
+                    };
                 }
-              });
+            });
 
             return routeInfoMap;
 
-            // return current route info, and the temporary route 
-
-        } catch (error: any){
+            // return current route info, and the temporary route
+        } catch (error: any) {
             return undefined;
-            console.log("Error: ", error);
         }
-    }
+    };
 
     private static secondsToMinutesHours = (seconds: number) => {
         const hours = Math.floor(seconds / 3600);
         const minutes = Math.floor((seconds % 3600) / 60).toFixed(2);
 
-        if (hours === 0){
-            return `${minutes} min`
-        } else {
-            return `${hours} hr ${minutes} min`
+        if (hours === 0) {
+            return `${minutes} min`;
         }
-    }
-
-    private static metersToMiles = (meters: number) => {
-        return `${parseFloat((meters/1609.34).toFixed(2))} mi`;
-    }   
+        return `${hours} hr ${minutes} min`;
+    };
 
+    private static metersToMiles = (meters: number) => `${parseFloat((meters / 1609.34).toFixed(2))} mi`;
 }
 
 // const drivingQuery = await fetch(
@@ -136,4 +122,4 @@ export class MapboxApiUtility {
 //         distance: this.metersToMiles(routeData.distance),
 //         coordinates: coordinates
 //     }
-// })
\ No newline at end of file
+// })
diff --git a/src/client/views/nodes/MapBox/MarkerIcons.tsx b/src/client/views/nodes/MapBox/MarkerIcons.tsx
index a580fcaa0..087472112 100644
--- a/src/client/views/nodes/MapBox/MarkerIcons.tsx
+++ b/src/client/views/nodes/MapBox/MarkerIcons.tsx
@@ -17,8 +17,6 @@ import {
     faHouse,
     faLandmark,
     faLocationDot,
-    faLocationPin,
-    faMapPin,
     faMasksTheater,
     faMugSaucer,
     faPersonHiking,
@@ -65,6 +63,7 @@ export class MarkerIcons {
             iconProps.color = color;
         }
 
+        // eslint-disable-next-line react/jsx-props-no-spreading
         return <FontAwesomeIcon {...iconProps} size={size} />;
     }
 
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 189e2105d..fce52ef4b 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -118,9 +118,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
      * @param sidebarKey
      * @returns
      */
-    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+    sidebarAddDocument = (docsIn: Doc | Doc[], sidebarKey?: string) => {
         if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
-        const docs = doc instanceof Doc ? [doc] : doc;
+        const docs = docsIn instanceof Doc ? [docsIn] : docsIn;
         docs.forEach(doc => {
             let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPin;
             if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) {
@@ -139,7 +139,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
             }
         }); // add to annotation list
 
-        return this.addDocument(doc, sidebarKey); // add to sidebar list
+        return this.addDocument(docs, sidebarKey); // add to sidebar list
     };
 
     removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => {
@@ -168,7 +168,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         setupMoveUpEvents(
             this,
             e,
-            (e, down, delta) =>
+            (moveEv, down, delta) =>
                 runInAction(() => {
                     const localDelta = this._props
                         .ScreenToLocalTransform()
@@ -243,10 +243,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         const docView = this.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
-                dragComplete: e => {
-                    if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
-                        e.annoDragData.linkSourceDoc.followLinkZoom = false;
+                dragComplete: dragEv => {
+                    if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
+                        dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.Document;
+                        dragEv.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
             });
@@ -680,23 +680,23 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         setupMoveUpEvents(
             e,
             e,
-            e => {
+            moveEv => {
                 if (!dragClone) {
                     dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement;
                     dragClone.style.position = 'absolute';
                     dragClone.style.zIndex = '10000';
                     DragManager.Root().appendChild(dragClone);
                 }
-                dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
+                dragClone.style.transform = `translate(${moveEv.clientX - 15}px, ${moveEv.clientY - 15}px)`;
                 return false;
             },
-            e => {
+            upEv => {
                 if (!dragClone) return;
                 DragManager.Root().removeChild(dragClone);
-                let target = document.elementFromPoint(e.x, e.y);
+                let target = document.elementFromPoint(upEv.x, upEv.y);
                 while (target) {
                     if (target === this._ref.current) {
-                        const cpt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY);
+                        const cpt = this.ScreenToLocalBoxXf().transformPoint(upEv.clientX, upEv.clientY);
                         const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
                         const y = cpt[1] - 32 /* height of search bar */ - this._props.PanelHeight() / 2;
                         const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
@@ -706,7 +706,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                     target = target.parentElement;
                 }
             },
-            e => {
+            () => {
                 const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map);
                 if (this.bingSearchBarContents) {
                     this.bingSearch().then(createPin);
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index 16450c359..48da4937a 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -6,22 +6,57 @@ import { RadialMenuItem, RadialMenuProps } from './RadialMenuItem';
 
 @observer
 export class RadialMenu extends React.Component {
+    // eslint-disable-next-line no-use-before-define
     static Instance: RadialMenu;
     static readonly buffer = 20;
 
+    @observable private _mouseX: number = -1;
+    @observable private _mouseY: number = -1;
+    @observable private _shouldDisplay: boolean = false;
+    @observable private _mouseDown: boolean = false;
+    @observable private _closest: number = -1;
+    @observable private _pageX: number = 0;
+    @observable private _pageY: number = 0;
+    @observable _display: boolean = false;
+    @observable private _yRelativeToTop: boolean = true;
+    @observable private _items: Array<RadialMenuProps> = [];
+    private _reactionDisposer?: IReactionDisposer;
+
     constructor(props: any) {
         super(props);
         makeObservable(this);
         RadialMenu.Instance = this;
     }
 
-    @observable private _mouseX: number = -1;
-    @observable private _mouseY: number = -1;
-    @observable private _shouldDisplay: boolean = false;
-    @observable private _mouseDown: boolean = false;
-    private _reactionDisposer?: IReactionDisposer;
+    componentDidMount() {
+        document.addEventListener('pointerdown', this.onPointerDown);
+        document.addEventListener('pointerup', this.onPointerUp);
+        this.previewcircle();
+        this._reactionDisposer = reaction(
+            () => this._shouldDisplay,
+            () =>
+                this._shouldDisplay &&
+                !this._mouseDown &&
+                runInAction(() => {
+                    this._display = true;
+                })
+        );
+    }
 
-    public used: boolean = false;
+    componentDidUpdate() {
+        this.previewcircle();
+    }
+    componentWillUnmount() {
+        document.removeEventListener('pointerdown', this.onPointerDown);
+
+        document.removeEventListener('pointerup', this.onPointerUp);
+        this._reactionDisposer && this._reactionDisposer();
+    }
+
+    @computed get menuItems() {
+        // eslint-disable-next-line react/jsx-props-no-spreading
+        return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />);
+    }
 
     catchTouch = (te: React.TouchEvent) => {
         te.stopPropagation();
@@ -33,13 +68,9 @@ export class RadialMenu extends React.Component {
         this._mouseDown = true;
         this._mouseX = e.clientX;
         this._mouseY = e.clientY;
-        this.used = false;
         document.addEventListener('pointermove', this.onPointerMove);
     };
 
-    @observable
-    private _closest: number = -1;
-
     @action
     onPointerMove = (e: PointerEvent) => {
         const curX = e.clientX;
@@ -65,7 +96,6 @@ export class RadialMenu extends React.Component {
     };
     @action
     onPointerUp = (e: PointerEvent) => {
-        this.used = true;
         this._mouseDown = false;
         const curX = e.clientX;
         const curY = e.clientY;
@@ -78,86 +108,6 @@ export class RadialMenu extends React.Component {
             this._items[this._closest].event();
         }
     };
-    componentWillUnmount() {
-        document.removeEventListener('pointerdown', this.onPointerDown);
-
-        document.removeEventListener('pointerup', this.onPointerUp);
-        this._reactionDisposer && this._reactionDisposer();
-    }
-
-    @action
-    componentDidMount() {
-        document.addEventListener('pointerdown', this.onPointerDown);
-        document.addEventListener('pointerup', this.onPointerUp);
-        this.previewcircle();
-        this._reactionDisposer = reaction(
-            () => this._shouldDisplay,
-            () => this._shouldDisplay && !this._mouseDown && runInAction(() => (this._display = true))
-        );
-    }
-
-    componentDidUpdate = () => {
-        this.previewcircle();
-    };
-
-    @observable private _pageX: number = 0;
-    @observable private _pageY: number = 0;
-    @observable _display: boolean = false;
-    @observable private _yRelativeToTop: boolean = true;
-
-    @observable private _width: number = 0;
-    @observable private _height: number = 0;
-
-    getItems() {
-        return this._items;
-    }
-
-    @action
-    addItem(item: RadialMenuProps) {
-        if (this._items.indexOf(item) === -1) {
-            this._items.push(item);
-        }
-    }
-
-    @observable
-    private _items: Array<RadialMenuProps> = [];
-
-    @action
-    displayMenu = (x: number, y: number) => {
-        //maxX and maxY will change if the UI/font size changes, but will work for any amount
-        //of items added to the menu
-        this._mouseX = x;
-        this._mouseY = y;
-        this._shouldDisplay = true;
-    };
-    // @computed
-    // get pageX() {
-    //     const x = this._pageX;
-    //     if (x < 0) {
-    //         return 0;
-    //     }
-    //     const width = this._width;
-    //     if (x + width > window.innerWidth - RadialMenu.buffer) {
-    //         return window.innerWidth - RadialMenu.buffer - width;
-    //     }
-    //     return x;
-    // }
-    // @computed
-    // get pageY() {
-    //     const y = this._pageY;
-    //     if (y < 0) {
-    //         return 0;
-    //     }
-    //     const height = this._height;
-    //     if (y + height > window.innerHeight - RadialMenu.buffer) {
-    //         return window.innerHeight - RadialMenu.buffer - height;
-    //     }
-    //     return y;
-    // }
-
-    @computed get menuItems() {
-        return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />);
-    }
 
     @action
     closeMenu = () => {
@@ -166,14 +116,6 @@ export class RadialMenu extends React.Component {
         this._shouldDisplay = false;
     };
 
-    @action
-    openMenu = (x: number, y: number) => {
-        this._pageX = x;
-        this._pageY = y;
-        this._shouldDisplay;
-        this._display = true;
-    };
-
     @action
     clearItems() {
         this._items = [];
diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx
index 91dc37d34..6f10e7b65 100644
--- a/src/client/views/nodes/RadialMenuItem.tsx
+++ b/src/client/views/nodes/RadialMenuItem.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { observer } from 'mobx-react';
@@ -53,6 +54,7 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
                 case 2:
                     color = 'lightgray';
                     break;
+                default:
             }
             if (circlemax % 3 === 1 && circlemin === circlemax - 1) {
                 color = '#c2c2c5';
@@ -80,7 +82,6 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
         const avg = (circlemin / circlemax + (circlemin + 1) / circlemax) / 2;
         const degrees = 360 * avg;
         const x = 100 * Math.cos((degrees * Math.PI) / 180);
-        const y = -125 * Math.sin((degrees * Math.PI) / 180);
         return x;
     }
 
@@ -91,7 +92,6 @@ export class RadialMenuItem extends React.Component<RadialMenuProps> {
         this.props.max ? (circlemax = this.props.max) : null;
         const avg = (circlemin / circlemax + (circlemin + 1) / circlemax) / 2;
         const degrees = 360 * avg;
-        const x = 125 * Math.cos((degrees * Math.PI) / 180);
         const y = -100 * Math.sin((degrees * Math.PI) / 180);
         return y;
     }
diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.tsx b/src/client/views/nodes/RecordingBox/ProgressBar.tsx
index 1bb2b7c84..62798bc2f 100644
--- a/src/client/views/nodes/RecordingBox/ProgressBar.tsx
+++ b/src/client/views/nodes/RecordingBox/ProgressBar.tsx
@@ -1,31 +1,33 @@
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable react/require-default-props */
 import * as React from 'react';
-import { useEffect, useState, useCallback, useRef } from "react"
-import "./ProgressBar.scss"
+import { useEffect, useState, useRef } from 'react';
+import './ProgressBar.scss';
 import { MediaSegment } from './RecordingView';
 
 interface ProgressBarProps {
-    videos: MediaSegment[],
-    setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>,
-    orderVideos: boolean,
-    progress: number,
-    recording: boolean,
-    doUndo: boolean,
-    setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>,
+    videos: MediaSegment[];
+    setVideos: React.Dispatch<React.SetStateAction<MediaSegment[]>>;
+    orderVideos: boolean;
+    progress: number;
+    recording: boolean;
+    doUndo: boolean;
+    setCanUndo?: React.Dispatch<React.SetStateAction<boolean>>;
 }
 
 interface SegmentBox {
-    endTime: number,
-    startTime: number,
-    order: number,
+    endTime: number;
+    startTime: number;
+    order: number;
 }
 interface CurrentHover {
-    index: number,
-    minX: number,
-    maxX: number
+    index: number;
+    minX: number;
+    maxX: number;
 }
 
 export function ProgressBar(props: ProgressBarProps) {
-    const progressBarRef = useRef<HTMLDivElement | null>(null)
+    const progressBarRef = useRef<HTMLDivElement | null>(null);
 
     // the actual list of JSX elements rendered as segments
     const [segments, setSegments] = useState<JSX.Element[]>([]);
@@ -47,8 +49,6 @@ export function ProgressBar(props: ProgressBarProps) {
     // update the canUndo props based on undo stack
     useEffect(() => props.setCanUndo?.(undoStack.length > 0), [undoStack.length]);
 
-    // useEffect for undo - brings back the most recently deleted segment
-    useEffect(() => handleUndo(), [props.doUndo])
     const handleUndo = () => {
         // get the last element from the undo if it exists
         if (undoStack.length === 0) return;
@@ -59,27 +59,36 @@ export function ProgressBar(props: ProgressBarProps) {
         // update the removed time and place element back into ordered
         setTotalRemovedTime(prevRemoved => prevRemoved - (last.endTime - last.startTime));
         setOrdered(prevOrdered => [...prevOrdered, last]);
-    }
+    };
+    // useEffect for undo - brings back the most recently deleted segment
+    useEffect(() => handleUndo(), [props.doUndo]);
 
     // useEffect for recording changes - changes style to disabled and adds the "expanding-segment"
     useEffect(() => {
         // get segments segment's html using it's id -> make them appeared disabled (or enabled)
-        segments.forEach((seg) => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording));
+        segments.forEach(seg => document.getElementById(seg.props.id)?.classList.toggle('segment-disabled', props.recording));
         progressBarRef.current?.classList.toggle('progressbar-disabled', props.recording);
 
         if (props.recording)
-            setSegments(prevSegments => [...prevSegments, <div key='segment-expanding' id='segment-expanding' className='segment segment-expanding blink' style={{ width: 'fit-content' }}>{props.videos.length + 1}</div>]);
-    }, [props.recording])
-
+            setSegments(prevSegments => [
+                ...prevSegments,
+                <div key="segment-expanding" id="segment-expanding" className="segment segment-expanding blink" style={{ width: 'fit-content' }}>
+                    {props.videos.length + 1}
+                </div>,
+            ]);
+    }, [props.recording]);
 
     // useEffect that updates the segmentsJSX, which is rendered
     // only updated when ordered is updated or if the user is dragging around a segment
     useEffect(() => {
         const totalTime = props.progress * 1000 - totalRemovedTime;
-        const segmentsJSX = ordered.map((seg, i) =>
-            <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>{seg.order + 1}</div>);
+        const segmentsJSX = ordered.map((seg, i) => (
+            <div key={`segment-${i}`} id={`segment-${i}`} className={dragged === i ? 'segment-hide' : 'segment'} style={{ width: `${((seg.endTime - seg.startTime) / totalTime) * 100}%` }}>
+                {seg.order + 1}
+            </div>
+        ));
 
-        setSegments(segmentsJSX)
+        setSegments(segmentsJSX);
     }, [dragged, ordered]);
 
     // useEffect for dragged - update the cursor to be grabbing while grabbing
@@ -89,14 +98,14 @@ export function ProgressBar(props: ProgressBarProps) {
 
     // to imporve performance, only want to update the CSS width, not re-render the whole JSXList
     useEffect(() => {
-        if (!props.recording) return
+        if (!props.recording) return;
         const totalTime = props.progress * 1000 - totalRemovedTime;
         let remainingTime = totalTime;
         segments.forEach((seg, i) => {
             // for the last segment, we need to set that directly
             if (i === segments.length - 1) return;
             // update remaining time
-            remainingTime -= (ordered[i].endTime - ordered[i].startTime);
+            remainingTime -= ordered[i].endTime - ordered[i].startTime;
 
             // update the width for this segment
             const htmlId = seg.props.id;
@@ -106,8 +115,7 @@ export function ProgressBar(props: ProgressBarProps) {
 
         // update the width of the expanding segment using the remaining time
         const segExapandHtml = document.getElementById('segment-expanding');
-        if (segExapandHtml)
-            segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`;
+        if (segExapandHtml) segExapandHtml.style.width = ordered.length === 0 ? '100%' : `${(remainingTime / totalTime) * 100}%`;
     }, [props.progress]);
 
     // useEffect for props.videos - update the ordered array when a new video is added
@@ -120,9 +128,7 @@ export function ProgressBar(props: ProgressBarProps) {
         // in this case, a new video is added -> push it onto ordered
         if (order >= ordered.length) {
             const { endTime, startTime } = props.videos.lastElement();
-            setOrdered(prevOrdered => {
-                return [...prevOrdered, { endTime, startTime, order }];
-            });
+            setOrdered(prevOrdered => [...prevOrdered, { endTime, startTime, order }]);
         }
 
         // in this case, a video is removed
@@ -132,7 +138,7 @@ export function ProgressBar(props: ProgressBarProps) {
     }, [props.videos]);
 
     // useEffect for props.orderVideos - matched the order array with the videos array before the export
-    useEffect(() => props.setVideos(vids => ordered.map((seg) => vids[seg.order])), [props.orderVideos]);
+    useEffect(() => props.setVideos(vids => ordered.map(seg => vids[seg.order])), [props.orderVideos]);
 
     // useEffect for removed - handles logic for removing a segment
     useEffect(() => {
@@ -151,36 +157,68 @@ export function ProgressBar(props: ProgressBarProps) {
     // returns the new currentHover based on the new index
     const updateCurrentHover = (segId: number): CurrentHover | null => {
         // get the segId of the segment that will become the new bounding area
-        const rect = progressBarRef.current?.children[segId].getBoundingClientRect()
-        if (rect == null) return null
+        const rect = progressBarRef.current?.children[segId].getBoundingClientRect();
+        if (rect == null) return null;
         return {
             index: segId,
             minX: rect.x,
             maxX: rect.x + rect.width,
-        }
-    }
+        };
+    };
+
+    const swapSegments = (oldIndex: number, newIndex: number) => {
+        if (newIndex == null) return;
+        setOrdered(prevOrdered => {
+            const temp = { ...prevOrdered[oldIndex] };
+            prevOrdered[oldIndex] = prevOrdered[newIndex];
+            prevOrdered[newIndex] = temp;
+            return prevOrdered;
+        });
+        // update visually where the segment is hovering over
+        setDragged(newIndex);
+    };
+
+    // functions for the floating segment that tracks the cursor while grabbing it
+    const initDetachSegment = (dot: HTMLDivElement, rect: DOMRect) => {
+        dot.classList.add('segment-selected');
+        dot.style.transitionDuration = '0s';
+        dot.style.position = 'absolute';
+        dot.style.zIndex = '999';
+        dot.style.width = `${rect.width}px`;
+        dot.style.height = `${rect.height}px`;
+        dot.style.left = `${rect.x}px`;
+        dot.style.top = `${rect.y}px`;
+        dot.draggable = false;
+        document.body.append(dot);
+    };
+    const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => {
+        // event.stopPropagation()
+        const { width, height } = dot.getBoundingClientRect();
+        dot.style.left = `${event.clientX - width / 2}px`;
+        dot.style.top = `${event.clientY - height / 2}px`;
+    };
 
     // pointerdown event for the progress bar
-  const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
-           // don't move the videobox element
-    e.stopPropagation();  
+    const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+        // don't move the videobox element
+        e.stopPropagation();
 
         // if recording, do nothing
-      if (props.recording) return;
+        if (props.recording) return;
 
         // get the segment the user clicked on to be dragged
-        const clickedSegment = e.target as HTMLDivElement & EventTarget
+        const clickedSegment = e.target as HTMLDivElement & EventTarget;
 
         // get the profess bar ro add event listeners
         // don't do anything if null
-        const progressBar = progressBarRef.current
-        if (progressBar == null || clickedSegment.id === progressBar.id) return
+        const progressBar = progressBarRef.current;
+        if (progressBar == null || clickedSegment.id === progressBar.id) return;
 
         // if holding shift key, let's remove that segment
         if (e.shiftKey) {
             const segId = parseInt(clickedSegment.id.split('-')[1]);
             setRemoved(segId);
-            return
+            return;
         }
 
         // if holding ctrl key and click, let's undo that segment #hiddenfeature lol
@@ -192,26 +230,26 @@ export function ProgressBar(props: ProgressBarProps) {
         // if we're here, the user is dragging a segment around
         // let the progress bar capture all the pointer events until the user releases (pointerUp)
         const ptrId = e.pointerId;
-        progressBar.setPointerCapture(ptrId)
+        progressBar.setPointerCapture(ptrId);
 
-        const rect = clickedSegment.getBoundingClientRect()
-        // id for segment is like 'segment-1' or 'segment-10', 
+        const rect = clickedSegment.getBoundingClientRect();
+        // id for segment is like 'segment-1' or 'segment-10',
         // so this works to get the id
-        const segId = parseInt(clickedSegment.id.split('-')[1])
+        const segId = parseInt(clickedSegment.id.split('-')[1]);
         // set the selected segment to be the one dragged
-        setDragged(segId)
+        setDragged(segId);
 
-        // this is the logic for storing the lower X bound and upper X bound to know 
+        // this is the logic for storing the lower X bound and upper X bound to know
         // whether a swap is needed between two segments
         let currentHover: CurrentHover = {
             index: segId,
             minX: rect.x,
             maxX: rect.x + rect.width,
-        }
+        };
 
         // create the floating segment that tracks the cursor
-        const detchedSegment = document.createElement("div")
-        initDeatchSegment(detchedSegment, rect);
+        const detchedSegment = document.createElement('div');
+        initDetachSegment(detchedSegment, rect);
 
         const updateSegmentOrder = (event: PointerEvent): void => {
             event.stopPropagation();
@@ -219,6 +257,7 @@ export function ProgressBar(props: ProgressBarProps) {
 
             // this fixes a bug where pointerup doesn't fire while cursor is upped while being dragged
             if (!progressBar.hasPointerCapture(ptrId)) {
+                // eslint-disable-next-line no-use-before-define
                 placeSegmentandCleanup();
                 return;
             }
@@ -228,24 +267,23 @@ export function ProgressBar(props: ProgressBarProps) {
             const curX = event.clientX;
             // handle the left bound
             if (curX < currentHover.minX && currentHover.index > 0) {
-                swapSegments(currentHover.index, currentHover.index - 1)
-                currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover
+                swapSegments(currentHover.index, currentHover.index - 1);
+                currentHover = updateCurrentHover(currentHover.index - 1) ?? currentHover;
             }
             // handle the right bound
             else if (curX > currentHover.maxX && currentHover.index < segments.length - 1) {
-                swapSegments(currentHover.index, currentHover.index + 1)
-                currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover
+                swapSegments(currentHover.index, currentHover.index + 1);
+                currentHover = updateCurrentHover(currentHover.index + 1) ?? currentHover;
             }
-        }
+        };
 
         // handles when the user is done dragging the segment (pointerUp)
         const placeSegmentandCleanup = (event?: PointerEvent): void => {
             event?.stopPropagation();
             event?.preventDefault();
             // if they put the segment outside of the bounds, remove it
-            if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight))
-                setRemoved(currentHover.index);
-            
+            if (event && (event.clientX < 0 || event.clientX > document.body.clientWidth || event.clientY < 0 || event.clientY > document.body.clientHeight)) setRemoved(currentHover.index);
+
             // remove the update event listener for pointermove
             progressBar.removeEventListener('pointermove', updateSegmentOrder);
             // remove the floating segment from the DOM
@@ -253,49 +291,16 @@ export function ProgressBar(props: ProgressBarProps) {
             // dragged is -1 is equiv to nothing being dragged, so the normal state
             // so this will place the segment in it's location and update the segment bar
             setDragged(-1);
-        }
+        };
 
         // event listeners that allow the user to drag and release the floating segment
         progressBar.addEventListener('pointermove', updateSegmentOrder);
         progressBar.addEventListener('pointerup', placeSegmentandCleanup, { once: true });
-    }
-
-    const swapSegments = (oldIndex: number, newIndex: number) => {
-        if (newIndex == null) return;
-        setOrdered(prevOrdered => {
-            const temp = { ...prevOrdered[oldIndex] }
-            prevOrdered[oldIndex] = prevOrdered[newIndex]
-            prevOrdered[newIndex] = temp
-            return prevOrdered
-        });
-        // update visually where the segment is hovering over
-        setDragged(newIndex);
-    }
-
-    // functions for the floating segment that tracks the cursor while grabbing it  
-    const initDeatchSegment = (dot: HTMLDivElement, rect: DOMRect) => {
-        dot.classList.add("segment-selected");
-        dot.style.transitionDuration = '0s';
-        dot.style.position = 'absolute';
-        dot.style.zIndex = '999';
-        dot.style.width = `${rect.width}px`;
-        dot.style.height = `${rect.height}px`;
-        dot.style.left = `${rect.x}px`;
-        dot.style.top = `${rect.y}px`;
-        dot.draggable = false;
-        document.body.append(dot);
-    }
-    const followCursor = (event: PointerEvent, dot: HTMLDivElement): void => {
-        // event.stopPropagation()
-        const { width, height } = dot.getBoundingClientRect();
-        dot.style.left = `${event.clientX - width / 2}px`;
-        dot.style.top = `${event.clientY - height / 2}px`;
-    }
-
+    };
 
     return (
         <div className="progressbar" id="progressbar" onPointerDown={onPointerDown} ref={progressBarRef}>
             {segments}
         </div>
-    )
-}
\ No newline at end of file
+    );
+}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 7d123d90c..2bc749e1b 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -44,18 +44,11 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @observable result: Upload.AccessPathInfo | undefined = undefined;
-    @observable videoDuration: number | undefined = undefined;
-
-    @action
-    setVideoDuration = (duration: number) => {
-        this.videoDuration = duration;
-    };
 
     @action
     setResult = (info: Upload.AccessPathInfo, presentation?: Presentation) => {
         this.result = info;
         this.dataDoc.type = DocumentType.VID;
-        this.dataDoc[this.fieldKey + '_duration'] = this.videoDuration;
 
         this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey);
         this.dataDoc[this._props.fieldKey] = new VideoField(this.result.accessPaths.client);
@@ -194,15 +187,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     render() {
         return (
             <div className="recordingBox" style={{ width: '100%' }} ref={this._ref}>
-                {!this.result && (
-                    <RecordingView
-                        forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])}
-                        getControls={this.getControls}
-                        setResult={this.setResult}
-                        setDuration={this.setVideoDuration}
-                        id={DocCast(this.Document.proto)?.[Id] || ''}
-                    />
-                )}
+                {!this.result && <RecordingView forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])} getControls={this.getControls} setResult={this.setResult} id={DocCast(this.Document.proto)?.[Id] || ''} />}
             </div>
         );
     }
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index 9c05a3e94..b8451fe60 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable react/button-has-type */
+/* eslint-disable jsx-a11y/control-has-associated-label */
 import * as React from 'react';
 import { useEffect, useRef, useState } from 'react';
 import { IconContext } from 'react-icons';
@@ -19,19 +22,18 @@ export interface MediaSegment {
 
 interface IRecordingViewProps {
     setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void;
-    setDuration: (seconds: number) => void;
     id: string;
     getControls: (record: () => void, pause: () => void, finish: () => void) => void;
     forceTrackScreen: boolean;
 }
 
 const MAXTIME = 100000;
+const iconVals = { color: '#cc1c08', className: 'video-edit-buttons' };
 
 export function RecordingView(props: IRecordingViewProps) {
     const [recording, setRecording] = useState(false);
     const recordingTimerRef = useRef<number>(0);
     const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second
-    const [playing, setPlaying] = useState(false);
     const [progress, setProgress] = useState(0);
 
     // acts as a "refresh state" to tell progressBar when to undo
@@ -62,7 +64,7 @@ export function RecordingView(props: IRecordingViewProps) {
     useEffect(() => {
         if (finished) {
             // make the total presentation that'll match the concatted video
-            let concatPres = (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));
+            const concatPres = (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));
 
             // this async function uses the server to create the concatted video and then sets the result to it's accessPaths
             (async () => {
@@ -100,16 +102,16 @@ export function RecordingView(props: IRecordingViewProps) {
         return () => clearInterval(interval);
     }, [recording]);
 
+    const setVideoProgressHelper = (curProgrss: number) => {
+        const newProgress = (curProgrss / MAXTIME) * 100;
+        setProgress(newProgress);
+    };
+
     useEffect(() => {
         setVideoProgressHelper(recordingTimer);
         recordingTimerRef.current = recordingTimer;
     }, [recordingTimer]);
 
-    const setVideoProgressHelper = (progress: number) => {
-        const newProgress = (progress / MAXTIME) * 100;
-        setProgress(newProgress);
-    };
-
     const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => {
         const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
 
@@ -131,7 +133,7 @@ export function RecordingView(props: IRecordingViewProps) {
             if (event.data.size > 0) videoChunks.push(event.data);
         };
 
-        videoRecorder.current.onstart = (event: any) => {
+        videoRecorder.current.onstart = () => {
             setRecording(true);
             // start the recording api when the video recorder starts
             (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.start();
@@ -149,7 +151,7 @@ export function RecordingView(props: IRecordingViewProps) {
 
                 // depending on if a presenation exists, add it to the video
                 const presentation = TrackMovements.Instance.yieldPresentation();
-                setVideos(videos => [...videos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]);
+                setVideos(theVideos => [...theVideos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]);
             }
 
             // reset the temporary chunks
@@ -186,7 +188,7 @@ export function RecordingView(props: IRecordingViewProps) {
             e,
             returnTrue,
             returnFalse,
-            e => {
+            () => {
                 // start recording if not already recording
                 if (!videoRecorder.current || videoRecorder.current.state === 'inactive') record();
 
@@ -202,14 +204,8 @@ export function RecordingView(props: IRecordingViewProps) {
         setDoUndo(prev => !prev);
     };
 
-    const handleOnTimeUpdate = () => {
-        playing && setVideoProgressHelper(videoElementRef.current!.currentTime);
-    };
-
     const millisecondToMinuteSecond = (milliseconds: number) => {
-        const toTwoDigit = (digit: number) => {
-            return String(digit).length == 1 ? '0' + digit : digit;
-        };
+        const toTwoDigit = (digit: number) => (String(digit).length === 1 ? '0' + digit : digit);
         const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
         const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
         return toTwoDigit(minutes) + ' : ' + toTwoDigit(seconds);
@@ -219,10 +215,11 @@ export function RecordingView(props: IRecordingViewProps) {
         props.getControls(record, pause, finish);
     }, []);
 
+    const iconUndoVals = React.useMemo(() => ({ color: 'grey', className: 'video-edit-buttons', style: { display: canUndo ? 'inherit' : 'none' } }), []);
     return (
         <div className="recording-container">
             <div className="video-wrapper">
-                <video id={`video-${props.id}`} autoPlay muted onTimeUpdate={() => handleOnTimeUpdate()} ref={videoElementRef} />
+                <video id={`video-${props.id}`} autoPlay muted ref={videoElementRef} />
                 <div className="recording-sign">
                     <span className="dot" />
                     <p className="timer">{millisecondToMinuteSecond(recordingTimer * 10)}</p>
@@ -246,10 +243,10 @@ export function RecordingView(props: IRecordingViewProps) {
                         {!recording &&
                             (videos.length > 0 ? (
                                 <div className="options-wrapper video-edit-wrapper">
-                                    <IconContext.Provider value={{ color: 'grey', className: 'video-edit-buttons', style: { display: canUndo ? 'inherit' : 'none' } }}>
+                                    <IconContext.Provider value={iconUndoVals}>
                                         <MdBackspace onPointerDown={undoPrevious} />
                                     </IconContext.Provider>
-                                    <IconContext.Provider value={{ color: '#cc1c08', className: 'video-edit-buttons' }}>
+                                    <IconContext.Provider value={iconVals}>
                                         <FaCheckCircle
                                             onPointerDown={e => {
                                                 e.stopPropagation();
@@ -268,7 +265,7 @@ export function RecordingView(props: IRecordingViewProps) {
                                                 setTrackScreen(e.target.checked);
                                             }}
                                         />
-                                        <span className="checkmark"></span>
+                                        <span className="checkmark" />
                                         Track Screen
                                     </label>
                                 </div>
diff --git a/src/client/views/nodes/RecordingBox/index.ts b/src/client/views/nodes/RecordingBox/index.ts
index ff21eaed6..e4f9b5e55 100644
--- a/src/client/views/nodes/RecordingBox/index.ts
+++ b/src/client/views/nodes/RecordingBox/index.ts
@@ -1,2 +1,2 @@
-export * from './RecordingView'
-export * from './RecordingBox'
\ No newline at end of file
+export * from './RecordingView';
+export * from './RecordingBox';
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 60d7e4b00..5ebc50a1b 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -1,4 +1,5 @@
-let ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
+/* eslint-disable react/button-has-type */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -17,10 +18,12 @@ import { ContextMenu } from '../ContextMenu';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { OverlayView } from '../OverlayView';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { FieldView, FieldViewProps } from './FieldView';
 import { DocumentIconContainer } from './DocumentIcon';
 import './ScriptingBox.scss';
+
 const _global = (window /* browser */ || global) /* node */ as any;
+const ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
 
 @observer
 export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@@ -79,26 +82,24 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     @computed({ keepAlive: true }) get rawScript() {
         return ScriptCast(this.dataDoc[this.fieldKey])?.script.originalScript ?? '';
     }
-    @computed({ keepAlive: true }) get functionName() {
-        return StrCast(this.dataDoc[this.fieldKey + '-functionName'], '');
-    }
-    @computed({ keepAlive: true }) get functionDescription() {
-        return StrCast(this.dataDoc[this.fieldKey + '-functionDescription'], '');
-    }
-    @computed({ keepAlive: true }) get compileParams() {
-        return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []);
-    }
-
     set rawScript(value) {
         this.dataDoc[this.fieldKey] = new ScriptField(undefined, undefined, value);
     }
+    @computed({ keepAlive: true }) get functionName() {
+        return StrCast(this.dataDoc[this.fieldKey + '-functionName'], '');
+    }
     set functionName(value) {
         this.dataDoc[this.fieldKey + '-functionName'] = value;
     }
+    @computed({ keepAlive: true }) get functionDescription() {
+        return StrCast(this.dataDoc[this.fieldKey + '-functionDescription'], '');
+    }
     set functionDescription(value) {
         this.dataDoc[this.fieldKey + '-functionDescription'] = value;
     }
-
+    @computed({ keepAlive: true }) get compileParams() {
+        return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []);
+    }
     set compileParams(value) {
         this.dataDoc[this.fieldKey + '-params'] = new List<string>(value);
     }
@@ -107,9 +108,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         if (typeof result === 'object') {
             const text = descrip ? result[1] : result[2];
             return text !== undefined ? text : '';
-        } else {
-            return '';
         }
+        return '';
     }
 
     onClickScriptDisable = returnAlways;
@@ -118,19 +118,18 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     componentDidMount() {
         this._props.setContentViewBox?.(this);
         this.rawText = this.rawScript;
-        const observer = new _global.ResizeObserver(
-            action((entries: any) => {
+        const resizeObserver = new _global.ResizeObserver(
+            action(() => {
                 const area = document.querySelector('textarea');
                 if (area) {
-                    for (const {} of entries) {
-                        const getCaretCoordinates = require('textarea-caret');
-                        const caret = getCaretCoordinates(area, this._selection);
-                        this.resetSuggestionPos(caret);
-                    }
+                    // eslint-disable-next-line global-require
+                    const getCaretCoordinates = require('textarea-caret');
+                    const caret = getCaretCoordinates(area, this._selection);
+                    this.resetSuggestionPos(caret);
                 }
             })
         );
-        observer.observe(document.getElementsByClassName('scriptingBox-outerDiv')[0]);
+        resizeObserver.observe(document.getElementsByClassName('scriptingBox-outerDiv')[0]);
     }
 
     @action
@@ -138,12 +137,12 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         if (!this._suggestionRef.current || !this._scriptTextRef.current) return;
         const suggestionWidth = this._suggestionRef.current.offsetWidth;
         const scriptWidth = this._scriptTextRef.current.offsetWidth;
-        const top = caret.top;
-        const x = this.dataDoc.x;
-        let left = caret.left;
+        const { top } = caret;
+        const { x } = this.dataDoc;
+        let { left } = caret;
         if (left + suggestionWidth > x + scriptWidth) {
             const diff = left + suggestionWidth - (x + scriptWidth);
-            left = left - diff;
+            left -= diff;
         }
 
         this._suggestionBoxX = left;
@@ -155,7 +154,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     }
 
     protected createDashEventsTarget = (ele: HTMLDivElement, dropFunc: (e: Event, de: DragManager.DropEvent) => void) => {
-        //used for stacking and masonry view
+        // used for stacking and masonry view
         if (ele) {
             this.dropDisposer?.();
             this.dropDisposer = DragManager.MakeDropTarget(ele, dropFunc, this.layoutDoc);
@@ -164,7 +163,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
     // only included in buttons, transforms scripting UI to a button
     @action
-    onFinish = () => (this.layoutDoc.layout_fieldKey = 'layout');
+    onFinish = () => {
+        this.layoutDoc.layout_fieldKey = 'layout';
+    };
 
     // displays error message
     @action
@@ -176,7 +177,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     @action
     onCompile = () => {
         const params: ScriptParam = {};
-        this.compileParams.forEach(p => (params[p.split(':')[0].trim()] = p.split(':')[1].trim()));
+        this.compileParams.forEach(p => {
+            params[p.split(':')[0].trim()] = p.split(':')[1].trim();
+        });
 
         const result = !this.rawText.trim()
             ? ({ compiled: false, errors: undefined } as any)
@@ -196,7 +199,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     onRun = () => {
         if (this.onCompile()) {
             const bindings: { [name: string]: any } = {};
-            this.paramsNames.forEach(key => (bindings[key] = this.dataDoc[key]));
+            this.paramsNames.forEach(key => {
+                bindings[key] = this.dataDoc[key];
+            });
             // binds vars so user doesnt have to refer to everything as this.<var>
             ScriptCast(this.dataDoc[this.fieldKey], null)?.script.run({ ...bindings, this: this.Document }, this.onError);
         }
@@ -247,7 +252,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
         this.dataDoc.name = this.functionName;
         this.dataDoc.description = this.functionDescription;
-        //this.dataDoc.parameters = this.compileParams;
+        // this.dataDoc.parameters = this.compileParams;
         this.dataDoc.script = this.rawScript;
 
         ScriptManager.Instance.addScript(this.dataDoc);
@@ -255,6 +260,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         this._scriptKeys = ScriptingGlobals.getGlobals();
         this._scriptingDescriptions = ScriptingGlobals.getDescriptions();
         this._scriptingParams = ScriptingGlobals.getParameters();
+        return undefined;
     };
 
     // overlays document numbers (ex. d32) over all documents when clicked on
@@ -267,7 +273,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     @action
     onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => {
         if (de.complete.docDragData) {
-            de.complete.docDragData.droppedDocuments.forEach(doc => (this.dataDoc[fieldKey] = doc));
+            de.complete.docDragData.droppedDocuments.forEach(doc => {
+                this.dataDoc[fieldKey] = doc;
+            });
             e.stopPropagation();
             return true;
         }
@@ -285,8 +293,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     // sets field of the param name to the selected value in drop down box
     @action
     viewChanged = (e: React.ChangeEvent, name: string) => {
-        //@ts-ignore
-        const val = e.target.selectedOptions[0].value;
+        const val = (e.target as any).selectedOptions[0].value;
         this.dataDoc[name] = val[0] === 'S' ? val.substring(1) : val[0] === 'N' ? parseInt(val.substring(1)) : val.substring(1) === 'true';
     };
 
@@ -306,8 +313,26 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     };
 
     renderFunctionInputs() {
-        const descriptionInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionDescription = e.target.value)} placeholder="enter description here" value={this.functionDescription} />;
-        const nameInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionName = e.target.value)} placeholder="enter name here" value={this.functionName} />;
+        const descriptionInput = (
+            <textarea
+                className="scriptingBox-textarea-inputs"
+                onChange={e => {
+                    this.functionDescription = e.target.value;
+                }}
+                placeholder="enter description here"
+                value={this.functionDescription}
+            />
+        );
+        const nameInput = (
+            <textarea
+                className="scriptingBox-textarea-inputs"
+                onChange={e => {
+                    this.functionName = e.target.value;
+                }}
+                placeholder="enter name here"
+                value={this.functionName}
+            />
+        );
 
         return (
             <div className="scriptingBox-inputDiv" onPointerDown={e => this._props.isSelected() && e.stopPropagation()}>
@@ -339,7 +364,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         return (
             <div className="scriptingBox-paramInputs" onFocus={this.onFocus} onBlur={() => this._overlayDisposer?.()} ref={ele => ele && this.createDashEventsTarget(ele, (e, de) => this.onDrop(e, de, parameter))}>
                 <EditableView
-                    display={'block'}
+                    display="block"
                     maxHeight={72}
                     height={35}
                     fontSize={14}
@@ -371,7 +396,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         return (
             <div className="scriptingBox-paramInputs" style={{ overflowY: 'hidden' }}>
                 <EditableView
-                    display={'block'}
+                    display="block"
                     maxHeight={72}
                     height={35}
                     fontSize={14}
@@ -404,6 +429,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             onChange={e => this.viewChanged(e, parameter)}
                             value={typeof this.dataDoc[parameter] === 'string' ? 'S' + StrCast(this.dataDoc[parameter]) : typeof this.dataDoc[parameter] === 'number' ? 'N' + NumCast(this.dataDoc[parameter]) : 'B' + BoolCast(this.dataDoc[parameter])}>
                             {types.map((type, i) => (
+                                // eslint-disable-next-line react/no-array-index-key
                                 <option key={i} className="scriptingBox-viewOption" value={(typeof type === 'string' ? 'S' : typeof type === 'number' ? 'N' : 'B') + type}>
                                     {' '}
                                     {type.toString()}{' '}
@@ -496,6 +522,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
     @action
     suggestionPos = () => {
+        // eslint-disable-next-line global-require
         const getCaretCoordinates = require('textarea-caret');
         const This = this;
         document.querySelector('textarea')?.addEventListener('input', function () {
@@ -509,7 +536,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     keyHandler(e: any, pos: number) {
         e.stopPropagation();
         if (this._lastChar === 'Enter') {
-            this.rawText = this.rawText + ' ';
+            this.rawText += ' ';
         }
         if (e.key === '(') {
             this.suggestionPos();
@@ -524,20 +551,18 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             }
         } else if (e.key === ')') {
             this._paramSuggestion = false;
-        } else {
-            if (e.key === 'Backspace') {
-                if (this._lastChar === '(') {
-                    this._paramSuggestion = false;
-                } else if (this._lastChar === ')') {
-                    if (this.rawText.slice(0, this.rawText.length - 1).split('(').length - 1 > this.rawText.slice(0, this.rawText.length - 1).split(')').length - 1) {
-                        if (this._scriptParamsText.length > 0) {
-                            this._paramSuggestion = true;
-                        }
+        } else if (e.key === 'Backspace') {
+            if (this._lastChar === '(') {
+                this._paramSuggestion = false;
+            } else if (this._lastChar === ')') {
+                if (this.rawText.slice(0, this.rawText.length - 1).split('(').length - 1 > this.rawText.slice(0, this.rawText.length - 1).split(')').length - 1) {
+                    if (this._scriptParamsText.length > 0) {
+                        this._paramSuggestion = true;
                     }
                 }
-            } else if (this.rawText.split('(').length - 1 <= this.rawText.split(')').length - 1) {
-                this._paramSuggestion = false;
             }
+        } else if (this.rawText.split('(').length - 1 <= this.rawText.split(')').length - 1) {
+            this._paramSuggestion = false;
         }
         this._lastChar = e.key === 'Backspace' ? this.rawText[this.rawText.length - 2] : e.key;
 
@@ -559,9 +584,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
             parameters.forEach((element: string, i: number) => {
                 if (i < numEntered - 1) {
-                    first = first + element;
+                    first += element;
                 } else if (i > numEntered - 1) {
-                    last = last + element;
+                    last += element;
                 }
             });
 
@@ -598,16 +623,16 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                     placeholder="write your script here"
                     onFocus={this.onFocus}
                     onBlur={() => this._overlayDisposer?.()}
-                    onChange={action((e: any) => (this.rawText = e.target.value))}
+                    onChange={action((e: any) => {
+                        this.rawText = e.target.value;
+                    })}
                     value={this.rawText}
-                    movePopupAsYouType={true}
+                    movePopupAsYouType
                     loadingComponent={() => <span>Loading</span>}
                     trigger={{
                         ' ': {
                             dataProvider: (token: any) => this.handleToken(token),
-                            component: (blob: any) => {
-                                return this.renderFuncListElement(blob.entity);
-                            },
+                            component: (blob: any) => this.renderFuncListElement(blob.entity),
                             output: (item: any, trigger: any) => {
                                 this._spaced = true;
                                 return trigger + item.trim();
@@ -615,9 +640,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         },
                         '.': {
                             dataProvider: (token: any) => this.handleToken(token),
-                            component: (blob: any) => {
-                                return this.renderFuncListElement(blob.entity);
-                            },
+                            component: (blob: any) => this.renderFuncListElement(blob.entity),
                             output: (item: any, trigger: any) => {
                                 this._spaced = true;
                                 return trigger + item.trim();
@@ -653,16 +676,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         // params box on bottom
         const parameterInput = (
             <div className="scriptingBox-params">
-                <EditableView
-                    display={'block'}
-                    maxHeight={72}
-                    height={35}
-                    fontSize={22}
-                    contents={''}
-                    GetValue={returnEmptyString}
-                    SetValue={value => (value && value !== ' ' ? this.compileParam(value) : false)}
-                    placeholder={'enter parameters here'}
-                />
+                <EditableView display="block" maxHeight={72} height={35} fontSize={22} contents="" GetValue={returnEmptyString} SetValue={value => (value && value !== ' ' ? this.compileParam(value) : false)} placeholder="enter parameters here" />
             </div>
         );
 
@@ -670,13 +684,14 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         const definedParameters = !this.compileParams.length ? null : (
             <div className="scriptingBox-plist" style={{ width: '30%' }}>
                 {this.compileParams.map((parameter, i) => (
+                    // eslint-disable-next-line react/no-array-index-key
                     <div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
                         <EditableView
-                            display={'block'}
+                            display="block"
                             maxHeight={72}
                             height={35}
                             fontSize={12}
-                            background-color={'beige'}
+                            background-color="beige"
                             contents={parameter}
                             GetValue={() => parameter}
                             SetValue={value => (value && value !== ' ' ? this.compileParam(value, i) : this.onDelete(i))}
@@ -749,6 +764,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 {!this.compileParams.length || !this.paramsNames ? null : (
                     <div className="scriptingBox-plist">
                         {this.paramsNames.map((parameter: string, i: number) => (
+                            // eslint-disable-next-line react/no-array-index-key
                             <div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
                                 <div className="scriptingBox-wrapper" style={{ maxHeight: '40px' }}>
                                     <div className="scriptingBox-paramNames"> {`${parameter}:${this.paramsTypes[i]} = `} </div>
diff --git a/src/client/views/nodes/SliderBox-components.tsx b/src/client/views/nodes/SliderBox-components.tsx
index e19f28f08..b9f215d77 100644
--- a/src/client/views/nodes/SliderBox-components.tsx
+++ b/src/client/views/nodes/SliderBox-components.tsx
@@ -1,6 +1,6 @@
-import * as React from "react";
-import { SliderItem } from "react-compound-slider";
-import "./SliderBox-tooltip.css";
+import * as React from 'react';
+import { SliderItem } from 'react-compound-slider';
+import './SliderBox-tooltip.css';
 
 const { Component, Fragment } = React;
 
@@ -8,24 +8,24 @@ const { Component, Fragment } = React;
 // TOOLTIP RAIL
 // *******************************************************
 const railStyle: React.CSSProperties = {
-    position: "absolute",
-    width: "100%",
+    position: 'absolute',
+    width: '100%',
     height: 40,
     borderRadius: 7,
-    cursor: "pointer",
+    cursor: 'pointer',
     opacity: 0.3,
     zIndex: 300,
-    border: "1px solid grey"
+    border: '1px solid grey',
 };
 
 const railCenterStyle: React.CSSProperties = {
-    position: "absolute",
-    width: "100%",
+    position: 'absolute',
+    width: '100%',
     height: 14,
     borderRadius: 7,
-    cursor: "pointer",
-    pointerEvents: "none",
-    backgroundColor: "rgb(155,155,155)"
+    cursor: 'pointer',
+    pointerEvents: 'none',
+    backgroundColor: 'rgb(155,155,155)',
 };
 
 interface TooltipRailProps {
@@ -37,21 +37,21 @@ interface TooltipRailProps {
 export class TooltipRail extends Component<TooltipRailProps> {
     state = {
         value: null,
-        percent: null
+        percent: null,
     };
 
     static defaultProps = {
-        disabled: false
+        disabled: false,
     };
 
     onMouseEnter = () => {
-        document.addEventListener("mousemove", this.onMouseMove);
-    }
+        document.addEventListener('mousemove', this.onMouseMove);
+    };
 
     onMouseLeave = () => {
         this.setState({ value: null, percent: null });
-        document.removeEventListener("mousemove", this.onMouseMove);
-    }
+        document.removeEventListener('mousemove', this.onMouseMove);
+    };
 
     onMouseMove = (e: Event) => {
         const { activeHandleID, getEventData } = this.props;
@@ -61,7 +61,7 @@ export class TooltipRail extends Component<TooltipRailProps> {
         } else {
             this.setState(getEventData(e));
         }
-    }
+    };
 
     render() {
         const { value, percent } = this.state;
@@ -73,11 +73,10 @@ export class TooltipRail extends Component<TooltipRailProps> {
                     <div
                         style={{
                             left: `${percent}%`,
-                            position: "absolute",
-                            marginLeft: "-11px",
-                            marginTop: "-35px"
-                        }}
-                    >
+                            position: 'absolute',
+                            marginLeft: '-11px',
+                            marginTop: '-35px',
+                        }}>
                         <div className="tooltip">
                             <span className="tooltiptext">Value: {value}</span>
                         </div>
@@ -87,7 +86,7 @@ export class TooltipRail extends Component<TooltipRailProps> {
                     style={railStyle}
                     {...getRailProps({
                         onMouseEnter: this.onMouseEnter,
-                        onMouseLeave: this.onMouseLeave
+                        onMouseLeave: this.onMouseLeave,
                     })}
                 />
                 <div style={railCenterStyle} />
@@ -110,20 +109,20 @@ interface HandleProps {
 
 export class Handle extends Component<HandleProps> {
     static defaultProps = {
-        disabled: false
+        disabled: false,
     };
 
     state = {
-        mouseOver: false
+        mouseOver: false,
     };
 
     onMouseEnter = () => {
         this.setState({ mouseOver: true });
-    }
+    };
 
     onMouseLeave = () => {
         this.setState({ mouseOver: false });
-    }
+    };
 
     render() {
         const {
@@ -131,7 +130,7 @@ export class Handle extends Component<HandleProps> {
             handle: { id, value, percent },
             isActive,
             disabled,
-            getHandleProps
+            getHandleProps,
         } = this.props;
         const { mouseOver } = this.state;
 
@@ -141,11 +140,10 @@ export class Handle extends Component<HandleProps> {
                     <div
                         style={{
                             left: `${percent}%`,
-                            position: "absolute",
-                            marginLeft: "-11px",
-                            marginTop: "-35px"
-                        }}
-                    >
+                            position: 'absolute',
+                            marginLeft: '-11px',
+                            marginTop: '-35px',
+                        }}>
                         <div className="tooltip">
                             <span className="tooltiptext">Value: {value}</span>
                         </div>
@@ -158,21 +156,21 @@ export class Handle extends Component<HandleProps> {
                     aria-valuenow={value}
                     style={{
                         left: `${percent}%`,
-                        position: "absolute",
-                        marginLeft: "-11px",
-                        marginTop: "-6px",
+                        position: 'absolute',
+                        marginLeft: '-11px',
+                        marginTop: '-6px',
                         zIndex: 400,
                         width: 24,
                         height: 24,
-                        cursor: "pointer",
+                        cursor: 'pointer',
                         border: 0,
-                        borderRadius: "50%",
-                        boxShadow: "1px 1px 1px 1px rgba(0, 0, 0, 0.4)",
-                        backgroundColor: disabled ? "#666" : "#3e1db3"
+                        borderRadius: '50%',
+                        boxShadow: '1px 1px 1px 1px rgba(0, 0, 0, 0.4)',
+                        backgroundColor: disabled ? '#666' : '#3e1db3',
                     }}
                     {...getHandleProps(id, {
                         onMouseEnter: this.onMouseEnter,
-                        onMouseLeave: this.onMouseLeave
+                        onMouseLeave: this.onMouseLeave,
                     })}
                 />
             </Fragment>
@@ -190,23 +188,18 @@ interface TrackProps {
     getTrackProps: () => object;
 }
 
-export function Track({
-    source,
-    target,
-    getTrackProps,
-    disabled = false
-}: TrackProps) {
+export function Track({ source, target, getTrackProps, disabled = false }: TrackProps) {
     return (
         <div
             style={{
-                position: "absolute",
+                position: 'absolute',
                 height: 14,
                 zIndex: 1,
-                backgroundColor: disabled ? "#999" : "#3e1db3",
+                backgroundColor: disabled ? '#999' : '#3e1db3',
                 borderRadius: 7,
-                cursor: "pointer",
+                cursor: 'pointer',
                 left: `${source.percent}%`,
-                width: `${target.percent - source.percent}%`
+                width: `${target.percent - source.percent}%`,
             }}
             {...getTrackProps()}
         />
@@ -229,25 +222,24 @@ export function Tick({ tick, count, format = defaultFormat }: TickProps) {
         <div>
             <div
                 style={{
-                    position: "absolute",
+                    position: 'absolute',
                     marginTop: 20,
                     width: 1,
                     height: 5,
-                    backgroundColor: "rgb(200,200,200)",
-                    left: `${tick.percent}%`
+                    backgroundColor: 'rgb(200,200,200)',
+                    left: `${tick.percent}%`,
                 }}
             />
             <div
                 style={{
-                    position: "absolute",
+                    position: 'absolute',
                     marginTop: 25,
                     fontSize: 10,
-                    textAlign: "center",
+                    textAlign: 'center',
                     marginLeft: `${-(100 / count) / 2}%`,
                     width: `${100 / count}%`,
-                    left: `${tick.percent}%`
-                }}
-            >
+                    left: `${tick.percent}%`,
+                }}>
                 {format(tick.value)}
             </div>
         </div>
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 5b3f37993..1121134cf 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -271,7 +271,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         setupMoveUpEvents(
             e.target,
             e,
-            action((e, down, delta) => {
+            action((moveEv, down, delta) => {
                 if (this._controlsTransform) {
                     this._controlsTransform.X = Math.max(0, Math.min(delta[0] + this._controlsTransform.X, window.innerWidth));
                     this._controlsTransform.Y = Math.max(0, Math.min(delta[1] + this._controlsTransform.Y, window.innerHeight));
@@ -309,8 +309,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             // convert to desired file format
             const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
             // if you want to preview the captured image,
-            const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
-            const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[\.\/\?\=]/g, '_'));
+            const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, '');
+            const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[./?=]/g, '_'));
             const filename = basename(encodedFilename);
             ClientUtils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
         }
@@ -410,7 +410,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             canvas.height = 100;
             canvas.width = 100;
             canvas.getContext('2d')?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 100, 100);
-            const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, '');
+            const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, '');
             const encodedFilename = encodeURIComponent('thumbnail' + retitled + '_' + video.currentTime.toString().replace(/\./, '_'));
             thumbnailPromises?.push(ClientUtils.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true));
             const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1);
@@ -486,7 +486,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                     },
                     icon: 'expand-arrows-alt',
                 });
-            subitems.push({ description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered', event: () => (this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks), icon: 'expand-arrows-alt' });
+            subitems.push({
+                description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered',
+                event: () => {
+                    this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks;
+                },
+                icon: 'expand-arrows-alt',
+            });
             subitems.push({
                 description: (this.layoutDoc.dontAutoPlayFollowedLinks ? '' : "Don't") + ' play when link is selected',
                 event: () => {
@@ -494,7 +500,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                 },
                 icon: 'expand-arrows-alt',
             });
-            subitems.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick', event: () => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors), icon: 'expand-arrows-alt' });
+            subitems.push({
+                description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick',
+                event: () => {
+                    this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors;
+                },
+                icon: 'expand-arrows-alt',
+            });
             // subitems.push({ description: "Start Trim All", event: () => this.startTrim(TrimScope.All), icon: "expand-arrows-alt" });
             // subitems.push({ description: "Start Trim Clip", event: () => this.startTrim(TrimScope.Clip), icon: "expand-arrows-alt" });
             // subitems.push({ description: "Stop Trim", event: () => this.finishTrim(), icon: "expand-arrows-alt" });
@@ -593,8 +605,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         setupMoveUpEvents(
             this,
             e,
-            e => {
-                this.Snapshot(e.clientX, e.clientY);
+            moveEv => {
+                this.Snapshot(moveEv.clientX, moveEv.clientY);
                 return true;
             },
             emptyFunction,
@@ -657,13 +669,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
 
     // for annotating, adds doc with time info
     @action.bound
-    addDocWithTimecode(doc: Doc | Doc[]): boolean {
-        const docs = doc instanceof Doc ? [doc] : doc;
+    addDocWithTimecode(docIn: Doc | Doc[]): boolean {
+        const docs = docIn instanceof Doc ? [docIn] : docIn;
         const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
         docs.forEach(doc => {
             doc._timecodeToHide = (doc._timecodeToShow = curTime) + 1;
         });
-        return this.addDocument(doc);
+        return this.addDocument(docs);
     }
 
     // play back the audio from seekTimeInSeconds, fullPlay tells whether clip is being played to end vs link range
@@ -671,7 +683,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
     playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => {
         clearTimeout(this._playRegionTimer);
         this._playRegionTimer = undefined;
-        if (isNaN(this.player?.duration)) {
+        if (this.player?.duration === undefined || isNaN(this.player.duration)) {
             setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
         } else if (this.player) {
             // trimBounds override requested playback bounds
@@ -723,7 +735,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                 e,
                 returnFalse,
                 returnFalse,
-                action((e: PointerEvent, doubleTap?: boolean) => {
+                action((clickEv: PointerEvent, doubleTap?: boolean) => {
                     if (doubleTap) {
                         this.startTrim(TrimScope.All);
                     } else if (this.timeline) {
@@ -782,9 +794,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             setupMoveUpEvents(
                 this,
                 e,
-                action(e => {
+                action(moveEv => {
                     MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
+                    this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
                     return true;
                 }),
                 returnFalse,
diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx
index 7fd799952..2d1d3d7db 100644
--- a/src/client/views/nodes/audio/AudioWaveform.tsx
+++ b/src/client/views/nodes/audio/AudioWaveform.tsx
@@ -7,8 +7,8 @@ import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { Cast } from '../../../../fields/Types';
 import { numberRange } from '../../../../Utils';
+import { Colors } from '../../global/globalEnums';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { Colors } from './../../global/globalEnums';
 import './AudioWaveform.scss';
 import { WaveCanvas } from './WaveCanvas';
 
@@ -62,7 +62,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
         return NumListCast(this._props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)]);
     }
 
-    audioBucketField = (start: number, end: number, zoomFactor: number) => this._props.fieldKey + '_audioBuckets/' + '/' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
+    audioBucketField = (start: number, end: number, zoomFactor: number) => this._props.fieldKey + '_audioBuckets//' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
 
     componentWillUnmount() {
         this._disposer?.();
@@ -72,7 +72,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
         this._disposer = reaction(
             () => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this._props.zoomFactor }),
             ({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
-                if (!this._props.layoutDoc[fieldKey] && this._props.layoutDoc.layout_fieldKey != 'layout_icon') {
+                if (!this._props.layoutDoc[fieldKey] && this._props.layoutDoc.layout_fieldKey !== 'layout_icon') {
                     // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
                     const waveform = Cast(this._props.layoutDoc[this.audioBucketField(0, this._props.rawDuration, 1)], listSpec('number'));
                     this._props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice((clipStart / this._props.rawDuration) * waveform.length, (clipEnd / this._props.rawDuration) * waveform.length));
@@ -109,7 +109,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
                     progressColor={Colors.MEDIUM_BLUE_ALT}
                     progress={this._props.progress ?? 1}
                     barWidth={200 / this.audioBuckets.length}
-                    //gradientColors={this._props.gradientColors}
+                    // gradientColors={this._props.gradientColors}
                     peaks={this.audioBuckets}
                     width={(this._props.PanelWidth ?? 0) * window.devicePixelRatio}
                     height={this.waveHeight * window.devicePixelRatio}
diff --git a/src/client/views/nodes/audio/WaveCanvas.tsx b/src/client/views/nodes/audio/WaveCanvas.tsx
index d3f5669a2..eacda2d42 100644
--- a/src/client/views/nodes/audio/WaveCanvas.tsx
+++ b/src/client/views/nodes/audio/WaveCanvas.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import React from 'react';
 
 interface WaveCanvasProps {
@@ -14,7 +15,7 @@ interface WaveCanvasProps {
 
 export class WaveCanvas extends React.Component<WaveCanvasProps> {
     // If the first value of peaks is negative, addToIndices will be 1
-    posPeaks = (peaks: number[], addToIndices: number) => peaks.filter((_, index) => (index + addToIndices) % 2 == 0);
+    posPeaks = (peaks: number[], addToIndices: number) => peaks.filter((_, index) => (index + addToIndices) % 2 === 0);
 
     drawBars = (waveCanvasCtx: CanvasRenderingContext2D, width: number, halfH: number, peaks: number[]) => {
         // Bar wave draws the bottom only as a reflection of the top,
@@ -47,6 +48,7 @@ export class WaveCanvas extends React.Component<WaveCanvasProps> {
 
         // A half-pixel offset makes lines crisp
         const $ = 0.5 / this.props.pixelRatio;
+        // eslint-disable-next-line no-bitwise
         const length = ~~(allPeaks.length / 2); // ~~ is Math.floor for positive numbers.
 
         const scale = width / length;
@@ -55,14 +57,14 @@ export class WaveCanvas extends React.Component<WaveCanvasProps> {
         waveCanvasCtx.beginPath();
         waveCanvasCtx.moveTo($, halfH);
 
-        for (var i = 0; i < length; i++) {
-            var h = Math.round((allPeaks[2 * i] / absmax) * halfH);
+        for (let i = 0; i < length; i++) {
+            const h = Math.round((allPeaks[2 * i] / absmax) * halfH);
             waveCanvasCtx.lineTo(i * scale + $, halfH - h);
         }
 
         // Draw the bottom edge going backwards, to make a single closed hull to fill.
-        for (var i = length - 1; i >= 0; i--) {
-            var h = Math.round((allPeaks[2 * i + 1] / absmax) * halfH);
+        for (let i = length - 1; i >= 0; i--) {
+            const h = Math.round((allPeaks[2 * i + 1] / absmax) * halfH);
             waveCanvasCtx.lineTo(i * scale + $, halfH - h);
         }
 
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 8577510e3..5893c346f 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -57,12 +57,13 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     docBackgroundColor(type: string): string {
         // TODO: Return a different color based on the event type
+        console.log(type);
         return 'blue';
     }
 
     get calendarEvents(): EventSourceInput | undefined {
         if (this.childDocs.length === 0) return undefined;
-        return this.childDocs.map((doc, idx) => {
+        return this.childDocs.map(doc => {
             const docTitle = StrCast(doc.title);
             const docDateRange = StrCast(doc.date_range);
             const [startDate, endDate] = dateRangeStrToDates(docDateRange);
@@ -85,7 +86,7 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
         });
     }
 
-    handleEventClick = (arg: EventClickArg) => {
+    handleEventClick = (/* arg: EventClickArg */) => {
         // TODO: open popover with event description, option to open CalendarManager and change event date, delete event, etc.
     };
 
@@ -113,7 +114,7 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
     render() {
         return (
             <div className="calendar-box-conatiner">
-                <div id="calendar-box-v1"></div>
+                <div id="calendar-box-v1" />
             </div>
         );
     }
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index a72ed1813..3ec49fa27 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -1,60 +1,11 @@
 import { TextSelection } from 'prosemirror-state';
 import * as ReactDOM from 'react-dom/client';
-import { Doc } from '../../../../fields/Doc';
-import { DocServer } from '../../../DocServer';
 import * as React from 'react';
 import { IReactionDisposer, computed, reaction } from 'mobx';
+import { Doc } from '../../../../fields/Doc';
+import { DocServer } from '../../../DocServer';
 import { NumCast } from '../../../../fields/Types';
 
-// creates an inline comment in a note when '>>' is typed.
-// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
-// the comment can be toggled on/off with the '<-' text anchor.
-export class DashDocCommentView {
-    dom: HTMLDivElement; // container for label and value
-    root: any;
-    node: any;
-
-    constructor(node: any, view: any, getPos: any) {
-        this.node = node;
-        this.dom = document.createElement('div');
-        this.dom.style.width = node.attrs.width;
-        this.dom.style.height = node.attrs.height;
-        this.dom.style.fontWeight = 'bold';
-        this.dom.style.position = 'relative';
-        this.dom.style.display = 'inline-block';
-        this.dom.onkeypress = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onkeydown = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onkeyup = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onmousedown = function (e: any) {
-            e.stopPropagation();
-        };
-
-        this.root = ReactDOM.createRoot(this.dom);
-        this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
-        (this as any).dom = this.dom;
-    }
-
-    setHeight = (hgt: number) => {
-        !this.node.attrs.reflow && DocServer.GetRefField(this.node.attrs.docId).then(doc => doc instanceof Doc && (this.dom.style.height = hgt + ''));
-    };
-
-    destroy() {
-        this.root.unmount();
-    }
-    deselectNode() {
-        this.dom.classList.remove('ProseMirror-selectednode');
-    }
-    selectNode() {
-        this.dom.classList.add('ProseMirror-selectednode');
-    }
-}
-
 interface IDashDocCommentViewInternal {
     docId: string;
     view: any;
@@ -65,9 +16,6 @@ interface IDashDocCommentViewInternal {
 export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
     _reactionDisposer: IReactionDisposer | undefined;
 
-    @computed get _dashDoc() {
-        return DocServer.GetRefField(this.props.docId);
-    }
     constructor(props: any) {
         super(props);
         this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
@@ -77,58 +25,62 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
     }
     componentDidMount(): void {
         this._reactionDisposer?.();
-        this._dashDoc.then(
-            doc =>
-                doc instanceof Doc &&
-                (this._reactionDisposer = reaction(
+        this._dashDoc.then(doc => {
+            if (doc instanceof Doc) {
+                this._reactionDisposer = reaction(
                     () => NumCast((doc as Doc)._height),
                     hgt => this.props.setHeight(hgt),
-                    {
-                        fireImmediately: true,
-                    }
-                ))
-        );
+                    { fireImmediately: true }
+                );
+            }
+        });
     }
     componentWillUnmount(): void {
         this._reactionDisposer?.();
     }
 
-    onPointerLeaveCollapsed(e: any) {
+    @computed get _dashDoc() {
+        return DocServer.GetRefField(this.props.docId);
+    }
+
+    onPointerLeaveCollapsed = (e: any) => {
         this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
         e.preventDefault();
         e.stopPropagation();
-    }
+    };
 
-    onPointerEnterCollapsed(e: any) {
+    onPointerEnterCollapsed = (e: any) => {
         this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
         e.preventDefault();
         e.stopPropagation();
-    }
+    };
 
-    onPointerUpCollapsed(e: any) {
+    onPointerUpCollapsed = (e: any) => {
         const target = this.targetNode();
 
         if (target) {
             const expand = target.hidden;
-            const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+            const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: !target.node.attrs.hidden });
             this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
             setTimeout(() => {
                 expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
                 try {
                     this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1))));
-                } catch (e) {}
+                } catch (err) {
+                    /* empty */
+                }
             }, 0);
         }
         e.stopPropagation();
-    }
+    };
 
-    onPointerDownCollapsed(e: any) {
+    onPointerDownCollapsed = (e: any) => {
         e.stopPropagation();
-    }
+    };
 
     targetNode = () => {
         // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
-        const state = this.props.view.state;
+        const { state } = this.props.view;
         for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) {
             const m = state.doc.nodeAt(i);
             if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docId === this.props.docId) {
@@ -141,7 +93,9 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
         setTimeout(() => {
             try {
                 this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2)));
-            } catch (e) {}
+            } catch (err) {
+                /* empty */
+            }
         }, 0);
         return undefined;
     };
@@ -154,7 +108,60 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
                 onPointerLeave={this.onPointerLeaveCollapsed}
                 onPointerEnter={this.onPointerEnterCollapsed}
                 onPointerUp={this.onPointerUpCollapsed}
-                onPointerDown={this.onPointerDownCollapsed}></span>
+                onPointerDown={this.onPointerDownCollapsed}
+            />
         );
     }
 }
+
+// creates an inline comment in a note when '>>' is typed.
+// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
+// the comment can be toggled on/off with the '<-' text anchor.
+export class DashDocCommentView {
+    dom: HTMLDivElement; // container for label and value
+    root: any;
+    node: any;
+
+    constructor(node: any, view: any, getPos: any) {
+        this.node = node;
+        this.dom = document.createElement('div');
+        this.dom.style.width = node.attrs.width;
+        this.dom.style.height = node.attrs.height;
+        this.dom.style.fontWeight = 'bold';
+        this.dom.style.position = 'relative';
+        this.dom.style.display = 'inline-block';
+        this.dom.onkeypress = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onkeydown = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onkeyup = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onmousedown = function (e: any) {
+            e.stopPropagation();
+        };
+
+        this.root = ReactDOM.createRoot(this.dom);
+        this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
+        (this as any).dom = this.dom;
+    }
+
+    setHeight = (hgt: number) => {
+        !this.node.attrs.reflow &&
+            DocServer.GetRefField(this.node.attrs.docId).then(doc => {
+                doc instanceof Doc && (this.dom.style.height = hgt + '');
+            });
+    };
+
+    destroy() {
+        this.root.unmount();
+    }
+    deselectNode() {
+        this.dom.classList.remove('ProseMirror-selectednode');
+    }
+    selectNode() {
+        this.dom.classList.add('ProseMirror-selectednode');
+    }
+}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index dee7d70bb..f311b3cdd 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,10 +1,10 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { NodeSelection } from 'prosemirror-state';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
-import { Utils } from '../../../../Utils';
-import { returnFalse } from '../../../../ClientUtils';
+import { ClientUtils, returnFalse } from '../../../../ClientUtils';
 import { Doc } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { NumCast } from '../../../../fields/Types';
@@ -16,63 +16,7 @@ import { DocumentView } from '../DocumentView';
 import { FocusViewOptions } from '../FieldView';
 import { FormattedTextBox } from './FormattedTextBox';
 
-var horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
-export class DashDocView {
-    dom: HTMLSpanElement; // container for label and value
-    root: any;
-
-    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-        this.dom = document.createElement('span');
-        this.dom.style.position = 'relative';
-        this.dom.style.textIndent = '0';
-        this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString();
-        this.dom.style.height = node.attrs.height;
-        this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
-        (this.dom.style as any).float = node.attrs.float;
-        this.dom.onkeypress = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onkeydown = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onkeyup = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onmousedown = function (e: any) {
-            e.stopPropagation();
-        };
-
-        this.root = ReactDOM.createRoot(this.dom);
-        this.root.render(
-            <DashDocViewInternal
-                docId={node.attrs.docId}
-                embedding={node.attrs.embedding}
-                width={node.attrs.width}
-                height={node.attrs.height}
-                hidden={node.attrs.hidden}
-                fieldKey={node.attrs.fieldKey}
-                tbox={tbox}
-                view={view}
-                node={node}
-                getPos={getPos}
-            />
-        );
-    }
-    destroy() {
-        setTimeout(() => {
-            try {
-                this.root.unmount();
-            } catch {}
-        });
-    }
-    deselectNode() {
-        this.dom.style.backgroundColor = '';
-    }
-    selectNode() {
-        this.dom.style.backgroundColor = 'rgb(141, 182, 247)';
-    }
-}
-
+const horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
 interface IDashDocViewInternal {
     docId: string;
     embedding: string;
@@ -85,6 +29,7 @@ interface IDashDocViewInternal {
     node: any;
     getPos: any;
 }
+
 @observer
 export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewInternal> {
     _spanRef = React.createRef<HTMLDivElement>();
@@ -158,7 +103,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
 
     getDocTransform = () => {
         if (!this._spanRef.current) return Transform.Identity();
-        const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
+        const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._spanRef.current);
         return new Transform(-translateX, -translateY, 1).scale(1 / scale);
     };
     outerFocus = (target: Doc, options: FocusViewOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
@@ -227,3 +172,61 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
         );
     }
 }
+
+export class DashDocView {
+    dom: HTMLSpanElement; // container for label and value
+    root: any;
+
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this.dom = document.createElement('span');
+        this.dom.style.position = 'relative';
+        this.dom.style.textIndent = '0';
+        this.dom.style.width = (+node.attrs.width.toString().replace('px', '') + horizPadding).toString();
+        this.dom.style.height = node.attrs.height;
+        this.dom.style.display = node.attrs.hidden ? 'none' : 'inline-block';
+        (this.dom.style as any).float = node.attrs.float;
+        this.dom.onkeypress = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onkeydown = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onkeyup = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onmousedown = function (e: any) {
+            e.stopPropagation();
+        };
+
+        this.root = ReactDOM.createRoot(this.dom);
+        this.root.render(
+            <DashDocViewInternal
+                docId={node.attrs.docId}
+                embedding={node.attrs.embedding}
+                width={node.attrs.width}
+                height={node.attrs.height}
+                hidden={node.attrs.hidden}
+                fieldKey={node.attrs.fieldKey}
+                tbox={tbox}
+                view={view}
+                node={node}
+                getPos={getPos}
+            />
+        );
+    }
+    destroy() {
+        setTimeout(() => {
+            try {
+                this.root.unmount();
+            } catch {
+                /* empty */
+            }
+        });
+    }
+    deselectNode() {
+        this.dom.style.backgroundColor = '';
+    }
+    selectNode() {
+        this.dom.style.backgroundColor = 'rgb(141, 182, 247)';
+    }
+}
diff --git a/src/client/views/nodes/formattedText/EquationEditor.tsx b/src/client/views/nodes/formattedText/EquationEditor.tsx
index b4102e08e..d9b1a2cf8 100644
--- a/src/client/views/nodes/formattedText/EquationEditor.tsx
+++ b/src/client/views/nodes/formattedText/EquationEditor.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/require-default-props */
 import React, { Component, createRef } from 'react';
 
 // Import JQuery, required for the functioning of the equation editor
@@ -5,11 +6,9 @@ import $ from 'jquery';
 
 import './EquationEditor.scss';
 
-// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
 // @ts-ignore
 window.jQuery = $;
 
-// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
 // @ts-ignore
 require('mathquill/build/mathquill');
 
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index b90653acc..5167c8f2a 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
 import { IReactionDisposer } from 'mobx';
 import { observer } from 'mobx-react';
 import { TextSelection } from 'prosemirror-state';
@@ -10,44 +11,6 @@ import EquationEditor from './EquationEditor';
 import { FormattedTextBox } from './FormattedTextBox';
 import { DocData } from '../../../../fields/DocSymbols';
 
-export class EquationView {
-    dom: HTMLDivElement; // container for label and value
-    root: any;
-    tbox: FormattedTextBox;
-    view: any;
-    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-        this.tbox = tbox;
-        this.view = view;
-        this.dom = document.createElement('div');
-        this.dom.style.width = node.attrs.width;
-        this.dom.style.height = node.attrs.height;
-        this.dom.style.position = 'relative';
-        this.dom.style.display = 'inline-block';
-        this.dom.onmousedown = function (e: any) {
-            e.stopPropagation();
-        };
-
-        this.root = ReactDOM.createRoot(this.dom);
-        this.root.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />);
-    }
-    _editor: EquationEditor | undefined;
-    setEditor = (editor?: EquationEditor) => (this._editor = editor);
-    destroy() {
-        this.root.unmount();
-    }
-    setSelection() {
-        this._editor?.mathField.focus();
-    }
-    selectNode() {
-        this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus
-        setTimeout(() => {
-            this._editor?.mathField.focus();
-            setTimeout(() => (this.tbox._applyingChange = ''));
-        });
-    }
-    deselectNode() {}
-}
-
 interface IEquationViewInternal {
     fieldKey: string;
     tbox: FormattedTextBox;
@@ -70,12 +33,12 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
         this._textBoxDoc = props.tbox.Document;
     }
 
-    componentWillUnmount() {
-        this._reactionDisposer?.();
-    }
     componentDidMount() {
         this.props.setEditor(this._ref.current ?? undefined);
     }
+    componentWillUnmount() {
+        this._reactionDisposer?.();
+    }
 
     render() {
         return (
@@ -100,12 +63,56 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
                 <EquationEditor
                     ref={this._ref}
                     value={StrCast(this._textBoxDoc[DocData][this._fieldKey])}
-                    onChange={(str: any) => (this._textBoxDoc[DocData][this._fieldKey] = str)}
+                    onChange={(str: any) => {
+                        this._textBoxDoc[DocData][this._fieldKey] = str;
+                    }}
                     autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
                     autoOperatorNames="sin cos tan"
-                    spaceBehavesLikeTab={true}
+                    spaceBehavesLikeTab
                 />
             </div>
         );
     }
 }
+
+export class EquationView {
+    dom: HTMLDivElement; // container for label and value
+    root: any;
+    tbox: FormattedTextBox;
+    view: any;
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this.tbox = tbox;
+        this.view = view;
+        this.dom = document.createElement('div');
+        this.dom.style.width = node.attrs.width;
+        this.dom.style.height = node.attrs.height;
+        this.dom.style.position = 'relative';
+        this.dom.style.display = 'inline-block';
+        this.dom.onmousedown = function (e: any) {
+            e.stopPropagation();
+        };
+
+        this.root = ReactDOM.createRoot(this.dom);
+        this.root.render(<EquationViewInternal fieldKey={node.attrs.fieldKey} width={node.attrs.width} height={node.attrs.height} getPos={getPos} setEditor={this.setEditor} tbox={tbox} />);
+    }
+    _editor: EquationEditor | undefined;
+    setEditor = (editor?: EquationEditor) => {
+        this._editor = editor;
+    };
+    destroy() {
+        this.root.unmount();
+    }
+    setSelection() {
+        this._editor?.mathField.focus();
+    }
+    selectNode() {
+        this.tbox._applyingChange = this.tbox.fieldKey; // setting focus will make prosemirror lose focus, which will cause it to change its selection to a text selection, which causes this view to get rebuilt but it's no longer node selected, so the equationview won't have focus
+        setTimeout(() => {
+            this._editor?.mathField.focus();
+            setTimeout(() => {
+                this.tbox._applyingChange = '';
+            });
+        });
+    }
+    deselectNode() {}
+}
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
index b327e5137..4641da2e9 100644
--- a/src/client/views/nodes/formattedText/FootnoteView.tsx
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -2,9 +2,9 @@ import { EditorView } from 'prosemirror-view';
 import { EditorState } from 'prosemirror-state';
 import { keymap } from 'prosemirror-keymap';
 import { baseKeymap, toggleMark } from 'prosemirror-commands';
-import { schema } from './schema_rts';
 import { redo, undo } from 'prosemirror-history';
 import { StepMap } from 'prosemirror-transform';
+import { schema } from './schema_rts';
 
 export class FootnoteView {
     innerView: any;
@@ -100,8 +100,8 @@ export class FootnoteView {
         this.innerView.updateState(state);
 
         if (!tr.getMeta('fromOutside')) {
-            const outerTr = this.outerView.state.tr,
-                offsetMap = StepMap.offset(this.getPos() + 1);
+            const outerTr = this.outerView.state.tr;
+            const offsetMap = StepMap.offset(this.getPos() + 1);
             for (const transaction of transactions) {
                 for (const step of transaction.steps) {
                     outerTr.step(step.map(offsetMap));
@@ -115,7 +115,7 @@ export class FootnoteView {
         if (!node.sameMarkup(this.node)) return false;
         this.node = node;
         if (this.innerView) {
-            const state = this.innerView.state;
+            const { state } = this.innerView;
             const start = node.content.findDiffStart(state.doc.content);
             if (start !== null) {
                 let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 52d93ec38..01c46edeb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -19,20 +19,22 @@ export function findLinkMark(marks: readonly Mark[]): Mark | undefined {
     return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor);
 }
 export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
-    let before = 0,
-        nbef = rpos.nodeBefore;
+    let before = 0;
+    let nbef = rpos.nodeBefore;
     while (nbef && finder(nbef.marks)) {
         before += nbef.nodeSize;
+        // eslint-disable-next-line no-param-reassign
         rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
         rpos && (nbef = rpos.nodeBefore);
     }
     return before;
 }
 export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) {
-    let after = 0,
-        naft = rpos.nodeAfter;
+    let after = 0;
+    let naft = rpos.nodeAfter;
     while (naft && finder(naft.marks)) {
         after += naft.nodeSize;
+        // eslint-disable-next-line no-param-reassign
         rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
         rpos && (naft = rpos.nodeAfter);
     }
@@ -80,10 +82,10 @@ export class FormattedTextBoxComment {
     }
 
     static showCommentbox(view: EditorView, nbef: number) {
-        const state = view.state;
+        const { state } = view;
         // These are in screen coordinates
-        const start = view.coordsAtPos(state.selection.from - nbef),
-            end = view.coordsAtPos(state.selection.from - nbef);
+        const start = view.coordsAtPos(state.selection.from - nbef);
+        const end = view.coordsAtPos(state.selection.from - nbef);
         // The box in which the tooltip is positioned, to use as base
         const box = (document.getElementsByClassName('mainView-container') as any)[0].getBoundingClientRect();
         // Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left)
@@ -110,14 +112,16 @@ export class FormattedTextBoxComment {
     }
 
     static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string, noPreview?: boolean) {
-        const state = view.state;
+        const { state } = view;
         // this section checks to see if the insertion point is over text entered by a different user.  If so, it sets ths comment text to indicate the user and the modification date
         if (state.selection.$from) {
             const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
             const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
             const noselection = state.selection.$from === state.selection.$to;
             let child: any = null;
-            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any /* , pos: number, parent: any */) => {
+                !child && node.marks.length && (child = node);
+            });
             const mark = child && findOtherUserMark(child.marks);
             if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
                 FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
@@ -132,7 +136,7 @@ export class FormattedTextBoxComment {
         if (state.selection.$from && hrefs?.length) {
             const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
             const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
-            //nbef &&
+            // nbef &&
             naft &&
                 LinkInfo.SetLinkInfo({
                     DocumentView: textBox.DocumentView,
diff --git a/src/client/views/nodes/formattedText/OrderedListView.tsx b/src/client/views/nodes/formattedText/OrderedListView.tsx
index c3595e59b..dbc60f7bf 100644
--- a/src/client/views/nodes/formattedText/OrderedListView.tsx
+++ b/src/client/views/nodes/formattedText/OrderedListView.tsx
@@ -1,8 +1,7 @@
 export class OrderedListView {
-
-    update(node: any) {
-        // if attr's of an ordered_list (e.g., bulletStyle) change, 
+    update() {
+        // if attr's of an ordered_list (e.g., bulletStyle) change,
         // return false forces the dom node to be recreated which is necessary for the bullet labels to update
-        return false; 
+        return false;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
index 30da91710..6c88a0d29 100644
--- a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
+++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
@@ -1,18 +1,19 @@
+/* eslint-disable import/extensions */
+import { Node, DOMOutputSpec } from 'prosemirror-model';
 import clamp from '../../../util/clamp';
 import convertToCSSPTValue from '../../../util/convertToCSSPTValue';
 import toCSSLineSpacing from '../../../util/toCSSLineSpacing';
-import { Node, DOMOutputSpec } from 'prosemirror-model';
 
-//import type { NodeSpec } from './Types';
+// import type { NodeSpec } from './Types';
 type NodeSpec = {
-    attrs?: { [key: string]: any },
-    content?: string,
-    draggable?: boolean,
-    group?: string,
-    inline?: boolean,
-    name?: string,
-    parseDOM?: Array<any>,
-    toDOM?: (node: any) => DOMOutputSpec,
+    attrs?: { [key: string]: any };
+    content?: string;
+    draggable?: boolean;
+    group?: string;
+    inline?: boolean;
+    name?: string;
+    parseDOM?: Array<any>;
+    toDOM?: (node: any) => DOMOutputSpec;
 };
 
 // This assumes that every 36pt maps to one indent level.
@@ -25,41 +26,18 @@ export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']);
 
 const ALIGN_PATTERN = /(left|right|center|justify)/;
 
-// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
-// :: NodeSpec A plain paragraph textblock. Represented in the DOM
-// as a `<p>` element.
-export const ParagraphNodeSpec: NodeSpec = {
-    attrs: {
-        align: { default: null },
-        color: { default: null },
-        id: { default: null },
-        indent: { default: null },
-        inset: { default: null },
-        lineSpacing: { default: null },
-        // TODO: Add UI to let user edit / clear padding.
-        paddingBottom: { default: null },
-        // TODO: Add UI to let user edit / clear padding.
-        paddingTop: { default: null },
-    },
-    content: 'inline*',
-    group: 'block',
-    parseDOM: [{ tag: 'p', getAttrs }],
-    toDOM,
-};
+function convertMarginLeftToIndentValue(marginLeft: string): number {
+    const ptValue = convertToCSSPTValue(marginLeft);
+    return clamp(MIN_INDENT_LEVEL, Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), MAX_INDENT_LEVEL);
+}
 
 function getAttrs(dom: HTMLElement): Object {
-    const {
-        lineHeight,
-        textAlign,
-        marginLeft,
-        paddingTop,
-        paddingBottom,
-    } = dom.style;
+    const { lineHeight, textAlign, marginLeft, paddingTop, paddingBottom } = dom.style;
 
     let align = dom.getAttribute('align') || textAlign || '';
-    align = ALIGN_PATTERN.test(align) ? align : "";
+    align = ALIGN_PATTERN.test(align) ? align : '';
 
-    let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10);
+    let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || '', 10);
 
     if (!indent && marginLeft) {
         indent = convertMarginLeftToIndentValue(marginLeft);
@@ -74,15 +52,7 @@ function getAttrs(dom: HTMLElement): Object {
 }
 
 function toDOM(node: Node): DOMOutputSpec {
-    const {
-        align,
-        indent,
-        inset,
-        lineSpacing,
-        paddingTop,
-        paddingBottom,
-        id,
-    } = node.attrs;
+    const { align, indent, inset, lineSpacing, paddingTop, paddingBottom, id } = node.attrs;
     const attrs: { [key: string]: any } | null = {};
 
     let style = '';
@@ -128,16 +98,29 @@ function toDOM(node: Node): DOMOutputSpec {
     return ['p', attrs, 0];
 }
 
+// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
+// :: NodeSpec A plain paragraph textblock. Represented in the DOM
+// as a `<p>` element.
+export const ParagraphNodeSpec: NodeSpec = {
+    attrs: {
+        align: { default: null },
+        color: { default: null },
+        id: { default: null },
+        indent: { default: null },
+        inset: { default: null },
+        lineSpacing: { default: null },
+        // TODO: Add UI to let user edit / clear padding.
+        paddingBottom: { default: null },
+        // TODO: Add UI to let user edit / clear padding.
+        paddingTop: { default: null },
+    },
+    content: 'inline*',
+    group: 'block',
+    parseDOM: [{ tag: 'p', getAttrs }],
+    toDOM,
+};
+
 export const toParagraphDOM = toDOM;
 export const getParagraphNodeAttrs = getAttrs;
 
-export function convertMarginLeftToIndentValue(marginLeft: string): number {
-    const ptValue = convertToCSSPTValue(marginLeft);
-    return clamp(
-        MIN_INDENT_LEVEL,
-        Math.floor(ptValue / INDENT_MARGIN_PT_SIZE),
-        MAX_INDENT_LEVEL
-    );
-}
-
-export default ParagraphNodeSpec;
\ No newline at end of file
+export default ParagraphNodeSpec;
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 88adab66e..c107a6724 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -14,6 +14,7 @@ import { CollectionView } from '../../collections/CollectionView';
 import { ContextMenu } from '../../ContextMenu';
 import { KeyValueBox } from '../KeyValueBox';
 import { FormattedTextBox } from './FormattedTextBox';
+// eslint-disable-next-line import/extensions
 import { wrappingInputRule } from './prosemirrorPatches';
 import { RichTextMenu } from './RichTextMenu';
 import { schema } from './schema_rts';
@@ -253,7 +254,7 @@ export class RichTextRules {
                 }
                 if (marks) {
                     const tr = state.tr.deleteRange(start, end);
-                    return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
+                    return marks ? Array.from(marks).reduce((tr2, m) => tr2.addStoredMark(m), tr) : tr;
                 }
 
                 const isValidColor = (strColor: string) => {
@@ -283,7 +284,7 @@ export class RichTextRules {
                 return marks
                     ? Array.from(marks)
                           .filter(m => m.type !== state.schema.marks.user_mark)
-                          .reduce((tr, m) => tr.removeStoredMark(m), tr)
+                          .reduce((tr2, m) => tr2.removeStoredMark(m), tr)
                     : tr;
             }),
 
@@ -309,11 +310,11 @@ export class RichTextRules {
                             teditor.dispatch(tstate.tr.setSelection(new TextSelection(tstate.doc.resolve(selection))));
                         }
                     };
-                    const getTitledDoc = (docTitle: string) => {
-                        if (!DocServer.FindDocByTitle(docTitle)) {
-                            Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true });
+                    const getTitledDoc = (title: string) => {
+                        if (!DocServer.FindDocByTitle(title)) {
+                            Docs.Create.TextDocument('', { title: title, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true });
                         }
-                        const titledDoc = DocServer.FindDocByTitle(docTitle);
+                        const titledDoc = DocServer.FindDocByTitle(title);
                         return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
                     };
                     const target = getTitledDoc(docTitle);
@@ -336,7 +337,7 @@ export class RichTextRules {
                     const assign = match[4] === ':' ? (match[4] = '') : match[4];
                     const value = match[5];
                     const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('=');
-                    const getTitledDoc = (docTitle: string) => DocServer.FindDocByTitle(docTitle);
+                    const getTitledDoc = (title: string) => DocServer.FindDocByTitle(title);
                     // if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' )
                     if (value?.includes(',') && !value.startsWith('((')) {
                         const values = value.split(',');
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 7ec296ed2..2c366b49b 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -3,6 +3,15 @@ import { Fragment, Node, Slice } from 'prosemirror-model';
 import * as ReactDOM from 'react-dom/client';
 import * as React from 'react';
 
+interface ISummaryView {}
+// currently nothing needs to be rendered for the internal view of a summary.
+// eslint-disable-next-line react/prefer-stateless-function
+export class SummaryViewInternal extends React.Component<ISummaryView> {
+    render() {
+        return null;
+    }
+}
+
 // an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
 // this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't
 // really need a react view.  However, it would be cleaner to figure out how to do this just as a react rendering
@@ -32,8 +41,8 @@ export class SummaryView {
         };
 
         const js = node.toJSON;
-        node.toJSON = function () {
-            return js.apply(this, arguments);
+        node.toJSON = function (...args: any[]) {
+            return js.apply(this, args);
         };
 
         this.root = ReactDOM.createRoot(this.dom);
@@ -54,7 +63,8 @@ export class SummaryView {
         const visited = new Set();
         for (let i: number = start + 1; i < view.state.doc.nodeSize - 1; i++) {
             let skip = false;
-            view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
+            // eslint-disable-next-line no-loop-func
+            view.state.doc.nodesBetween(start, i, (node: Node /* , pos: number, parent: Node, index: number */) => {
                 if (node.isLeaf && !visited.has(node) && !skip) {
                     if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
                         visited.add(node);
@@ -87,11 +97,3 @@ export class SummaryView {
         this.dom.className = this.className(visible);
     };
 }
-
-interface ISummaryView {}
-// currently nothing needs to be rendered for the internal view of a summary.
-export class SummaryViewInternal extends React.Component<ISummaryView> {
-    render() {
-        return <> </>;
-    }
-}
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 95eb86720..b195654ce 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
 /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
 /* eslint-disable jsx-a11y/img-redundant-alt */
 /* eslint-disable jsx-a11y/click-events-have-key-events */
@@ -56,7 +57,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     // format: array of [image source, corresponding image Doc]
     const [edits, setEdits] = useState<(string | Doc)[][]>([]);
     const [edited, setEdited] = useState(false);
-    const [brushStyle, setBrushStyle] = useState<BrushStyle>(BrushStyle.ADD);
+    const [brushStyle] = useState<BrushStyle>(BrushStyle.ADD);
     const [input, setInput] = useState('');
     const [loading, setLoading] = useState(false);
     const [canvasDims, setCanvasDims] = useState<ImageDimensions>({
@@ -102,8 +103,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         if (!ctx || !currImg.current || !canvasRef.current) return;
 
         const target = redoStack.current[redoStack.current.length - 1];
-        if (!target) {
-        } else {
+        if (target) {
             undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()];
             const img = new Image();
             img.src = target;
@@ -139,7 +139,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     };
 
     // stop brushing, push to undo stack
-    const handlePointerUp = (e: React.PointerEvent) => {
+    const handlePointerUp = () => {
         const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef);
         if (!ctx) return;
         if (!isBrushing) return;
@@ -148,11 +148,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
     // handles brushing on pointer movement
     useEffect(() => {
-        if (!isBrushing) return;
+        if (!isBrushing) return undefined;
         const canvas = canvasRef.current;
-        if (!canvas) return;
+        if (!canvas) return undefined;
         const ctx = ImageUtility.getCanvasContext(canvasRef);
-        if (!ctx) return;
+        if (!ctx) return undefined;
 
         const handlePointerMove = (e: PointerEvent) => {
             const currPoint = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale);
@@ -300,6 +300,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                     CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
 
                     // add the doc to the main freeform
+                    // eslint-disable-next-line no-use-before-define
                     await createNewImgDoc(originalImg.current, true);
                 }
             } else {
@@ -313,12 +314,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height)));
                 const imgRes = await Promise.all(
                     imgUrls.map(async url => {
+                        // eslint-disable-next-line no-use-before-define
                         const saveRes = await onSave(url);
                         return [url, saveRes as Doc];
                     })
                 );
                 setEdits(imgRes);
                 const image = new Image();
+                // eslint-disable-next-line prefer-destructuring
                 image.src = imgUrls[0];
                 ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height);
                 currImg.current = image;
@@ -351,7 +354,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
     // creates a new image document and returns its reference
     const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise<Doc | undefined> => {
-        if (!imageRootDoc) return;
+        if (!imageRootDoc) return undefined;
         const { src } = img;
         const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
         const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
@@ -375,7 +378,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             parentDoc.current = newImg;
             return newImg;
         }
-        if (!parentDoc.current) return;
+        if (!parentDoc.current) return undefined;
         const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
         const initialY = 0;
 
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index 185ba2280..d1f68ee0e 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -1,9 +1,9 @@
 import './GenerativeFillButtons.scss';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
-import { activeColor } from './generativeFillUtils/generativeFillConstants';
 import { Button, IconButton, Type } from 'browndash-components';
 import { AiOutlineInfo } from 'react-icons/ai';
+import { activeColor } from './generativeFillUtils/generativeFillConstants';
 
 interface ButtonContainerProps {
     getEdit: () => Promise<void>;
@@ -11,7 +11,7 @@ interface ButtonContainerProps {
     onReset: () => void;
 }
 
-const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
+function Buttons({ loading, getEdit, onReset }: ButtonContainerProps) {
     return (
         <div className="generativeFillBtnContainer">
             <Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} />
@@ -20,7 +20,7 @@ const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
                     text="GET EDITS"
                     type={Type.TERT}
                     color={activeColor}
-                    icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
+                    icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
                     iconPlacement="right"
                     onClick={() => {
                         if (!loading) getEdit();
@@ -36,9 +36,9 @@ const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
                     }}
                 />
             )}
-            <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size={'16px'} />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
+            <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size="16px" />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
         </div>
     );
-};
+}
 
 export default Buttons;
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
index 97e03ff20..6da8c3da0 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
@@ -1,10 +1,6 @@
 import { Point } from './generativeFillInterfaces';
 
 export class GenerativeFillMathHelpers {
-    static distanceBetween = (p1: Point, p2: Point) => {
-        return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
-    };
-    static angleBetween = (p1: Point, p2: Point) => {
-        return Math.atan2(p2.x - p1.x, p2.y - p1.y);
-    };
+    static distanceBetween = (p1: Point, p2: Point) => Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
+    static angleBetween = (p1: Point, p2: Point) => Math.atan2(p2.x - p1.x, p2.y - p1.y);
 }
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 47a14135f..24dba1778 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -17,15 +17,14 @@ export class ImageUtility {
      * @param canvas Canvas to convert
      * @returns Blob of canvas
      */
-    static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> => {
-        return new Promise(resolve => {
+    static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> =>
+        new Promise(resolve => {
             canvas.toBlob(blob => {
                 if (blob) {
                     resolve(blob);
                 }
             }, 'image/png');
         });
-    };
 
     // given a square api image, get the cropped img
     static getCroppedImg = (img: HTMLImageElement, width: number, height: number): HTMLCanvasElement | undefined => {
@@ -48,11 +47,12 @@ export class ImageUtility {
             }
             return canvas;
         }
+        return undefined;
     };
 
     // converts an image to a canvas data url
-    static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> => {
-        return new Promise<string>((resolve, reject) => {
+    static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> =>
+        new Promise<string>((resolve, reject) => {
             const img = new Image();
             img.onload = () => {
                 const canvas = this.getCroppedImg(img, width, height);
@@ -66,7 +66,6 @@ export class ImageUtility {
             };
             img.src = imageSrc;
         });
-    };
 
     // calls the openai api to get image edits
     static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
@@ -91,7 +90,7 @@ export class ImageUtility {
             console.log(data.data);
             return {
                 status: 'success',
-                urls: (data.data as { b64_json: string }[]).map(data => `data:image/png;base64,${data.b64_json}`),
+                urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`),
             };
         } catch (err) {
             console.log(err);
@@ -100,12 +99,10 @@ export class ImageUtility {
     };
 
     // mock api call
-    static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => {
-        return {
-            status: 'success',
-            urls: [mockSrc, mockSrc, mockSrc],
-        };
-    };
+    static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => ({
+        status: 'success',
+        urls: [mockSrc, mockSrc, mockSrc],
+    });
 
     // Gets the canvas rendering context of a canvas
     static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => {
@@ -150,12 +147,12 @@ export class ImageUtility {
 
     // Draws the image to the current canvas
     static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject<HTMLCanvasElement>, width: number, height: number) => {
-        const drawImg = (img: HTMLImageElement) => {
+        const drawImg = (htmlImg: HTMLImageElement) => {
             const ctx = this.getCanvasContext(canvasRef);
             if (!ctx) return;
             ctx.globalCompositeOperation = 'source-over';
             ctx.clearRect(0, 0, width, height);
-            ctx.drawImage(img, 0, 0, width, height);
+            ctx.drawImage(htmlImg, 0, 0, width, height);
         };
 
         if (img.complete) {
@@ -173,7 +170,7 @@ export class ImageUtility {
         canvas.width = canvasSize;
         canvas.height = canvasSize;
         const ctx = canvas.getContext('2d');
-        if (!ctx) return;
+        if (!ctx) return undefined;
         ctx?.clearRect(0, 0, canvasSize, canvasSize);
         ctx.drawImage(paddedCanvas, 0, 0);
 
@@ -195,7 +192,7 @@ export class ImageUtility {
     // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas)
     static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => {
         const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
-        const data = imageData.data;
+        const { data } = imageData;
         for (let i = 0; i < canvas.height; i++) {
             for (let j = 0; j < xOffset; j++) {
                 const targetIdx = 4 * (i * canvas.width + j);
@@ -224,7 +221,7 @@ export class ImageUtility {
     // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas)
     static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => {
         const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
-        const data = imageData.data;
+        const { data } = imageData;
         for (let j = 0; j < canvas.width; j++) {
             for (let i = 0; i < yOffset; i++) {
                 const targetIdx = 4 * (i * canvas.width + j);
@@ -256,7 +253,7 @@ export class ImageUtility {
         canvas.width = canvasSize;
         canvas.height = canvasSize;
         const ctx = canvas.getContext('2d');
-        if (!ctx) return;
+        if (!ctx) return undefined;
         // fix scaling
         const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
         const width = Math.floor(img.width * scale);
@@ -310,5 +307,6 @@ export class ImageUtility {
         } catch (err) {
             console.error(err);
         }
+        return undefined;
     };
 }
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
index 9e620ad11..260923a64 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
@@ -1,15 +1,11 @@
-import { Point } from "./generativeFillInterfaces";
+import { Point } from './generativeFillInterfaces';
 
 export class PointerHandler {
-  static getPointRelativeToElement = (
-    element: HTMLElement,
-    e: React.PointerEvent | PointerEvent,
-    scale: number
-  ): Point => {
-    const boundingBox = element.getBoundingClientRect();
-    return {
-      x: (e.clientX - boundingBox.x) / scale,
-      y: (e.clientY - boundingBox.y) / scale,
+    static getPointRelativeToElement = (element: HTMLElement, e: React.PointerEvent | PointerEvent, scale: number): Point => {
+        const boundingBox = element.getBoundingClientRect();
+        return {
+            x: (e.clientX - boundingBox.x) / scale,
+            y: (e.clientY - boundingBox.y) / scale,
+        };
     };
-  };
 }
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index dec9a5019..317719032 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -22,13 +22,14 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return (
             <div style={{ backgroundColor: 'pink' }}>
                 <DocumentView
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props} //
                     LayoutTemplateString={undefined}
                     Document={this.Document}
                     isContentActive={returnFalse}
                     addDocument={returnFalse}
                     ScreenToLocalTransform={this.screenToLocalXf}
-                    hideResizeHandles={true}
+                    hideResizeHandles
                 />
             </div>
         );
diff --git a/src/client/views/nodes/trails/index.ts b/src/client/views/nodes/trails/index.ts
index 8f3f7b03a..7b18974df 100644
--- a/src/client/views/nodes/trails/index.ts
+++ b/src/client/views/nodes/trails/index.ts
@@ -1,3 +1,3 @@
-export * from "./PresBox";
-export * from "./PresElementBox";
-export * from "./PresEnums";
\ No newline at end of file
+export * from './PresBox';
+export * from './PresElementBox';
+export * from './PresEnums';
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 02ff661e5..7b959533f 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -95,29 +95,29 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         setupMoveUpEvents(
             this,
             e,
-            (e: PointerEvent) => {
-                this.StartDrag(e, this._commentRef.current!);
+            (moveEv: PointerEvent) => {
+                this.StartDrag(moveEv, this._commentRef.current!);
                 return true;
             },
             returnFalse,
-            e => this.OnClick?.(e)
+            clickEv => this.OnClick?.(clickEv)
         );
     };
 
     audioDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnAudio?.(e));
+        setupMoveUpEvents(this, e, returnFalse, returnFalse, clickEv => this.OnAudio?.(clickEv));
     };
 
     cropDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(
             this,
             e,
-            (e: PointerEvent) => {
-                this.StartCropDrag(e, this._cropRef.current!);
+            (moveEv: PointerEvent) => {
+                this.StartCropDrag(moveEv, this._cropRef.current!);
                 return true;
             },
             returnFalse,
-            e => this.OnCrop?.(e)
+            clickev => this.OnCrop?.(clickev)
         );
     };
 
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 4fff2a8e7..7a1e35636 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -30,7 +30,6 @@ export class Uploader extends React.Component<ImageUploadProps> {
     @observable nm: string = 'Choose files'; // Text of 'Choose Files' button
     @observable process: string = ''; // Current status of upload
     @observable private dialogueBoxOpacity = 1;
-    @observable private overlayOpacity = 0.4;
 
     onClick = async () => {
         try {
@@ -167,6 +166,6 @@ export class Uploader extends React.Component<ImageUploadProps> {
     };
 
     render() {
-        return <MainViewModal contents={this.uploadInterface} isDisplayed interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.closeUpload} />;
+        return <MainViewModal contents={this.uploadInterface} isDisplayed interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.closeUpload} />;
     }
 }
-- 
cgit v1.2.3-70-g09d2


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/pdf/AnchorMenu.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


From d40efe81ab30485266b7b686dfb35c531e75a568 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 8 May 2024 23:16:54 -0400
Subject: fixed text selection on web pages.  cleaned up gptSummarize to work
 on any text.

---
 src/ClientUtils.ts                                        |  2 +-
 src/client/views/MarqueeAnnotator.tsx                     |  1 +
 src/client/views/nodes/DocumentView.tsx                   |  9 +++++++++
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  3 ++-
 src/client/views/pdf/AnchorMenu.tsx                       | 15 ++++-----------
 5 files changed, 17 insertions(+), 13 deletions(-)

(limited to 'src/client/views/pdf/AnchorMenu.tsx')

diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index dbbba896f..d03ae1486 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -518,7 +518,7 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe
 export function getWordAtPoint(elem: any, x: number, y: number): string | undefined {
     if (elem.tagName === 'INPUT') return 'input';
     if (elem.tagName === 'TEXTAREA') return 'textarea';
-    if (elem.nodeType === elem.TEXT_NODE) {
+    if (elem.nodeType === elem.TEXT_NODE || elem.textContent) {
         const range = elem.ownerDocument.createRange();
         range.selectNodeContents(elem);
         let currentPos = 0;
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index a917a7d57..c18ac6738 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -258,6 +258,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
     };
     @action
     onEnd = (x: number, y: number) => {
+        AnchorMenu.Instance.setSelectedText('');
         const marquees = this.props.marqueeContainer.getElementsByClassName('marqueeAnnotator-dragBox');
         const marqueeStyle = (Array.from(marquees).lastElement() as HTMLDivElement)?.style;
         if (!this.isEmpty && marqueeStyle) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8df28a770..ada3de355 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -981,6 +981,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 @observer
 export class DocumentView extends DocComponent<DocumentViewProps>() {
     public static ROOT_DIV = 'documentView-effectsWrapper';
+    /**
+     * Opens a new Tab for the doc in the specified location (or in the lightbox)
+     */
     public static addSplit: (Doc: Doc, where: OpenWhereMod) => void;
     // Lightbox
     public static _lightboxDoc: () => Doc | undefined;
@@ -1012,7 +1015,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     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;
+    /**
+     * returns a list of all currently selected DocumentViews
+     */
     public static Selected: () => DocumentView[];
+    /**
+     * returns a list of all currently selected Docs
+     */
     public static SelectedDocs: () => Doc[];
     public static SelectSchemaDoc: (doc: Doc, deselectAllFirst?: boolean) => void;
     public static SelectedSchemaDoc: () => Opt<Doc>;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e354aedb7..624721d7c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -240,7 +240,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     @action
     setupAnchorMenu = () => {
         AnchorMenu.Instance.Status = 'marquee';
-
         AnchorMenu.Instance.OnClick = () => {
             !this.layoutDoc.layout_showSidebar && this.toggleSidebar();
             setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created
@@ -292,6 +291,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.DocumentView?.()!, () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
         });
+
+        AnchorMenu.Instance.setSelectedText(window.getSelection()?.toString() ?? '');
         const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
         this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
         let ele: Opt<HTMLDivElement>;
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index a837969aa..495ea59f0 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -36,10 +36,10 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     @observable public Status: 'marquee' | 'annotation' | '' = '';
 
     // GPT additions
-    @observable private selectedText: string = '';
+    @observable private _selectedText: string = '';
     @action
     public setSelectedText = (txt: string) => {
-        this.selectedText = txt;
+        this._selectedText = txt.trim();
     };
 
     public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
@@ -76,7 +76,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * @param e pointer down event
      */
     gptSummarize = async () => {
-        GPTPopup.Instance?.setSelectedText(this.selectedText);
+        GPTPopup.Instance?.setSelectedText(this._selectedText);
         GPTPopup.Instance.generateSummary();
     };
 
@@ -140,13 +140,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         this.highlightColor = ClientUtils.colorString(col);
     };
 
-    /**
-     * Returns whether the selected text can be summarized. The goal is to have
-     * 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 = () => DocumentView.SelectedDocs().some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any));
-
     render() {
         const buttons =
             this.Status === 'marquee' ? (
@@ -161,7 +154,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                         />
                     </div>
                     {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */}
-                    {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
+                    {this._selectedText && (
                         <IconButton
                             tooltip="Summarize with AI" //
                             onPointerDown={this.gptSummarize}
-- 
cgit v1.2.3-70-g09d2

