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/PDFViewer.tsx | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index aaff2a342..0ab952e84 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -10,7 +10,8 @@ import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils';
 import { DocUtils } from '../../documents/Documents';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -98,9 +99,13 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     }
 
     componentDidMount() {
-        runInAction(() => (this._showWaiting = true));
+        runInAction(() => {
+            this._showWaiting = true;
+        });
         this.setupPdfJsViewer();
-        this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0));
+        this._mainCont.current?.addEventListener('scroll', e => {
+            (e.target as any).scrollLeft = 0;
+        });
 
         this._disposers.layout_autoHeight = reaction(
             () => this._props.layoutDoc._layout_autoHeight,
@@ -176,7 +181,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         let focusSpeed: Opt<number>;
         if (doc !== this._props.Document && mainCont) {
             const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
+            const scrollTo = ClientUtils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
             if (scrollTo !== undefined && scrollTo !== this._props.layoutDoc._layout_scrollTop) {
                 if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc };
                 else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper);
@@ -456,7 +461,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
     onClick = (e: React.MouseEvent) => {
         this._scrollStopper?.();
-        if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+        if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < ClientUtils.DRAG_THRESHOLD) {
             this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
         }
         // e.stopPropagation();  // bcz: not sure why this was here.  We need to allow the DocumentView to get clicks to process doubleClicks
@@ -496,8 +501,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     overlayTransform = () => this.scrollXf().scale(1 / NumCast(this._props.layoutDoc._freeform_scale, 1));
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1);
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
-    transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
-    opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
+    transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this._props.childFilters(), ClientUtils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [ClientUtils.OpaqueBackgroundFilter])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
@@ -532,7 +537,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 PanelWidth={this.panelWidth}
                 ScreenToLocalTransform={this.overlayTransform}
                 isAnyChildContentActive={returnFalse}
-                isAnnotationOverlayScrollable={true}
+                isAnnotationOverlayScrollable
                 childFilters={childFilters}
                 select={emptyFunction}
                 styleProvider={this.childStyleProvider}
-- 
cgit v1.2.3-70-g09d2


From aa4f7b37483c516b92181d3374d3151972b98383 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 Apr 2024 14:56:48 -0400
Subject: fixed search on pdfs to display results when pDF is not selected. 
 fixed presentation transitions to animate.  changed so that annotaitons on
 pdfs would highlight when following a pres slide.   fixed scrolling to
 annotations (and other viewSpecs) from presentations by using the slide
 target, not the slide as the focus document.  cleaned up search and fixed to
 unhighlight searches on close.  fixe pdf search unhighligting to work.

---
 src/ClientUtils.ts                                 |  17 +-
 src/client/util/DocumentManager.ts                 |   4 +-
 src/client/util/SerializationHelper.ts             |   4 +-
 src/client/views/MarqueeAnnotator.tsx              |  12 +-
 src/client/views/PropertiesSection.tsx             |   3 +-
 src/client/views/PropertiesView.tsx                |   9 +-
 .../CollectionFreeFormPannableContents.tsx         |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   3 +-
 src/client/views/nodes/FieldView.tsx               |   1 -
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |   2 +-
 src/client/views/nodes/PDFBox.scss                 |   2 +-
 src/client/views/nodes/PDFBox.tsx                  |  12 +-
 src/client/views/nodes/trails/PresBox.tsx          |   3 +-
 src/client/views/pdf/Annotation.tsx                |  89 ++++++----
 src/client/views/pdf/PDFViewer.tsx                 |  54 +++---
 src/client/views/search/SearchBox.tsx              | 189 +++++++++++++--------
 src/client/views/selectedDoc/SelectedDocView.tsx   |   2 +-
 src/client/views/selectedDoc/index.ts              |   2 +-
 src/fields/Doc.ts                                  |   3 +
 src/fields/List.ts                                 |   2 +-
 20 files changed, 253 insertions(+), 164 deletions(-)

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

diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index 57e0373c4..402f6613a 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -3,7 +3,7 @@ import * as React from 'react';
 import { ColorResult } from 'react-color';
 import * as rp from 'request-promise';
 import { numberRange, decimalToHexString } from './Utils';
-import { DocumentType } from './client/documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from './client/documents/DocumentTypes';
 import { Colors } from './client/views/global/globalEnums';
 
 export function DashColor(color: string) {
@@ -100,11 +100,22 @@ export namespace ClientUtils {
         return Date.now() - downTime < ClientUtils.CLICK_TIME && Math.abs(x - downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(y - downY) < ClientUtils.DRAG_THRESHOLD;
     }
 
-    export function cleanDocumentType(type: DocumentType) {
+    export function cleanDocumentTypeExt(type: DocumentType) {
         switch (type) {
+            case DocumentType.PDF:   return 'PDF';
+            case DocumentType.IMG:   return 'Img';
+            case DocumentType.AUDIO: return 'Aud';
+            case DocumentType.COL:   return 'Col';
+            case DocumentType.RTF:   return 'Rtf';
+            default:                 return type.charAt(0).toUpperCase() + type.substring(1,3);
+        } // prettier-ignore
+    }
+    export function cleanDocumentType(type: DocumentType, colType: CollectionViewType) {
+        switch (type) {
+            case DocumentType.PDF:   return 'PDF';
             case DocumentType.IMG:   return 'Image';
             case DocumentType.AUDIO: return 'Audio';
-            case DocumentType.COL:   return 'Collection';
+            case DocumentType.COL:   return 'Collection:'+colType;
             case DocumentType.RTF:   return 'Text';
             default:                 return type.charAt(0).toUpperCase() + type.slice(1);
         } // prettier-ignore
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index cca92816f..ac3721436 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -310,8 +310,8 @@ export class DocumentManager {
             focused = focused || nextFocus !== undefined; // keep track of whether focusing on a view needed to actually change anything
             // eslint-disable-next-line no-await-in-loop
             const { childDocView, viewSpec } = await iterator(docView);
-            if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.Document, docView, contextView, focused };
-            contextView = options.anchorDoc?.layout_unrendered && !childDocView.Document.layout_unrendered ? childDocView : docView;
+            if (!childDocView) return { viewSpec: viewSpec ?? docView.Document, docView, contextView, focused };
+            contextView = !childDocView.Document.layout_unrendered ? childDocView : docView;
             docView = childDocView;
         }
         return undefined;
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index fa1a911f7..d9d22437c 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -65,7 +65,7 @@ export namespace SerializationHelper {
     }
 }
 
-export function Deserializable(className: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void {
+export function Deserializable(classNameForSerializer: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void {
     function addToMap(className: string, Ctor: { new (...args: any[]): any }) {
         const schema = getDefaultModelSchema(Ctor) as any;
         if (schema.targetClass !== Ctor || constructorArgs) {
@@ -78,7 +78,7 @@ export function Deserializable(className: string, afterDeserialize?: (obj: any)
             throw new Error(`Name ${className} has already been registered as deserializable`);
         }
     }
-    return (ctor: { new (...args: any[]): any }) => addToMap(className, ctor);
+    return (ctor: { new (...args: any[]): any }) => addToMap(classNameForSerializer, ctor);
 }
 
 export function autoObject(): PropSchema {
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 0301632ec..c4c00e0c3 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -95,6 +95,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             presentation_zoomText: true,
             title: '>' + this.props.Document.title,
         });
+        const textRegionAnnoProto = textRegionAnno[DocData];
         let minX = Number.MAX_VALUE;
         let maxX = -Number.MAX_VALUE;
         let minY = Number.MAX_VALUE;
@@ -107,7 +108,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
                 textRegion.y = parseInt(anno.style.top ?? '0');
                 textRegion._height = parseInt(anno.style.height ?? '0');
                 textRegion._width = parseInt(anno.style.width ?? '0');
-                textRegion.annoTextRegion = textRegionAnno;
+                textRegion.embedContainer = textRegionAnnoProto;
                 textRegion.backgroundColor = color;
                 annoDocs.push(textRegion);
                 anno.remove();
@@ -118,7 +119,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             })
         );
 
-        const textRegionAnnoProto = textRegionAnno[DocData];
         textRegionAnnoProto.y = Math.max(minY, 0);
         textRegionAnnoProto.x = Math.max(minX, 0);
         textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
@@ -202,10 +202,10 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
                 return target;
             };
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.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.props.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.props.Document;
+                        dragEv.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
             });
diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx
index f083f03b9..b9a587719 100644
--- a/src/client/views/PropertiesSection.tsx
+++ b/src/client/views/PropertiesSection.tsx
@@ -13,7 +13,6 @@ export interface PropertiesSectionProps {
     children?: JSX.Element | string | null;
     isOpen: boolean;
     setIsOpen: (bool: boolean) => any;
-    setInSection?: (bool: boolean) => any;
     onDoubleClick?: () => void;
 }
 
@@ -30,7 +29,7 @@ export class PropertiesSection extends React.Component<PropertiesSectionProps> {
     render() {
         if (this.props.children === undefined || this.props.children === null) return null;
         return (
-            <div className="propertiesView-section" onPointerEnter={action(() => this.props.setInSection && this.props.setInSection(true))} onPointerLeave={action(() => this.props.setInSection && this.props.setInSection(false))}>
+            <div className="propertiesView-section">
                 <div
                     className="propertiesView-sectionTitle"
                     onDoubleClick={action(() => {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 8ac0f7d7b..5e1c80f0c 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -114,7 +114,6 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @observable openAddSlide: boolean = false;
     @observable openSlideOptions: boolean = false;
 
-    @observable inOptions: boolean = false;
     @observable _controlButton: boolean = false;
     private _disposers: { [name: string]: IReactionDisposer } = {};
 
@@ -602,9 +601,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
 
     @computed get currentType() {
-        const documentType = StrCast(this.selectedDoc?.type);
-        const currentType: string = documentType;
-        const capitalizedDocType = ClientUtils.cleanDocumentType(currentType as DocumentType);
+        const docType = StrCast(this.selectedDoc?.type) as DocumentType;
+        const colType = StrCast(this.selectedDoc?.type_collection) as CollectionViewType;
+        const capitalizedDocType = ClientUtils.cleanDocumentType(docType, colType);
 
         return (
             <div>
@@ -1080,7 +1079,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @computed get optionsSubMenu() {
         return (
             // prettier-ignore
-            <PropertiesSection title="Options" inSection={this.inOptions} isOpen={this.openOptions} setInSection={bool => { this.inOptions = bool; }} setIsOpen={bool => { this.openOptions = bool; }} onDoubleClick={this.CloseAll}>
+            <PropertiesSection title="Options" isOpen={this.openOptions} setIsOpen={bool => { this.openOptions = bool; }} onDoubleClick={this.CloseAll}>
                 <PropertiesButtons />
             </PropertiesSection>
         );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index 707f6c198..90977d955 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -11,7 +11,7 @@ export interface CollectionFreeFormPannableContentsProps {
     Document: Doc;
     viewDefDivClick?: ScriptField;
     children?: React.ReactNode | undefined;
-    transition?: string;
+    transition: () => string;
     isAnnotationOverlay: boolean | undefined;
     showPresPaths: () => boolean;
     transform: () => string;
@@ -53,7 +53,7 @@ export class CollectionFreeFormPannableContents extends ObservableReactComponent
                 }}
                 style={{
                     transform: this._props.transform(),
-                    transition: this._props.transition,
+                    transition: this._props.transition(),
                     width: this._props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable.  This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
                 }}>
                 {this.props.children}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d9c988d54..12a299ce6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -246,6 +246,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     elementFunc = () => this._layoutElements;
     viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined);
+    panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null)));
     fitContentOnce = () => {
         const vals = this.fitToContentVals;
         this.layoutDoc._freeform_panX = vals.bounds.cx;
@@ -1854,7 +1855,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 isAnnotationOverlay={this.isAnnotationOverlay}
                 transform={this.PanZoomCenterXf}
                 showPresPaths={this.showPresPaths}
-                transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))}
+                transition={this.panZoomTransition}
                 viewDefDivClick={this._props.viewDefDivClick}>
                 {this.props.children ?? null} {/* most likely case of children is document content that's being annoated: eg., an image */}
                 {this.contentViews}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 5c91d5aca..3f453eb93 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -29,7 +29,6 @@ export interface FocusViewOptions {
     openLocation?: OpenWhere; // where to open a missing document
     zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
     toggleTarget?: boolean; // whether to toggle target on and off
-    anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc
     easeFunc?: 'linear' | 'ease'; // transition method for scrolling
 }
 export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 79738c452..1b2aefbe2 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -178,7 +178,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                     if (selected.length > 1) {
                         text = selected.length + ' selected';
                     } else {
-                        text = ClientUtils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType);
+                        text = ClientUtils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType, '' as CollectionViewType);
                         icon = Doc.toIcon(selected.lastElement());
                     }
                     return (
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 0f5e25a0c..7bca1230f 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -236,7 +236,7 @@
     //pointer-events: none;
     .pdfViewerDash-text {
         .textLayer {
-            display: none;
+            // display: none;  // this makes search highlights not show up
             span {
                 user-select: none;
             }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index cc897aaef..b03b90418 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -28,6 +28,7 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { Colors } from '../global/globalEnums';
+// eslint-disable-next-line import/extensions
 import { CreateImage } from './WebBoxRenderer';
 import { PDFViewer } from '../pdf/PDFViewer';
 import { SidebarAnnos } from '../SidebarAnnos';
@@ -225,12 +226,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         );
     }
 
-    sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
-        if (DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
+    sidebarAddDocTab = (docIn: Doc | Doc[], where: OpenWhere) => {
+        const docs = docIn instanceof Doc ? [docIn] : docIn;
+        if (docs.some(doc => DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc)) && !this.SidebarShown) {
             this.toggleSidebar(false);
             return true;
         }
-        return this._props.addDocTab(doc, where);
+        return this._props.addDocTab(docs, where);
     };
     focus = (anchor: Doc, options: FocusViewOptions) => {
         this._initialScrollTarget = anchor;
@@ -344,7 +346,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         setupMoveUpEvents(
             this,
             e,
-            (e, down, delta) => {
+            (moveEv, down, delta) => {
                 const localDelta = this._props
                     .ScreenToLocalTransform()
                     .scale(this._props.NativeDimScaling?.() || 1)
@@ -359,7 +361,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 }
                 return false;
             },
-            (e, movement, isClick) => !isClick && batch.end(),
+            (clickEv, movement, isClick) => !isClick && batch.end(),
             () => {
                 onButton && this.toggleSidebar();
                 batch.end();
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index a3b1a419b..0a4efbfe4 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -104,7 +104,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return DocCast(this.childDocs[NumCast(this.Document._itemIndex)]);
     }
     @computed get targetDoc() {
-        return Cast(this.activeItem?.presentation_targetDoc, Doc, null);
+        return DocCast(this.activeItem?.presentation_targetDoc);
     }
     public static targetRenderedDoc = (doc: Doc) => {
         const targetDoc = Cast(doc?.presentation_targetDoc, Doc, null);
@@ -774,7 +774,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             effect: activeItem,
             noSelect: true,
             openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft,
-            anchorDoc: activeItem,
             easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any,
             zoomTextSelections: BoolCast(activeItem.presentation_zoomText),
             playAudio: BoolCast(activeItem.presPlayAudio),
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 053c88e17..38578837a 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,18 +1,20 @@
+/* eslint-disable react/jsx-props-no-spreading */
 import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
-import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
-import { undoBatch } from '../../util/UndoManager';
+import { undoable } from '../../util/UndoManager';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { OpenWhere } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { AnchorMenu } from './AnchorMenu';
 import './Annotation.scss';
-import { ObservableReactComponent } from '../ObservableReactComponent';
+import { Highlight } from '../../../fields/DocSymbols';
 
 interface IAnnotationProps extends FieldViewProps {
     anno: Doc;
@@ -27,11 +29,41 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
         super(props);
         makeObservable(this);
     }
+
+    @computed get linkHighlighted() {
+        const found = LinkManager.Instance.getAllDirectLinks(this._props.anno).find(link => {
+            const a1 = LinkManager.getOppositeAnchor(link, this._props.anno);
+            return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1));
+        });
+        return found;
+    }
+    linkHighlightedFunc = () => this.linkHighlighted;
+    highlightedFunc = () => this._props.anno[Highlight];
+
+    deleteAnnotation = undoable(() => {
+        const docAnnotations = DocListCast(this._props.dataDoc[this._props.fieldKey]);
+        this._props.dataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this._props.anno));
+        AnchorMenu.Instance.fadeOut(true);
+        this._props.select(false);
+    }, 'delete annotation');
+
+    pinToPres = undoable(() => this._props.pinToPres(this._props.anno, {}), 'pin to pres');
+
     render() {
         return (
             <div style={{ display: this._props.anno.textCopied && !Doc.GetBrushHighlightStatus(this._props.anno) ? 'none' : undefined }}>
                 {DocListCast(this._props.anno.text_inlineAnnotations).map(a => (
-                    <RegionAnnotation pointerEvents={this._props.pointerEvents} {...this._props} document={a} key={a[Id]} />
+                    // eslint-disable-next-line no-use-before-define
+                    <RegionAnnotation
+                        pointerEvents={this._props.pointerEvents}
+                        {...this._props}
+                        highlighted={this.highlightedFunc}
+                        linkHighlighted={this.linkHighlightedFunc}
+                        pinToPres={this.pinToPres}
+                        deleteAnnotation={this.deleteAnnotation}
+                        document={a}
+                        key={a[Id]}
+                    />
                 ))}
             </div>
         );
@@ -40,49 +72,41 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
 
 interface IRegionAnnotationProps extends IAnnotationProps {
     document: Doc;
+    linkHighlighted: () => Doc | undefined;
+    highlighted: () => any;
+    deleteAnnotation: () => void;
+    pinToPres: (...args: any[]) => void;
     pointerEvents?: () => Opt<string>;
 }
 @observer
 class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps> {
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
 
-    @computed get annoTextRegion() {
-        return Cast(this._props.document.annoTextRegion, Doc, null) || this._props.document;
+    @computed get regionDoc() {
+        return DocCast(this._props.document.embedContainer, this._props.document);
     }
 
-    @undoBatch
-    deleteAnnotation = () => {
-        const docAnnotations = DocListCast(this._props.dataDoc[this._props.fieldKey]);
-        this._props.dataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
-        AnchorMenu.Instance.fadeOut(true);
-        this._props.select(false);
-    };
-
-    @undoBatch
-    pinToPres = () => this._props.pinToPres(this.annoTextRegion, {});
+    makeTargetToggle = undoable(() => { this.regionDoc.followLinkToggle = !this.regionDoc.followLinkToggle }, "set link toggle"); // prettier-ignore
 
-    @undoBatch
-    makeTargetToggle = () => { this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle }; // prettier-ignore
+    isTargetToggler = () => BoolCast(this.regionDoc.followLinkToggle);
 
-    isTargetToggler = () => BoolCast(this.annoTextRegion.followLinkToggle);
-    @undoBatch
-    showTargetTrail = (anchor: Doc) => {
+    showTargetTrail = undoable((anchor: Doc) => {
         const trail = DocCast(anchor.presentationTrail);
         if (trail) {
             Doc.ActivePresentation = trail;
             this._props.addDocTab(trail, OpenWhere.replaceRight);
         }
-    };
+    }, 'show target trail');
 
     @action
     onContextMenu = (e: React.MouseEvent) => {
         AnchorMenu.Instance.Status = 'annotation';
-        AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this);
+        AnchorMenu.Instance.Delete = this._props.deleteAnnotation;
         AnchorMenu.Instance.Pinned = false;
-        AnchorMenu.Instance.PinToPres = this.pinToPres;
+        AnchorMenu.Instance.PinToPres = this._props.pinToPres;
         AnchorMenu.Instance.MakeTargetToggle = this.makeTargetToggle;
         AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler;
-        AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.annoTextRegion);
+        AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.regionDoc);
         AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
         e.stopPropagation();
         e.preventDefault();
@@ -94,19 +118,12 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps>
             e.preventDefault();
         } else if (e.button === 0) {
             e.stopPropagation();
-            LinkFollower.FollowLink(undefined, this.annoTextRegion, false);
+            LinkFollower.FollowLink(undefined, this.regionDoc, false);
         }
     };
 
-    @computed get linkHighlighted() {
-        for (const link of LinkManager.Instance.getAllDirectLinks(this._props.document)) {
-            const a1 = LinkManager.getOppositeAnchor(link, this._props.document);
-            if (a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, this._props.document))) return true;
-        }
-    }
-
     render() {
-        const brushed = this.annoTextRegion && Doc.GetBrushHighlightStatus(this.annoTextRegion);
+        const brushed = this.regionDoc && Doc.GetBrushHighlightStatus(this.regionDoc);
         return (
             <div
                 className="htmlAnnotation"
@@ -128,8 +145,8 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps>
                     height: NumCast(this._props.document._height),
                     opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined,
                     pointerEvents: this._props.pointerEvents?.() as any,
-                    outline: brushed === Doc.DocBrushStatus.unbrushed && this.linkHighlighted ? 'solid 1px lightBlue' : undefined,
-                    backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? 'orange' : StrCast(this._props.document.backgroundColor),
+                    outline: this._props.linkHighlighted() ? 'solid 1px lightBlue' : undefined,
+                    backgroundColor: this._props.highlighted() ? 'orange' : StrCast(this._props.document.backgroundColor),
                 }}
             />
         );
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 0ab952e84..a3fd192f7 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
@@ -10,7 +12,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, Utils } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils';
 import { DocUtils } from '../../documents/Documents';
 import { SelectionManager } from '../../util/SelectionManager';
@@ -27,9 +29,8 @@ import { AnchorMenu } from './AnchorMenu';
 import { Annotation } from './Annotation';
 import { GPTPopup } from './GPTPopup/GPTPopup';
 import './PDFViewer.scss';
-const _global = (window /* browser */ || global) /* node */ as any;
 
-//pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
+// pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
 // The workerSrc property shall be specified.
 Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.1.392/build/pdf.worker.mjs';
 
@@ -43,6 +44,7 @@ interface IViewerProps extends FieldViewProps {
     url: string;
     sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean;
     loaded?: (nw: number, nh: number, np: number) => void;
+    // eslint-disable-next-line no-use-before-define
     setPdfViewer: (view: PDFViewer) => void;
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
     crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined;
@@ -109,8 +111,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
         this._disposers.layout_autoHeight = reaction(
             () => this._props.layoutDoc._layout_autoHeight,
-            layout_autoHeight => {
-                if (layout_autoHeight) {
+            layoutAutoHeight => {
+                if (layoutAutoHeight) {
                     this._props.layoutDoc._nativeHeight = NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']);
                     this._props.setHeight?.(NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
                 }
@@ -119,7 +121,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
         this._disposers.selected = reaction(
             () => this._props.isSelected(),
-            selected => SelectionManager.Views.length === 1 && this.setupPdfJsViewer(),
+            () => SelectionManager.Views.length === 1 && this.setupPdfJsViewer(),
             { fireImmediately: true }
         );
         this._disposers.curPage = reaction(
@@ -169,7 +171,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 )
             );
         }
-        runInAction(() => (this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72));
+        runInAction(() => {
+            this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72;
+        });
     };
 
     _scrollStopper: undefined | (() => void);
@@ -207,14 +211,18 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
     pagesinit = () => {
         if (this._pdfViewer._setDocumentViewerElement?.offsetParent) {
-            runInAction(() => (this._pdfViewer.currentScaleValue = this._props.layoutDoc._freeform_scale = 1));
+            runInAction(() => {
+                this._pdfViewer.currentScaleValue = this._props.layoutDoc._freeform_scale = 1;
+            });
             this.gotoPage(NumCast(this._props.Document._layout_curPage, 1));
         }
         document.removeEventListener('pagesinit', this.pagesinit);
-        var quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined };
+        let quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined };
         this._disposers.scale = reaction(
             () => NumCast(this._props.layoutDoc._freeform_scale, 1),
-            scale => (this._pdfViewer.currentScaleValue = scale),
+            scale => {
+                this._pdfViewer.currentScaleValue = scale;
+            },
             { fireImmediately: true }
         );
         this._disposers.scroll = reaction(
@@ -231,7 +239,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                         setTimeout(
                             () => {
                                 this._mainCont.current && (this._scrollStopper = smoothScroll(duration, this._mainCont.current, pos, this._initialScroll?.easeFunc ?? 'ease', this._scrollStopper));
-                                setTimeout(() => (this._forcedScroll = false), duration);
+                                setTimeout(() => {
+                                    this._forcedScroll = false;
+                                }, duration);
                             },
                             this._mainCont.current ? 0 : 250
                         ); // wait for mainCont and try again to scroll
@@ -267,7 +277,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         eventBus._on('pagesinit', this.pagesinit);
         eventBus._on(
             'pagerendered',
-            action(() => (this._showWaiting = false))
+            action(() => {
+                this._showWaiting = false;
+            })
         );
         const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus });
         const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
@@ -310,7 +322,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
     @observable private _scrollTimer: any = undefined;
 
-    onScroll = (e: React.UIEvent<HTMLElement>) => {
+    onScroll = () => {
         if (this._mainCont.current && !this._forcedScroll) {
             this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation)
             if (!LinkInfo.Instance?.LinkInfo) {
@@ -345,7 +357,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
             query: searchString,
         };
         if (clear) {
-            this._pdfViewer?.eventBus.dispatch('reset', {});
+            this._pdfViewer?.eventBus.dispatch('findbarclose', {});
         } else if (!searchString) {
             bwd ? this.prevAnnotation() : this.nextAnnotation();
         } else if (this._pdfViewer?.pageViewsReady) {
@@ -392,7 +404,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     };
 
     @action
-    finishMarquee = (x?: number, y?: number) => {
+    finishMarquee = (/* x?: number, y?: number */) => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this.isAnnotating = false;
         this._marqueeref.current?.onTerminateSelection();
@@ -467,7 +479,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         // e.stopPropagation();  // bcz: not sure why this was here.  We need to allow the DocumentView to get clicks to process doubleClicks
     };
 
-    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
+    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => {
+        this._setPreviewCursor = func;
+    };
 
     @action
     onZoomWheel = (e: React.WheelEvent) => {
@@ -490,6 +504,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         return (
             <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
                 {inlineAnnos.map(anno => (
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this._props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
                 ))}
             </div>
@@ -522,6 +537,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined,
             }}>
             <CollectionFreeFormView
+                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
@@ -529,7 +545,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 pointerEvents={this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
                 childPointerEvents={this.childPointerEvents} // but freeform children need to get events to allow text editing, etc
                 renderDepth={this._props.renderDepth + 1}
-                isAnnotationOverlay={true}
+                isAnnotationOverlay
                 fieldKey={this._props.fieldKey + '_annotations'}
                 getScrollHeight={this.getScrollHeight}
                 setPreviewCursor={this.setPreviewCursor}
@@ -582,7 +598,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                     {this.pdfViewerDiv}
                     {this.annotationLayer}
                     {this.overlayLayer}
-                    {this._showWaiting ? <img className="pdfViewerDash-waiting" src={'/assets/loading.gif'} /> : null}
+                    {this._showWaiting ? <img alt="" className="pdfViewerDash-waiting" src="/assets/loading.gif" /> : null}
                     {!this._mainCont.current || !this._annotationLayer.current ? null : (
                         <MarqueeAnnotator
                             ref={this._marqueeref}
@@ -590,7 +606,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                             getPageFromScroll={this.getPageFromScroll}
                             anchorMenuClick={this._props.anchorMenuClick}
                             scrollTop={0}
-                            isNativeScaled={true}
+                            isNativeScaled
                             annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)}
                             addDocument={this.addDocumentWrapper}
                             docView={this._props.pdfBox.DocumentView!}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 0c1a419aa..384e6d654 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -6,7 +6,6 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCastAsync, Field, FieldType } from '../../../fields/Doc';
 import { DirectLinks, DocData } from '../../../fields/DocSymbols';
-import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocUtils } from '../../documents/Documents';
@@ -16,16 +15,111 @@ import { SearchUtil } from '../../util/SearchUtil';
 import { SettingsManager } from '../../util/SettingsManager';
 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 { FieldView, FieldViewProps } from '../nodes/FieldView';
 import './SearchBox.scss';
+import { Id } from '../../../fields/FieldSymbols';
+import { ClientUtils } from '../../../ClientUtils';
 
 const DAMPENING_FACTOR = 0.9;
 const MAX_ITERATIONS = 25;
 const ERROR = 0.03;
 
+export interface SearchBoxItemProps {
+    Document: Doc;
+    searchString: string;
+    isLinkSearch: boolean;
+    matchedKeys: string[];
+    className: string;
+    linkFrom: Doc | undefined;
+    selectItem: (doc: Doc) => void;
+    linkCreateAnchor?: () => Doc | undefined;
+    linkCreated?: (link: Doc) => void;
+}
+@observer
+export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps> {
+    constructor(props: SearchBoxItemProps) {
+        super(props);
+        makeObservable(this);
+    }
+
+    /**
+     * @param {Doc} doc - doc to be selected
+     *
+     * This method selects a doc by either jumping to it (centering/zooming in on it)
+     * or opening it in a new tab.
+     */
+    selectElement = async (doc: Doc, finishFunc: () => void) => {
+        await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc);
+    };
+
+    /**
+     * @param {Doc} doc - doc of the search result that has been clicked on
+     *
+     * This method is called when the user clicks on a search result. The _selectedResult is
+     * updated accordingly and the doc is highlighted with the selectElement method.
+     */
+    onResultClick = action(async (doc: Doc) => {
+        this._props.selectItem(doc);
+        this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._props.searchString, undefined, false));
+    });
+
+    componentWillUnmount(): void {
+        const doc = this._props.Document;
+        DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
+    }
+
+    @undoBatch
+    makeLink = action((linkTo: Doc) => {
+        const linkFrom = this._props.linkCreateAnchor?.();
+        if (linkFrom) {
+            const link = DocUtils.MakeLink(linkFrom, linkTo, {});
+            link && this._props.linkCreated?.(link);
+        }
+    });
+
+    render() {
+        // eslint-disable-next-line no-use-before-define
+        const formattedType = SearchBox.formatType(StrCast(this._props.Document.type), StrCast(this._props.Document.type_collection));
+        const { title } = this._props.Document;
+
+        return (
+            <Tooltip placement="right" title={<div className="dash-tooltip">{title as string}</div>}>
+                <div
+                    onClick={
+                        this._props.isLinkSearch
+                            ? () => this.makeLink(this._props.Document)
+                            : e => {
+                                  this.onResultClick(this._props.Document);
+                                  e.stopPropagation();
+                              }
+                    }
+                    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)
+                        )
+                            ? 'bold'
+                            : '',
+                    }}
+                    className={this._props.className}>
+                    <div className="searchBox-result-title">{title as string}</div>
+                    <div className="searchBox-result-type" style={{ color: SettingsManager.userVariantColor }}>
+                        {formattedType}
+                    </div>
+                    <div className="searchBox-result-keys" style={{ color: SettingsManager.userVariantColor }}>
+                        {this._props.matchedKeys.join(', ')}
+                    </div>
+                </div>
+            </Tooltip>
+        );
+    }
+}
+
 export interface SearchBoxProps extends FieldViewProps {
     linkSearch: boolean;
     linkFrom?: (() => Doc | undefined) | undefined;
@@ -111,26 +205,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
         this._docTypeString = e.target.value;
     });
 
-    /**
-     * @param {Doc} doc - doc of the search result that has been clicked on
-     *
-     * This method is called when the user clicks on a search result. The _selectedResult is
-     * updated accordingly and the doc is highlighted with the selectElement method.
-     */
-    onResultClick = action(async (doc: Doc) => {
-        this._selectedResult = doc;
-        this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, false));
-    });
-
-    @undoBatch
-    makeLink = action((linkTo: Doc) => {
-        const linkFrom = this._props.linkCreateAnchor?.();
-        if (linkFrom) {
-            const link = DocUtils.MakeLink(linkFrom, linkTo, {});
-            link && this._props.linkCreated?.(link);
-        }
-    });
-
     /**
      * @param {Doc[]} docs - docs to be searched through recursively
      * @param {number, Doc => void} func - function to be called on each doc
@@ -217,7 +291,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
             if (doc[DocData][DirectLinks].size === 0) {
                 this._linkedDocsOut.set(doc, new Set(this._results.keys()));
 
-                this._results.forEach((_, linkedDoc) => {
+                this._results.forEach((__, linkedDoc) => {
                     this._linkedDocsIn.get(linkedDoc)?.add(doc);
                 });
             } else {
@@ -251,7 +325,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     pageRankIteration(): boolean {
         let converged = true;
         const pageRankFromAll = (1 - DAMPENING_FACTOR) / this._results.size;
-
         const nextPageRanks = new Map<Doc, number>();
 
         this._results.forEach((_, doc) => {
@@ -345,16 +418,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
         });
     });
 
-    /**
-     * @param {Doc} doc - doc to be selected
-     *
-     * This method selects a doc by either jumping to it (centering/zooming in on it)
-     * or opening it in a new tab.
-     */
-    selectElement = async (doc: Doc, finishFunc: () => void) => {
-        await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc);
-    };
-
     /**
      * This method returns a JSX list of the options in the select drop-down menu, which
      * is used to filter the types of documents that appear in the search results.
@@ -365,7 +428,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
 
         return selectValues.map(value => (
             <option key={value} value={value}>
-                {SearchBox.formatType(value, '')}
+                {ClientUtils.cleanDocumentTypeExt(value as DocumentType)}
             </option>
         ));
     }
@@ -374,56 +437,36 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
      * This method renders the search input box, select drop-down menu, and search results.
      */
     render() {
-        let validResults = 0;
-
         const isLinkSearch: boolean = this._props.linkSearch;
-
         const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank
-
         const resultsJSX = [] as any[];
+        const linkFrom = this._props.linkFrom?.();
 
-        const fromDoc = this._props.linkFrom?.();
-
-        sortedResults.forEach(result => {
+        let validResults = 0;
+        sortedResults.forEach(([Document, matchedKeys]) => {
             let className = 'searchBox-results-scroll-view-result';
 
-            if (this._selectedResult === result[0]) {
+            if (this._selectedResult === Document) {
                 className += ' searchBox-results-scroll-view-result-selected';
             }
 
-            const formattedType = SearchBox.formatType(StrCast(result[0].type), StrCast(result[0].type_collection));
-            const { title } = result[0];
-
-            if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === result[0].type) {
+            if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === Document.type) {
                 validResults++;
                 resultsJSX.push(
-                    <Tooltip key={result[0][Id]} placement="right" title={<div className="dash-tooltip">{title as string}</div>}>
-                        <div
-                            onClick={
-                                isLinkSearch
-                                    ? () => this.makeLink(result[0])
-                                    : e => {
-                                          this.onResultClick(result[0]);
-                                          e.stopPropagation();
-                                      }
-                            }
-                            style={{
-                                fontWeight: LinkManager.Links(fromDoc).find(
-                                    link => Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, fromDoc!), result[0] as Doc) || Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, fromDoc!)?.annotationOn), result[0] as Doc)
-                                )
-                                    ? 'bold'
-                                    : '',
-                            }}
-                            className={className}>
-                            <div className="searchBox-result-title">{title as string}</div>
-                            <div className="searchBox-result-type" style={{ color: SettingsManager.userVariantColor }}>
-                                {formattedType}
-                            </div>
-                            <div className="searchBox-result-keys" style={{ color: SettingsManager.userVariantColor }}>
-                                {result[1].join(', ')}
-                            </div>
-                        </div>
-                    </Tooltip>
+                    <SearchBoxItem
+                        key={Document[Id]}
+                        Document={Document}
+                        selectItem={action((doc: Doc) => {
+                            this._selectedResult = doc;
+                        })}
+                        isLinkSearch={isLinkSearch}
+                        searchString={this._searchString}
+                        matchedKeys={matchedKeys}
+                        linkFrom={linkFrom}
+                        className={className}
+                        linkCreateAnchor={this._props.linkCreateAnchor}
+                        linkCreated={this._props.linkCreated}
+                    />
                 );
             }
         });
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index c9c01189e..7ad7b2927 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -35,7 +35,7 @@ export class SelectedDocView extends React.Component<SelectedDocViewProps> {
                             val: StrCast(doc._id),
                             color: SettingsManager.userColor,
                             background: SettingsManager.userBackgroundColor,
-                            icon: <FontAwesomeIcon size={'1x'} icon={Doc.toIcon(doc)} />,
+                            icon: <FontAwesomeIcon size="1x" icon={Doc.toIcon(doc)} />,
                             onClick: () => DocumentManager.Instance.showDocument(doc, options, emptyFunction),
                         };
                     })}
diff --git a/src/client/views/selectedDoc/index.ts b/src/client/views/selectedDoc/index.ts
index 1f1db91f6..968e9c2d4 100644
--- a/src/client/views/selectedDoc/index.ts
+++ b/src/client/views/selectedDoc/index.ts
@@ -1 +1 @@
-export * from './SelectedDocView'
\ No newline at end of file
+export * from './SelectedDocView';
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 5028e1f8f..7b5e21ab7 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1235,6 +1235,8 @@ export namespace Doc {
                 // 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]);
                 doc[DocData][Highlight] = true;
+                // want to highlight the targets of presentation docs explicitly since following a pres target does not highlight PDf <Annotations> which are not DocumentViews
+                if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = true;
             }
         });
     }
@@ -1246,6 +1248,7 @@ export namespace Doc {
                 highlightedDocs.delete(doc[DocData]);
                 doc[Highlight] = doc[DocData][Highlight] = false;
                 doc[Animation] = undefined;
+                if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = false;
             });
         });
     }
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 852b05bc6..38c47d546 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -308,7 +308,7 @@ class ListImpl<T extends FieldType> extends ObjectField {
     // eslint-disable-next-line no-use-before-define
     private [SelfProxy]: List<FieldType>; // also used in utils.ts even though it won't be found using find all references
 
-    [ToScriptString]()     { return `new List(${this[ToJavascriptString]})`; } // prettier-ignore
+    [ToScriptString]()     { return `new List(${this[ToJavascriptString]()})`; } // prettier-ignore
     [ToJavascriptString]() { return `[${(this as any).map((field: any) => Field.toScriptString(field))}]`; } // prettier-ignore
     [ToString]()           { return `[${(this as any).map((field: any) => Field.toString(field))}]`; } // prettier-ignore
 }
-- 
cgit v1.2.3-70-g09d2


From d6720fa48d78cc313d6418acd8cbdaeda965285c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 25 Apr 2024 17:15:20 -0400
Subject: changed marqueeAnnotator to save inline annotations as text strings
 instead of Docs. enabled making image crops of text selections on PDFs. 
 cleaned up webboxrendered lint promses, and Annotation render

---
 src/client/views/MarqueeAnnotator.tsx    |  27 +++---
 src/client/views/StyleProvider.tsx       |   2 +-
 src/client/views/nodes/PDFBox.tsx        |   4 +
 src/client/views/nodes/WebBox.tsx        |   2 +-
 src/client/views/nodes/WebBoxRenderer.js | 133 ++++++++++++--------------
 src/client/views/pdf/Annotation.tsx      | 157 +++++++++++++++----------------
 src/client/views/pdf/PDFViewer.tsx       |   6 +-
 7 files changed, 157 insertions(+), 174 deletions(-)

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

diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index c4c00e0c3..ea73a53a9 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -100,22 +100,19 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         let maxX = -Number.MAX_VALUE;
         let minY = Number.MAX_VALUE;
         let maxY = -Number.MIN_VALUE;
-        const annoDocs: Doc[] = [];
+        const annoRects: string[] = [];
         savedAnnoMap.forEach((value: HTMLDivElement[]) =>
             value.forEach(anno => {
-                const textRegion = new Doc();
-                textRegion.x = parseInt(anno.style.left ?? '0');
-                textRegion.y = parseInt(anno.style.top ?? '0');
-                textRegion._height = parseInt(anno.style.height ?? '0');
-                textRegion._width = parseInt(anno.style.width ?? '0');
-                textRegion.embedContainer = textRegionAnnoProto;
-                textRegion.backgroundColor = color;
-                annoDocs.push(textRegion);
+                const x = parseInt(anno.style.left ?? '0');
+                const y = parseInt(anno.style.top ?? '0');
+                const height = parseInt(anno.style.height ?? '0');
+                const width = parseInt(anno.style.width ?? '0');
+                annoRects.push(`${x}:${y}:${width}:${height}`);
                 anno.remove();
-                minY = Math.min(NumCast(textRegion.y), minY);
-                minX = Math.min(NumCast(textRegion.x), minX);
-                maxY = Math.max(NumCast(textRegion.y) + NumCast(textRegion._height), maxY);
-                maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX);
+                minY = Math.min(NumCast(y), minY);
+                minX = Math.min(NumCast(x), minX);
+                maxY = Math.max(NumCast(y) + NumCast(height), maxY);
+                maxX = Math.max(NumCast(x) + NumCast(width), maxX);
             })
         );
 
@@ -123,8 +120,10 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         textRegionAnnoProto.x = Math.max(minX, 0);
         textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
         textRegionAnnoProto.width = Math.max(maxX, 0) - Math.max(minX, 0);
+        textRegionAnnoProto.backgroundColor = color;
         // mainAnnoDocProto.text = this._selectionText;
-        textRegionAnnoProto.text_inlineAnnotations = new List<Doc>(annoDocs);
+        textRegionAnnoProto.text_inlineAnnotations = new List<string>(annoRects);
+        textRegionAnnoProto.opacity = 0;
         textRegionAnnoProto.layout_unrendered = true;
         savedAnnoMap.clear();
         return textRegionAnno;
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3697aa010..6d4f69e6e 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -177,7 +177,7 @@ 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 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
+        case StyleProp.Opacity:      return LayoutTemplateString?.includes(KeyValueBox.name) ? 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));
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index b03b90418..fdba9ff49 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -110,6 +110,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
     crop = (region: Doc | undefined, addCrop?: boolean) => {
         if (!region) return undefined;
         const cropping = Doc.MakeCopy(region, true);
+        cropping.layout_unrendered = false; // text selection have this
+        cropping.text_inlineAnnotations = undefined; // text selections have this -- it causes them not to be rendered.
+        cropping.backgroundColor = undefined; // text selections have this -- it causes images to be fully transparent
+        cropping.opacity = undefined; // text selections have this -- it causes images to be fully transparent
         const regionData = region[DocData];
         regionData.lockedPosition = true;
         regionData.title = 'region:' + this.Document.title;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index fc2e4bf61..c0fffd66c 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1030,7 +1030,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                     .sort((a, b) => NumCast(a.y) - NumCast(b.y))
                     .map(anno => (
                         // eslint-disable-next-line react/jsx-props-no-spreading
-                        <Annotation {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+                        <Annotation {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} containerDataDoc={this.dataDoc} annoDoc={anno} key={`${anno[Id]}-annotation`} />
                     ))}
             </div>
         );
diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js
index 914adb404..6fb8f4957 100644
--- a/src/client/views/nodes/WebBoxRenderer.js
+++ b/src/client/views/nodes/WebBoxRenderer.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-undef */
 /**
  *
  * @param {StyleSheetList} styleSheets
@@ -9,15 +10,14 @@ const ForeignHtmlRenderer = function (styleSheets) {
      *
      * @param {String} binStr
      */
-    const binaryStringToBase64 = function (binStr) {
-        return new Promise(resolve => {
+    const binaryStringToBase64 = binStr =>
+        new Promise(resolve => {
             const reader = new FileReader();
             reader.readAsDataURL(binStr);
             reader.onloadend = function () {
                 resolve(reader.result);
             };
         });
-    };
 
     function prepend(extension) {
         return window.location.origin + extension;
@@ -30,8 +30,8 @@ const ForeignHtmlRenderer = function (styleSheets) {
      * @param {String} url
      * @returns {Promise}
      */
-    const getResourceAsBase64 = function (webUrl, inurl) {
-        return new Promise((resolve, reject) => {
+    const getResourceAsBase64 = (webUrl, inurl) =>
+        new Promise(resolve => {
             const xhr = new XMLHttpRequest();
             // const url = inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl;
             // const url = CorsProxy(inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl);// inurl.startsWith("http") ? CorsProxy(inurl) : inurl;
@@ -67,16 +67,15 @@ const ForeignHtmlRenderer = function (styleSheets) {
 
             xhr.send(null);
         });
-    };
 
     /**
      *
      * @param {String[]} urls
      * @returns {Promise}
      */
-    const getMultipleResourcesAsBase64 = function (webUrl, urls) {
+    const getMultipleResourcesAsBase64 = (webUrl, urls) => {
         const promises = [];
-        for (let i = 0; i < urls.length; i += 1) {
+        for (let i = 0; webUrl && i < urls.length; i += 1) {
             promises.push(getResourceAsBase64(webUrl, urls[i]));
         }
         return Promise.all(promises);
@@ -130,6 +129,7 @@ const ForeignHtmlRenderer = function (styleSheets) {
         const urlsFound = [];
         let searchStartIndex = 0;
 
+        // eslint-disable-next-line no-constant-condition
         while (true) {
             const url = parseValue(cssRuleStr, searchStartIndex, selector, delimiters);
             if (url === null) {
@@ -155,9 +155,6 @@ const ForeignHtmlRenderer = function (styleSheets) {
     const getImageUrlsFromFromHtml = function (html) {
         return getUrlsFromCssString(html, 'src=', [' ', '>', '\t'], true);
     };
-    const getSourceUrlsFromFromHtml = function (html) {
-        return getUrlsFromCssString(html, 'source=', [' ', '>', '\t'], true);
-    };
 
     const escapeRegExp = function (string) {
         return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
@@ -171,46 +168,45 @@ const ForeignHtmlRenderer = function (styleSheets) {
      *
      * @returns {Promise<String>}
      */
-    const buildSvgDataUri = async function (webUrl, inputContentHtml, width, height, scroll, xoff) {
-        return new Promise(async (resolve, reject) => {
-            /* !! The problems !!
-             *  1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with)
-             *  2. Platform won't wait for external assets to load (fonts, images, etc.)
-             */
-
-            // copy styles
-            let cssStyles = '';
-            const urlsFoundInCss = [];
-
-            for (let i = 0; i < styleSheets.length; i += 1) {
-                try {
-                    const rules = styleSheets[i].cssRules;
-                    for (let j = 0; j < rules.length; j += 1) {
-                        const cssRuleStr = rules[j].cssText;
-                        urlsFoundInCss.push(...getUrlsFromCssString(cssRuleStr));
-                        cssStyles += cssRuleStr;
-                    }
-                } catch (e) {
-                    /* empty */
+    const buildSvgDataUri = (webUrl, inputContentHtml, width, height, scroll, xoff) => {
+        /* !! The problems !!
+         *  1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with)
+         *  2. Platform won't wait for external assets to load (fonts, images, etc.)
+         */
+
+        // copy styles
+        let cssStyles = '';
+        const urlsFoundInCss = [];
+
+        for (let i = 0; i < styleSheets.length; i += 1) {
+            try {
+                const rules = styleSheets[i].cssRules;
+                for (let j = 0; j < rules.length; j += 1) {
+                    const cssRuleStr = rules[j].cssText;
+                    urlsFoundInCss.push(...getUrlsFromCssString(cssRuleStr));
+                    cssStyles += cssRuleStr;
                 }
+            } catch (e) {
+                /* empty */
             }
+        }
 
-            // const fetchedResourcesFromStylesheets = await getMultipleResourcesAsBase64(webUrl, urlsFoundInCss);
-            // for (let i = 0; i < fetchedResourcesFromStylesheets.length; i++) {
-            //     const r = fetchedResourcesFromStylesheets[i];
-            //     if (r.resourceUrl) {
-            //         cssStyles = cssStyles.replace(new RegExp(escapeRegExp(r.resourceUrl), "g"), r.resourceBase64);
-            //     }
-            // }
-
-            let contentHtml = inputContentHtml
-                .replace(/<source[^>]*>/g, '') // <picture> tags have a <source> which has a srcset field of image refs.  instead of converting each, just use the default <img> of the picture
-                .replace(/noscript/g, 'div')
-                .replace(/<div class="mediaset"><\/div>/g, '') // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s.  But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag
-                .replace(/<link[^>]*>/g, '') // don't need to keep any linked style sheets because we've already processed all style sheets above
-                .replace(/srcset="([^ "]*)[^"]*"/g, 'src="$1"'); // instead of converting each item in the srcset to a data url, just convert the first one and use that
-            const urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith('data:'));
-            const fetchedResources = webUrl ? await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml) : [];
+        // const fetchedResourcesFromStylesheets = await getMultipleResourcesAsBase64(webUrl, urlsFoundInCss);
+        // for (let i = 0; i < fetchedResourcesFromStylesheets.length; i++) {
+        //     const r = fetchedResourcesFromStylesheets[i];
+        //     if (r.resourceUrl) {
+        //         cssStyles = cssStyles.replace(new RegExp(escapeRegExp(r.resourceUrl), "g"), r.resourceBase64);
+        //     }
+        // }
+
+        let contentHtml = inputContentHtml
+            .replace(/<source[^>]*>/g, '') // <picture> tags have a <source> which has a srcset field of image refs.  instead of converting each, just use the default <img> of the picture
+            .replace(/noscript/g, 'div')
+            .replace(/<div class="mediaset"><\/div>/g, '') // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s.  But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag
+            .replace(/<link[^>]*>/g, '') // don't need to keep any linked style sheets because we've already processed all style sheets above
+            .replace(/srcset="([^ "]*)[^"]*"/g, 'src="$1"'); // instead of converting each item in the srcset to a data url, just convert the first one and use that
+        const urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith('data:'));
+        return getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml).then(fetchedResources => {
             for (let i = 0; i < fetchedResources.length; i += 1) {
                 const r = fetchedResources[i];
                 if (r.resourceUrl) {
@@ -243,9 +239,7 @@ const ForeignHtmlRenderer = function (styleSheets) {
                 </svg>`;
 
             // convert SVG to data-uri
-            const dataUri = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svg)))}`;
-
-            resolve(dataUri);
+            return `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svg)))}`;
         });
     };
 
@@ -256,18 +250,19 @@ const ForeignHtmlRenderer = function (styleSheets) {
      *
      * @return {Promise<Image>}
      */
-    this.renderToImage = async function (webUrl, html, width, height, scroll, xoff) {
-        return new Promise(async (resolve, reject) => {
+    this.renderToImage = (webUrl, html, width, height, scroll, xoff) =>
+        new Promise(resolve => {
             const img = new Image();
-            console.log(`BUILDING SVG for: ${webUrl}`);
-            img.src = await buildSvgDataUri(webUrl, html, width, height, scroll, xoff);
-
             img.onload = function () {
                 console.log(`IMAGE SVG created: ${webUrl}`);
                 resolve(img);
             };
+            console.log(`BUILDING SVG for: ${webUrl}`);
+            buildSvgDataUri(webUrl, html, width, height, scroll, xoff).then(uri => {
+                img.src = uri;
+                return img;
+            });
         });
-    };
 
     /**
      * @param {String} html
@@ -276,20 +271,16 @@ const ForeignHtmlRenderer = function (styleSheets) {
      *
      * @return {Promise<Image>}
      */
-    this.renderToCanvas = async function (webUrl, html, width, height, scroll, xoff, oversample) {
-        return new Promise(async (resolve, reject) => {
-            const img = await self.renderToImage(webUrl, html, width, height, scroll, xoff);
-
+    this.renderToCanvas = (webUrl, html, width, height, scroll, xoff, oversample) =>
+        self.renderToImage(webUrl, html, width, height, scroll, xoff).then(img => {
             const canvas = document.createElement('canvas');
             canvas.width = img.width * oversample;
             canvas.height = img.height * oversample;
 
             const canvasCtx = canvas.getContext('2d');
             canvasCtx.drawImage(img, 0, 0, img.width * oversample, img.height * oversample);
-
-            resolve(canvas);
+            return canvas;
         });
-    };
 
     /**
      * @param {String} html
@@ -298,12 +289,10 @@ const ForeignHtmlRenderer = function (styleSheets) {
      *
      * @return {Promise<String>}
      */
-    this.renderToBase64Png = async function (webUrl, html, width, height, scroll, xoff, oversample) {
-        return new Promise(async (resolve, reject) => {
-            const canvas = await self.renderToCanvas(webUrl, html, width, height, scroll, xoff, oversample);
-            resolve(canvas.toDataURL('image/png'));
-        });
-    };
+    this.renderToBase64Png = (webUrl, html, width, height, scroll, xoff, oversample) =>
+        self
+            .renderToCanvas(webUrl, html, width, height, scroll, xoff, oversample) //
+            .then(canvas => canvas.toDataURL('image/png'));
 };
 
 export function CreateImage(webUrl, styleSheets, html, width, height, scroll, xoff = 0, oversample = 1) {
@@ -379,11 +368,11 @@ const ClipboardUtils = new (function () {
                         .then(result => {
                             loadFile(result, callback);
                         })
-                        .catch(error => {
+                        .catch(() => {
                             callback(null, 'Reading clipboard error.');
                         });
                 })
-                .catch(error => {
+                .catch(() => {
                     callback(null, 'Reading clipboard error.');
                 });
         } else {
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 38578837a..f1cd1a4f7 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,9 +1,8 @@
-/* eslint-disable react/jsx-props-no-spreading */
 import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { Id } from '../../../fields/FieldSymbols';
+import { 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';
@@ -14,13 +13,38 @@ import { OpenWhere } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { AnchorMenu } from './AnchorMenu';
 import './Annotation.scss';
-import { Highlight } from '../../../fields/DocSymbols';
+
+interface IRegionAnnotationProps {
+    x: number;
+    y: number;
+    width: number;
+    height: number;
+    opacity: () => number;
+    background: () => string;
+    outline: () => string | undefined;
+}
+
+const RegionAnnotation = function (props: IRegionAnnotationProps) {
+    return (
+        <div
+            className="htmlAnnotation"
+            style={{
+                left: NumCast(props.x),
+                top: NumCast(props.y),
+                width: NumCast(props.width),
+                height: NumCast(props.height),
+                opacity: props.opacity(),
+                outline: props.outline(),
+                backgroundColor: props.background(),
+            }}
+        />
+    );
+};
 
 interface IAnnotationProps extends FieldViewProps {
-    anno: Doc;
-    dataDoc: Doc;
+    annoDoc: Doc;
+    containerDataDoc: Doc;
     fieldKey: string;
-    showInfo?: (anno: Opt<Doc>) => void;
     pointerEvents?: () => Opt<string>;
 }
 @observer
@@ -31,64 +55,25 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
     }
 
     @computed get linkHighlighted() {
-        const found = LinkManager.Instance.getAllDirectLinks(this._props.anno).find(link => {
-            const a1 = LinkManager.getOppositeAnchor(link, this._props.anno);
+        const found = LinkManager.Instance.getAllDirectLinks(this._props.annoDoc).find(link => {
+            const a1 = LinkManager.getOppositeAnchor(link, this._props.annoDoc);
             return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1));
         });
         return found;
     }
-    linkHighlightedFunc = () => this.linkHighlighted;
-    highlightedFunc = () => this._props.anno[Highlight];
 
     deleteAnnotation = undoable(() => {
-        const docAnnotations = DocListCast(this._props.dataDoc[this._props.fieldKey]);
-        this._props.dataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this._props.anno));
+        const docAnnotations = DocListCast(this._props.containerDataDoc[this._props.fieldKey]);
+        this._props.containerDataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this._props.annoDoc));
         AnchorMenu.Instance.fadeOut(true);
         this._props.select(false);
     }, 'delete annotation');
 
-    pinToPres = undoable(() => this._props.pinToPres(this._props.anno, {}), 'pin to pres');
-
-    render() {
-        return (
-            <div style={{ display: this._props.anno.textCopied && !Doc.GetBrushHighlightStatus(this._props.anno) ? 'none' : undefined }}>
-                {DocListCast(this._props.anno.text_inlineAnnotations).map(a => (
-                    // eslint-disable-next-line no-use-before-define
-                    <RegionAnnotation
-                        pointerEvents={this._props.pointerEvents}
-                        {...this._props}
-                        highlighted={this.highlightedFunc}
-                        linkHighlighted={this.linkHighlightedFunc}
-                        pinToPres={this.pinToPres}
-                        deleteAnnotation={this.deleteAnnotation}
-                        document={a}
-                        key={a[Id]}
-                    />
-                ))}
-            </div>
-        );
-    }
-}
-
-interface IRegionAnnotationProps extends IAnnotationProps {
-    document: Doc;
-    linkHighlighted: () => Doc | undefined;
-    highlighted: () => any;
-    deleteAnnotation: () => void;
-    pinToPres: (...args: any[]) => void;
-    pointerEvents?: () => Opt<string>;
-}
-@observer
-class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps> {
-    private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
-
-    @computed get regionDoc() {
-        return DocCast(this._props.document.embedContainer, this._props.document);
-    }
+    pinToPres = undoable(() => this._props.pinToPres(this._props.annoDoc, {}), 'pin to pres');
 
-    makeTargetToggle = undoable(() => { this.regionDoc.followLinkToggle = !this.regionDoc.followLinkToggle }, "set link toggle"); // prettier-ignore
+    makeTargetToggle = undoable(() => { this._props.annoDoc.followLinkToggle = !this._props.annoDoc.followLinkToggle }, "set link toggle"); // prettier-ignore
 
-    isTargetToggler = () => BoolCast(this.regionDoc.followLinkToggle);
+    isTargetToggler = () => BoolCast(this._props.annoDoc.followLinkToggle);
 
     showTargetTrail = undoable((anchor: Doc) => {
         const trail = DocCast(anchor.presentationTrail);
@@ -101,12 +86,12 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps>
     @action
     onContextMenu = (e: React.MouseEvent) => {
         AnchorMenu.Instance.Status = 'annotation';
-        AnchorMenu.Instance.Delete = this._props.deleteAnnotation;
+        AnchorMenu.Instance.Delete = this.deleteAnnotation;
         AnchorMenu.Instance.Pinned = false;
-        AnchorMenu.Instance.PinToPres = this._props.pinToPres;
+        AnchorMenu.Instance.PinToPres = this.pinToPres;
         AnchorMenu.Instance.MakeTargetToggle = this.makeTargetToggle;
         AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler;
-        AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.regionDoc);
+        AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this._props.annoDoc);
         AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
         e.stopPropagation();
         e.preventDefault();
@@ -118,37 +103,43 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps>
             e.preventDefault();
         } else if (e.button === 0) {
             e.stopPropagation();
-            LinkFollower.FollowLink(undefined, this.regionDoc, false);
+            LinkFollower.FollowLink(undefined, this._props.annoDoc, false);
         }
     };
-
+    brushed = () => this._props.annoDoc && Doc.GetBrushHighlightStatus(this._props.annoDoc);
+    opacity = () => (this.brushed() === Doc.DocBrushStatus.highlighted ? 0.5 : 1);
+    outline = () => (this.linkHighlighted ? 'solid 1px lightBlue' : undefined);
+    background = () => (this._props.annoDoc[Highlight] ? 'orange' : StrCast(this._props.annoDoc.backgroundColor));
     render() {
-        const brushed = this.regionDoc && Doc.GetBrushHighlightStatus(this.regionDoc);
         return (
-            <div
-                className="htmlAnnotation"
-                ref={this._mainCont}
-                onPointerEnter={action(() => {
-                    Doc.BrushDoc(this._props.anno);
-                    this._props.showInfo?.(this._props.anno);
-                })}
-                onPointerLeave={action(() => {
-                    Doc.UnBrushDoc(this._props.anno);
-                    this._props.showInfo?.(undefined);
-                })}
-                onPointerDown={this.onPointerDown}
-                onContextMenu={this.onContextMenu}
-                style={{
-                    left: NumCast(this._props.document.x),
-                    top: NumCast(this._props.document.y),
-                    width: NumCast(this._props.document._width),
-                    height: NumCast(this._props.document._height),
-                    opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined,
-                    pointerEvents: this._props.pointerEvents?.() as any,
-                    outline: this._props.linkHighlighted() ? 'solid 1px lightBlue' : undefined,
-                    backgroundColor: this._props.highlighted() ? 'orange' : StrCast(this._props.document.backgroundColor),
-                }}
-            />
+            <div style={{ display: this._props.annoDoc.textCopied && !Doc.GetBrushHighlightStatus(this._props.annoDoc) ? 'none' : undefined }}>
+                {StrListCast(this._props.annoDoc.text_inlineAnnotations)
+                    .map(a => a.split?.(':'))
+                    .filter(fields => fields)
+                    .map(([x, y, width, height]) => (
+                        <div
+                            key={'' + x + y + width + height}
+                            style={{ pointerEvents: this._props.pointerEvents?.() as any }}
+                            onPointerDown={this.onPointerDown}
+                            onContextMenu={this.onContextMenu}
+                            onPointerEnter={() => {
+                                Doc.BrushDoc(this._props.annoDoc);
+                            }}
+                            onPointerLeave={() => {
+                                Doc.UnBrushDoc(this._props.annoDoc);
+                            }}>
+                            <RegionAnnotation //
+                                x={Number(x)}
+                                y={Number(y)}
+                                width={Number(width)}
+                                height={Number(height)}
+                                outline={this.outline}
+                                background={this.background}
+                                opacity={this.opacity}
+                            />
+                        </div>
+                    ))}
+            </div>
         );
     }
 }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index a3fd192f7..22355bc57 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -388,7 +388,6 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             this._props.select(false);
             MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-            this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
             this.isAnnotating = true;
             const target = e.target as any;
             if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
@@ -400,6 +399,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, 'htmlAnnotation', { 'pointer-events': 'none' });
                 document.addEventListener('pointerup', this.onSelectEnd);
             }
+            this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
         }
     };
 
@@ -505,7 +505,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
             <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
                 {inlineAnnos.map(anno => (
                     // eslint-disable-next-line react/jsx-props-no-spreading
-                    <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this._props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+                    <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} containerDataDoc={this._props.dataDoc} annoDoc={anno} key={`${anno[Id]}-annotation`} />
                 ))}
             </div>
         );
@@ -615,7 +615,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                             selectionText={this.selectionText}
                             annotationLayer={this._annotationLayer.current}
                             marqueeContainer={this._mainCont.current}
-                            anchorMenuCrop={this._textSelecting ? undefined : this.crop}
+                            anchorMenuCrop={this.crop}
                         />
                     )}
                 </div>
-- 
cgit v1.2.3-70-g09d2


From 098deaa68c8b9bb781748fbe0c1bd0104bab3596 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 30 Apr 2024 23:35:18 -0400
Subject: unwinding more import loops by splitting up Documents.ts into
 DocUtils.ts and moving crate functions to <>Box functions

---
 .eslintrc.json                                     |    2 +-
 .../apis/google_docs/GooglePhotosClientUtils.ts    |    3 +-
 src/client/documents/DocFromField.ts               |   50 +
 src/client/documents/DocUtils.ts                   |  883 ++++++++++++++
 src/client/documents/Documents.ts                  | 1270 +-------------------
 src/client/util/CurrentUserUtils.ts                |    8 +-
 src/client/util/DictationManager.ts                |    5 +-
 src/client/util/DocumentManager.ts                 |    5 +-
 src/client/util/DragManager.ts                     |   21 +-
 src/client/util/GroupManager.tsx                   |    6 +-
 src/client/util/GroupMemberView.tsx                |    4 +-
 src/client/util/LinkFollower.ts                    |   16 +-
 src/client/util/RTFMarkup.tsx                      |   22 +-
 src/client/util/ReplayMovements.ts                 |    2 +-
 src/client/util/SettingsManager.tsx                |   17 +-
 src/client/util/SharingManager.tsx                 |   19 +-
 src/client/views/DashboardView.tsx                 |   47 +-
 src/client/views/DocComponent.tsx                  |    7 +-
 src/client/views/DocumentButtonBar.tsx             |    7 +-
 src/client/views/DocumentDecorations.tsx           |    5 +-
 src/client/views/GestureOverlay.tsx                |   43 +-
 src/client/views/GlobalKeyHandler.ts               |   11 +-
 src/client/views/InkStrokeProperties.ts            |    3 +-
 src/client/views/InkingStroke.tsx                  |   79 +-
 src/client/views/LightboxView.tsx                  |   10 +-
 src/client/views/MainView.tsx                      |   10 +-
 src/client/views/MarqueeAnnotator.tsx              |    8 +-
 src/client/views/PreviewCursor.tsx                 |    3 +-
 src/client/views/PropertiesButtons.tsx             |    5 +-
 .../views/PropertiesDocBacklinksSelector.tsx       |    2 +-
 src/client/views/PropertiesDocContextSelector.tsx  |    3 +-
 src/client/views/PropertiesView.tsx                |    5 +-
 src/client/views/SidebarAnnos.tsx                  |    5 +-
 src/client/views/TemplateMenu.tsx                  |    3 +-
 .../views/collections/CollectionCarousel3DView.tsx |    2 +-
 .../views/collections/CollectionDockingView.tsx    |   26 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |    2 +-
 .../views/collections/CollectionNoteTakingView.tsx |   12 +-
 .../collections/CollectionNoteTakingViewColumn.tsx |   12 +-
 .../views/collections/CollectionPileView.tsx       |    4 +-
 .../collections/CollectionStackedTimeline.tsx      |   12 +-
 .../views/collections/CollectionStackingView.tsx   |   11 +-
 .../CollectionStackingViewFieldColumn.tsx          |   10 +-
 src/client/views/collections/CollectionSubView.tsx |   12 +-
 .../views/collections/CollectionTreeView.tsx       |    5 +-
 src/client/views/collections/CollectionView.tsx    |   23 +-
 src/client/views/collections/TabDocView.tsx        |    5 +-
 src/client/views/collections/TreeView.tsx          |    8 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   17 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |    9 +-
 .../collectionGrid/CollectionGridView.tsx          |    5 +-
 .../collectionSchema/CollectionSchemaView.tsx      |    6 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |    4 +-
 src/client/views/global/globalScripts.ts           |    6 +-
 src/client/views/linking/LinkMenuItem.tsx          |    3 +-
 .../views/newlightbox/ButtonMenu/ButtonMenu.tsx    |    2 +-
 src/client/views/newlightbox/NewLightboxView.tsx   |   10 +-
 src/client/views/nodes/AudioBox.tsx                |   11 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |    6 +-
 src/client/views/nodes/ComparisonBox.tsx           |   21 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   27 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |    4 +-
 src/client/views/nodes/DocumentView.tsx            |   39 +-
 src/client/views/nodes/EquationBox.tsx             |   18 +-
 src/client/views/nodes/FieldView.tsx               |   27 +-
 src/client/views/nodes/FocusViewOptions.ts         |   24 +
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |   17 +-
 src/client/views/nodes/FunctionPlotBox.tsx         |   11 +-
 src/client/views/nodes/ImageBox.tsx                |   13 +-
 src/client/views/nodes/KeyValueBox.tsx             |   10 +-
 src/client/views/nodes/KeyValuePair.tsx            |    3 +-
 src/client/views/nodes/LabelBox.tsx                |   12 +-
 src/client/views/nodes/LinkBox.tsx                 |   18 +
 src/client/views/nodes/LinkDocPreview.tsx          |    3 +-
 src/client/views/nodes/LoadingBox.tsx              |    6 +
 src/client/views/nodes/MapBox/MapBox.tsx           |   16 +-
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx |    2 +-
 src/client/views/nodes/MapBox/MapPushpinBox.tsx    |    7 +
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |   11 +-
 src/client/views/nodes/OpenWhere.ts                |   25 +
 src/client/views/nodes/PDFBox.tsx                  |   14 +-
 .../nodes/PhysicsBox/PhysicsSimulationBox.tsx      |  383 +++---
 .../views/nodes/RecordingBox/RecordingBox.tsx      |    5 +
 src/client/views/nodes/ScreenshotBox.tsx           |   10 +-
 src/client/views/nodes/ScriptingBox.tsx            |    7 +
 src/client/views/nodes/VideoBox.tsx                |   17 +-
 src/client/views/nodes/WebBox.tsx                  |   15 +-
 src/client/views/nodes/calendarBox/CalendarBox.tsx |    6 +
 .../views/nodes/formattedText/DashDocView.tsx      |    5 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |    2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   56 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    |    4 +-
 .../views/nodes/formattedText/RichTextRules.ts     |    3 +-
 .../views/nodes/generativeFill/GenerativeFill.tsx  |    5 +-
 src/client/views/nodes/trails/PresBox.tsx          |   15 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |    7 +-
 src/client/views/pdf/Annotation.tsx                |    2 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |    3 +-
 src/client/views/pdf/PDFViewer.tsx                 |   11 +-
 src/client/views/search/SearchBox.tsx              |   12 +-
 src/client/views/selectedDoc/SelectedDocView.tsx   |    2 +-
 src/fields/Doc.ts                                  |   76 ++
 src/fields/RichTextUtils.ts                        |    3 +-
 src/fields/ScriptField.ts                          |    4 +
 src/mobile/MobileInterface.tsx                     |    3 +-
 105 files changed, 1900 insertions(+), 1896 deletions(-)
 create mode 100644 src/client/documents/DocFromField.ts
 create mode 100644 src/client/documents/DocUtils.ts
 create mode 100644 src/client/views/nodes/FocusViewOptions.ts
 create mode 100644 src/client/views/nodes/OpenWhere.ts

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

diff --git a/.eslintrc.json b/.eslintrc.json
index 6d851a64a..2e4da56b8 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -57,7 +57,7 @@
         "react/destructuring-assignment": 0,
         "no-restricted-globals": ["error", "event"],
         "no-param-reassign": ["error", { "props": false }],
-        "import/no-cycle": 0,
+        // "import/no-cycle": 0,
         "no-alert": 0,
         "radix": "off"
     },
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 1eb977813..fdc185a8e 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -10,7 +10,8 @@ import { RichTextUtils } from '../../../fields/RichTextUtils';
 import { Cast, ImageCast, StrCast } from '../../../fields/Types';
 import { MediaItem, NewMediaItemResult } from '../../../server/apis/google/SharedTypes';
 import { Networking } from '../../Network';
-import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents';
+import { Docs, DocumentOptions } from '../../documents/Documents';
+import { DocUtils } from '../../documents/DocUtils';
 import { FormattedTextBox } from '../../views/nodes/formattedText/FormattedTextBox';
 import { GoogleAuthenticationManager } from '../GoogleAuthenticationManager';
 
diff --git a/src/client/documents/DocFromField.ts b/src/client/documents/DocFromField.ts
new file mode 100644
index 000000000..1c0a9755b
--- /dev/null
+++ b/src/client/documents/DocFromField.ts
@@ -0,0 +1,50 @@
+/* eslint-disable prefer-destructuring */
+/* eslint-disable default-param-last */
+/* eslint-disable no-use-before-define */
+import { Doc, DocListCast } from '../../fields/Doc';
+import { InkField } from '../../fields/InkField';
+import { List } from '../../fields/List';
+import { AudioField, ImageField, PdfField, VideoField } from '../../fields/URLField';
+import { InkingStroke } from '../views/InkingStroke';
+import { CollectionView } from '../views/collections/CollectionView';
+import { AudioBox } from '../views/nodes/AudioBox';
+import { ImageBox } from '../views/nodes/ImageBox';
+import { PDFBox } from '../views/nodes/PDFBox';
+import { VideoBox } from '../views/nodes/VideoBox';
+import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
+import { Docs, DocumentOptions } from './Documents';
+
+export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined {
+    let created: Doc | undefined;
+    const field = target[fieldKey];
+    const resolved = options ?? {};
+    if (field instanceof ImageField) {
+        created = Docs.Create.ImageDocument(field.url.href, resolved);
+        created.layout = ImageBox.LayoutString(fieldKey);
+    } else if (field instanceof Doc) {
+        created = field;
+    } else if (field instanceof VideoField) {
+        created = Docs.Create.VideoDocument(field.url.href, resolved);
+        created.layout = VideoBox.LayoutString(fieldKey);
+    } else if (field instanceof PdfField) {
+        created = Docs.Create.PdfDocument(field.url.href, resolved);
+        created.layout = PDFBox.LayoutString(fieldKey);
+    } else if (field instanceof AudioField) {
+        created = Docs.Create.AudioDocument(field.url.href, resolved);
+        created.layout = AudioBox.LayoutString(fieldKey);
+    } else if (field instanceof InkField) {
+        created = Docs.Create.InkDocument(field.inkData, resolved);
+        created.layout = InkingStroke.LayoutString(fieldKey);
+    } else if (field instanceof List && field[0] instanceof Doc) {
+        created = Docs.Create.StackingDocument(DocListCast(field), resolved);
+        created.layout = CollectionView.LayoutString(fieldKey);
+    } else {
+        created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _layout_autoHeight: true }, ...resolved });
+        created.layout = FormattedTextBox.LayoutString(fieldKey);
+    }
+    if (created) {
+        created.title = fieldKey;
+        proto && created.proto && (created.proto = Doc.GetProto(proto));
+    }
+    return created;
+}
diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
new file mode 100644
index 000000000..e93bbc41b
--- /dev/null
+++ b/src/client/documents/DocUtils.ts
@@ -0,0 +1,883 @@
+/* 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, runInAction } from 'mobx';
+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 { DocData } from '../../fields/DocSymbols';
+import { Id } from '../../fields/FieldSymbols';
+import { InkDataFieldName, InkField } from '../../fields/InkField';
+import { List, ListFieldName } from '../../fields/List';
+import { ProxyField } from '../../fields/Proxy';
+import { RichTextField } from '../../fields/RichTextField';
+import { ComputedField, FollowLinkScript, ScriptField } from '../../fields/ScriptField';
+import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types';
+import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from '../../fields/URLField';
+import { SharingPermissions } from '../../fields/util';
+import { Upload } from '../../server/SharedMediaTypes';
+import { DocServer } from '../DocServer';
+import { Networking } from '../Network';
+import { LinkManager } from '../util/LinkManager';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { SerializationHelper } from '../util/SerializationHelper';
+import { UndoManager, undoable } from '../util/UndoManager';
+import { ContextMenu } from '../views/ContextMenu';
+import { ContextMenuProps } from '../views/ContextMenuItem';
+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';
+import { Docs, DocumentOptions } from './Documents';
+
+const { DFLT_IMAGE_NATIVE_DIM } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
+
+const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
+
+export namespace DocUtils {
+    function matchFieldValue(doc: Doc, key: string, valueIn: any): boolean {
+        let value = valueIn;
+        const hasFunctionFilter = ClientUtils.HasFunctionFilter(value);
+        if (hasFunctionFilter) {
+            return hasFunctionFilter(StrCast(doc[key]));
+        }
+        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 = (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
+            return  (value === Doc.FilterNone && !allLinks.length) || 
+                    (value === Doc.FilterAny && !!allLinks.length) ||
+                    (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || 
+                                           matchLink(value,DocCast(link.link_anchor_2)) ));
+        }
+        if (typeof value === 'string') {
+            value = value.replace(`,${ClientUtils.noRecursionHack}`, '');
+        }
+        const fieldVal = doc[key];
+        // prettier-ignore
+        if ((value === Doc.FilterAny && fieldVal !== undefined) || 
+            (value === Doc.FilterNone && fieldVal === undefined)) {
+            return true;
+        }
+        const vals = StrListCast(fieldVal); // list typing is very imperfect.  casting to a string list doesn't mean that the entries will actually be strings
+        if (vals.length) {
+            return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+        }
+        return Field.toString(fieldVal as FieldType).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+    }
+    /**
+     * @param docs
+     * @param childFilters
+     * @param childFiltersByRanges
+     * @param parentCollection
+     * Given a list of docs and childFilters, @returns the list of Docs that match those filters
+     */
+    export function FilterDocs(childDocs: Doc[], childFilters: string[], childFiltersByRanges: string[], parentCollection?: Doc) {
+        if (!childFilters?.length && !childFiltersByRanges?.length) {
+            return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one
+        }
+
+        const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
+        childFilters.forEach(filter => {
+            const fields = filter.split(Doc.FilterSep);
+            const key = fields[0];
+            const value = fields[1];
+            const modifiers = fields[2];
+            if (!filterFacets[key]) {
+                filterFacets[key] = {};
+            }
+            filterFacets[key][value] = modifiers;
+        });
+
+        const filteredDocs = childFilters.length
+            ? childDocs.filter(d => {
+                  if (d.z) return true;
+                  // if the document needs a cookie but no filter provides the cookie, then the document does not pass the filter
+                  if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) {
+                      return false;
+                  }
+                  const facetKeys = Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== ClientUtils.noDragDocsFilter.split(Doc.FilterSep)[0]);
+                  // eslint-disable-next-line no-restricted-syntax
+                  for (const facetKey of facetKeys) {
+                      const facet = filterFacets[facetKey];
+
+                      // facets that match some value in the field of the document (e.g. some text field)
+                      const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match');
+
+                      // facets that have a check next to them
+                      const checks = Object.keys(facet).filter(value => facet[value] === 'check');
+
+                      // metadata facets that exist
+                      const exists = Object.keys(facet).filter(value => facet[value] === 'exists');
+
+                      // facets that unset metadata (a hack for making cookies work)
+                      const unsets = Object.keys(facet).filter(value => facet[value] === 'unset');
+
+                      // facets that specify that a field must not match a specific value
+                      const xs = Object.keys(facet).filter(value => facet[value] === 'x');
+
+                      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 satisfiesUnsetsFacets = !unsets.length ? true : d[facetKey] === undefined;
+                      const satisfiesMatchFacets = !matches.length
+                          ? true
+                          : matches.some(value => {
+                                if (facetKey.startsWith('*')) {
+                                    //  fields starting with a '*' are used to match families of related fields.  ie, *modificationDate will match text_modificationDate, data_modificationDate, etc
+                                    const allKeys = Array.from(Object.keys(d));
+                                    allKeys.push(...Object.keys(Doc.GetProto(d)));
+                                    const keys = allKeys.filter(key => key.includes(facetKey.substring(1)));
+                                    return keys.some(key => Field.toString(d[key] as FieldType).includes(value));
+                                }
+                                return Field.toString(d[facetKey] as FieldType).includes(value);
+                            });
+                      // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
+                      if (parentCollection?.childFilters_boolean === 'OR') {
+                          if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
+                      }
+                      // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
+                      else if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
+                  }
+                  return parentCollection?.childFilters_boolean !== 'OR';
+              })
+            : childDocs;
+        const rangeFilteredDocs = filteredDocs.filter(d => {
+            for (let i = 0; i < childFiltersByRanges.length; i += 3) {
+                const key = childFiltersByRanges[i];
+                const min = Number(childFiltersByRanges[i + 1]);
+                const max = Number(childFiltersByRanges[i + 2]);
+                const val = typeof d[key] === 'string' ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], 'number', null);
+                if (val === undefined) {
+                    // console.log("Should 'undefined' pass range filter or not?")
+                } else if (val < min || val > max) return false;
+            }
+            return true;
+        });
+        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[]) {
+        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, showAt?: number[]) => {
+            if (showAt) {
+                LinkManager.Instance.currentLink = linkDoc;
+
+                TaskCompletionBox.textDisplayed = 'Link Created';
+                TaskCompletionBox.popupX = showAt[0];
+                TaskCompletionBox.popupY = showAt[1] - 33;
+                TaskCompletionBox.taskCompleted = true;
+
+                LinkDescriptionPopup.Instance.popupX = showAt[0];
+                LinkDescriptionPopup.Instance.popupY = showAt[1];
+                LinkDescriptionPopup.Instance.display = true;
+
+                const rect = document.body.getBoundingClientRect();
+                if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
+                    LinkDescriptionPopup.Instance.popupX -= 190;
+                    TaskCompletionBox.popupX -= 40;
+                }
+                if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
+                    LinkDescriptionPopup.Instance.popupY -= 40;
+                    TaskCompletionBox.popupY -= 40;
+                }
+
+                setTimeout(
+                    action(() => {
+                        TaskCompletionBox.taskCompleted = false;
+                    }),
+                    2500
+                );
+            }
+            return linkDoc;
+        });
+
+        const a = source.layout_unrendered ? 'link_anchor_1?.annotationOn' : 'link_anchor_1';
+        const b = target.layout_unrendered ? 'link_anchor_2?.annotationOn' : 'link_anchor_2';
+
+        return makeLink(
+            Docs.Create.LinkDocument(
+                source,
+                target,
+                {
+                    acl_Guest: SharingPermissions.Augment,
+                    _acl_Guest: SharingPermissions.Augment,
+                    title: ComputedField.MakeFunction('generateLinkTitle(this)') as any,
+                    link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined,
+                    link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined,
+                    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,
+                    y: ComputedField.MakeFunction(`((this.${a}?.y||0)+(this.${b}?.y||0))/2`) as any,
+                    link_autoMoveAnchors: true,
+                    _lockedPosition: true,
+                    _layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box)
+                    _layout_showTitle: '',
+                    // _layout_showCaption: 'link_description',
+                    // _layout_showTitle: 'link_relationship',
+                },
+                id
+            ),
+            showPopup
+        );
+    }
+
+    export function AssignScripts(doc: Doc, scripts?: { [key: string]: string | undefined }, funcs?: { [key: string]: string }) {
+        scripts &&
+            Object.keys(scripts).forEach(key => {
+                const script = scripts[key];
+                if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) {
+                    (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, {
+                        self: Doc.name,
+                        this: Doc.name,
+                        dragData: Doc.DocDragDataName,
+                        value: 'any',
+                        _readOnly_: 'boolean',
+                        scriptContext: 'any',
+                        documentView: Doc.name,
+                        heading: Doc.name,
+                        checked: 'boolean',
+                        containingTreeView: Doc.name,
+                        altKey: 'boolean',
+                        ctrlKey: 'boolean',
+                        shiftKey: 'boolean',
+                    });
+                }
+            });
+        funcs &&
+            Object.keys(funcs)
+                .filter(key => !key.endsWith('-setter'))
+                .forEach(key => {
+                    const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+                    if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) {
+                        const setFunc = Cast(funcs[key + '-setter'], 'string', null);
+                        (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: Doc.DocDragDataName }, { _readOnly_: true }, setFunc) : undefined;
+                    }
+                });
+        return doc;
+    }
+    export function AssignOpts(doc: Doc | undefined, reqdOpts: DocumentOptions, items?: Doc[]) {
+        if (doc) {
+            const compareValues = (val1: any, val2: any) => {
+                if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) {
+                    return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v));
+                }
+                return val1 === val2;
+            };
+            Object.entries(reqdOpts).forEach(pair => {
+                const targetDoc = pair[0].startsWith('_') ? doc : Doc.GetProto(doc as Doc);
+                if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/, '')) || !compareValues(pair[1], targetDoc[pair[0]])) {
+                    targetDoc[pair[0]] = pair[1];
+                }
+            });
+            items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item));
+            items && DocListCast(doc.data).forEach(item => Doc.IsSystem(item) && !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item));
+        }
+        return doc;
+    }
+    export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) {
+        // eslint-disable-next-line no-return-assign
+        return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs);
+    }
+
+    /**
+     *
+     * @param type the type of file.
+     * @param path the path to the file.
+     * @param options the document options.
+     * @param overwriteDoc the placeholder loading doc.
+     * @returns
+     */
+    export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise<Opt<Doc>> {
+        let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined;
+        if (type.indexOf('image') !== -1) {
+            ctor = Docs.Create.ImageDocument;
+            if (!options._width) options._width = 300;
+        }
+        if (type.indexOf('video') !== -1) {
+            ctor = Docs.Create.VideoDocument;
+            if (!options._width) options._width = 600;
+            if (!options._height) options._height = ((options._width as number) * 2) / 3;
+        }
+        if (type.indexOf('audio') !== -1) {
+            ctor = Docs.Create.AudioDocument;
+        }
+        if (type.indexOf('pdf') !== -1) {
+            ctor = Docs.Create.PdfDocument;
+            if (!options._width) options._width = 400;
+            if (!options._height) options._height = ((options._width as number) * 1200) / 927;
+        }
+        if (type.indexOf('csv') !== -1) {
+            ctor = Docs.Create.DataVizDocument;
+            if (!options._width) options._width = 400;
+            if (!options._height) options._height = ((options._width as number) * 1200) / 927;
+        }
+        // TODO:al+glr
+        // if (type.indexOf("map") !== -1) {
+        //     ctor = Docs.Create.MapDocument;
+        //     if (!options._width) options._width = 800;
+        //     if (!options._height) options._height = (options._width as number) * 3 / 4;
+        // }
+        if (type.indexOf('html') !== -1) {
+            if (path.includes(window.location.hostname)) {
+                const s = path.split('/');
+                const id = s[s.length - 1];
+                return DocServer.GetRefField(id).then(field => {
+                    if (field instanceof Doc) {
+                        const embedding = Doc.MakeEmbedding(field);
+                        embedding.x = (options.x as number) || 0;
+                        embedding.y = (options.y as number) || 0;
+                        embedding._width = (options._width as number) || 300;
+                        embedding._height = (options._height as number) || (options._width as number) || 300;
+                        return embedding;
+                    }
+                    return undefined;
+                });
+            }
+            ctor = Docs.Create.WebDocument;
+            // eslint-disable-next-line no-param-reassign
+            options = { ...options, _width: 400, _height: 512, title: path };
+        }
+
+        return ctor ? ctor(path, overwriteDoc ? { ...options, title: StrCast(overwriteDoc.title, path) } : options, overwriteDoc) : undefined;
+    }
+
+    export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void {
+        const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data)
+            .filter(btnDoc => !btnDoc.hidden)
+            .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
+            .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title)
+            .map(dragDoc => ({
+                description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
+                event: undoable(() => {
+                    const newDoc = DocUtils.copyDragFactory(dragDoc);
+                    if (newDoc) {
+                        newDoc.author = ClientUtils.CurrentUserEmail();
+                        newDoc.x = x;
+                        newDoc.y = y;
+                        Doc.SetSelectOnLoad(newDoc);
+                        if (pivotField) {
+                            newDoc[pivotField] = pivotValue;
+                        }
+                        docAdder?.(newDoc);
+                    }
+                }, StrCast(dragDoc.title)),
+                icon: Doc.toIcon(dragDoc),
+            })) as ContextMenuProps[];
+        ContextMenu.Instance.addItem({
+            description: 'Create document',
+            subitems: documentList,
+            icon: 'file',
+        });
+        !simpleMenu &&
+            ContextMenu.Instance.addItem({
+                description: 'Styled Notes',
+                subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map(note => ({
+                    description: ':' + StrCast(note.title),
+                    event: undoable(() => {
+                        const textDoc = Docs.Create.TextDocument('', {
+                            _width: 200,
+                            x,
+                            y,
+                            _layout_autoHeight: note._layout_autoHeight !== false,
+                            title: StrCast(note.title) + '#' + (note.embeddingCount = NumCast(note.embeddingCount) + 1),
+                        });
+                        textDoc.layout_fieldKey = 'layout_' + note.title;
+                        textDoc[textDoc.layout_fieldKey] = note;
+                        if (pivotField) {
+                            textDoc[pivotField] = pivotValue;
+                        }
+                        docTextAdder(textDoc);
+                    }, 'create quick note'),
+                    icon: StrCast(note.icon) as IconProp,
+                })) as ContextMenuProps[],
+                icon: 'sticky-note',
+            });
+        const userDocList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[1]?.data)
+            .filter(btnDoc => !btnDoc.hidden)
+            .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
+            .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title)
+            .map(dragDoc => ({
+                description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
+                event: undoable(() => {
+                    const newDoc = DocUtils.delegateDragFactory(dragDoc);
+                    if (newDoc) {
+                        newDoc.author = ClientUtils.CurrentUserEmail();
+                        newDoc.x = x;
+                        newDoc.y = y;
+                        Doc.SetSelectOnLoad(newDoc);
+                        if (pivotField) {
+                            newDoc[pivotField] = pivotValue;
+                        }
+                        docAdder?.(newDoc);
+                    }
+                }, StrCast(dragDoc.title)),
+                icon: Doc.toIcon(dragDoc),
+            })) as ContextMenuProps[];
+        ContextMenu.Instance.addItem({
+            description: 'User Templates',
+            subitems: userDocList,
+            icon: 'file',
+        });
+    }
+
+    // applies a custom template to a document.  the template is identified by it's short name (e.g, slideView not layout_slideView)
+
+    /**
+     * Applies a template to a Doc and logs the action with the UndoManager
+     * If the template already exists and has been registered, it can be specified by it's signature name (e.g., 'icon' not 'layout_icon').
+     * Alternatively, the signature can be omitted and the template can be provided.
+     * @param doc the Doc to apply the template to.
+     * @param creator a function that will create the template if it doesn't exist
+     * @param templateSignature the signature name for a template that has already been created and registered on the userDoc.  (can be "" if template is provide)
+     * @param template the template to use (optional if templateSignature is provided)
+     * @returns doc
+     */
+    export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', template?: Doc) {
+        const batch = UndoManager.StartBatch('makeCustomViewClicked');
+        createCustomView(doc, creator, templateSignature || StrCast(template?.title), template);
+        batch.end();
+        return doc;
+    }
+    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);
+        const noteTypes = DocListCast(Cast(Doc.UserDoc().template_notes, Doc, null)?.data);
+        const userTypes = DocListCast(Cast(Doc.UserDoc().template_user, Doc, null)?.data);
+        const clickFuncs = DocListCast(Cast(Doc.UserDoc().template_clickFuncs, Doc, null)?.data);
+        const allTemplates = iconViews
+            .concat(templBtns)
+            .concat(noteTypes)
+            .concat(userTypes)
+            .concat(clickFuncs)
+            .map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc)
+            .filter(doc => doc.isTemplateDoc);
+        // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document.  but type is not reliable and there could be other types of template searches so this should be generalized
+        // first try to find a template that matches the specific document type (<typeName>_<templateName>).  otherwise, fallback to a general match on <templateName>
+        !docLayoutTemplate &&
+            allTemplates.forEach(tempDoc => {
+                StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc);
+            });
+        !docLayoutTemplate &&
+            allTemplates.forEach(tempDoc => {
+                StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc);
+            });
+        return docLayoutTemplate;
+    }
+    export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) {
+        const templateName = templateSignature.replace(/\(.*\)/, '');
+        doc.layout_fieldKey = 'layout_' + (templateSignature || (docLayoutTemplate?.title ?? ''));
+        // eslint-disable-next-line no-param-reassign
+        docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type));
+
+        const customName = 'layout_' + templateSignature;
+        const _width = NumCast(doc._width);
+        const _height = NumCast(doc._height);
+        const options = { title: 'data', backgroundColor: StrCast(doc.backgroundColor), _layout_autoHeight: true, _width, x: -_width / 2, y: -_height / 2, _layout_showSidebar: false };
+
+        if (docLayoutTemplate) {
+            if (docLayoutTemplate !== doc[customName]) {
+                Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined);
+            }
+        } else {
+            let fieldTemplate: Opt<Doc>;
+            if (doc.data instanceof RichTextField || typeof doc.data === 'string') {
+                fieldTemplate = Docs.Create.TextDocument('', options);
+            } else if (doc.data instanceof PdfField) {
+                fieldTemplate = Docs.Create.PdfDocument('http://www.msn.com', options);
+            } else if (doc.data instanceof VideoField) {
+                fieldTemplate = Docs.Create.VideoDocument('http://www.cs.brown.edu', options);
+            } else if (doc.data instanceof AudioField) {
+                fieldTemplate = Docs.Create.AudioDocument('http://www.cs.brown.edu', options);
+            } else if (doc.data instanceof ImageField) {
+                fieldTemplate = Docs.Create.ImageDocument('http://www.cs.brown.edu', options);
+            }
+            const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + '(' + doc.title + ')', isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
+            fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
+            docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
+        }
+    }
+    export function makeCustomView(doc: Doc, custom: boolean, layout: string) {
+        Doc.setNativeView(doc);
+        if (custom) {
+            makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined);
+        }
+    }
+    export function iconify(doc: Doc) {
+        const layoutFieldKey = Cast(doc.layout_fieldKey, 'string', null);
+        DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined);
+        if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') doc.deiconifyLayout = layoutFieldKey.replace('layout_', '');
+    }
+
+    export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) {
+        runInAction(() => {
+            docList.forEach((doc, i) => {
+                const d = doc;
+                DocUtils.iconify(d);
+                d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size - size;
+                d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size - size;
+                d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+            });
+        });
+        if (create) {
+            const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', _freeform_noZoom: true, x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true, _layout_fitWidth: false });
+            newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size;
+            newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size;
+            newCollection._width = newCollection._height = size * 2;
+            return newCollection;
+        }
+        return undefined;
+    }
+    export function makeIntoPortal(doc: Doc, layoutDoc: Doc, allLinks: Doc[]) {
+        const portalLink = allLinks.find(d => d.link_anchor_1 === doc && d.link_relationship === 'portal to:portal from');
+        if (!portalLink) {
+            DocUtils.MakeLink(
+                doc,
+                Docs.Create.FreeformDocument([], {
+                    _width: NumCast(layoutDoc._width) + 10,
+                    _height: Math.max(NumCast(layoutDoc._height), NumCast(layoutDoc._width) + 10),
+                    _isLightbox: true,
+                    _layout_fitWidth: true,
+                    title: StrCast(doc.title) + ' [Portal]',
+                }),
+                { link_relationship: 'portal to:portal from' }
+            );
+        }
+        doc.followLinkLocation = OpenWhere.lightbox;
+        doc.onClick = FollowLinkScript();
+    }
+
+    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));
+        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: '',
+                annotationOn: Cast(doc.annotationOn, Doc, null),
+                followLinkToggle: true,
+                icon: 'map-pin',
+                x: Cast(doc.x, 'number', null),
+                y: Cast(doc.y, 'number', null),
+                backgroundColor: '#ACCEF7',
+                layout_hideAllLinks: true,
+                _width: 15,
+                _height: 15,
+                _xPadding: 0,
+                onClick: FollowLinkScript(),
+                _timecodeToShow: Cast(doc._timecodeToShow, 'number', null),
+            });
+            Doc.AddDocToList(context, annotationField, pushpin);
+            DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, '');
+            doc._timecodeToShow = undefined;
+            return pushpin;
+        }
+        return undefined;
+    }
+
+    // /**
+    //  *
+    //  * @param dms Degree Minute Second format exif gps data
+    //  * @param ref ref that determines negativity of decimal coordinates
+    //  * @returns a decimal format of gps latitude / longitude
+    //  */
+    // function getDecimalfromDMS(dms?: number[], ref?: string) {
+    //     if (dms && ref) {
+    //         let degrees = dms[0] / dms[1];
+    //         let minutes = dms[2] / dms[3] / 60.0;
+    //         let seconds = dms[4] / dms[5] / 3600.0;
+
+    //         if (['S', 'W'].includes(ref)) {
+    //             degrees = -degrees; minutes = -minutes; seconds = -seconds
+    //         }
+    //         return (degrees + minutes + seconds).toFixed(5);
+    //     }
+    // }
+
+    function ConvertDMSToDD(degrees: number, minutes: number, seconds: number, direction: string) {
+        let dd = degrees + minutes / 60 + seconds / (60 * 60);
+        if (direction === 'S' || direction === 'W') {
+            dd *= -1;
+        } // Don't do anything for N or E
+        return dd;
+    }
+
+    export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) {
+        const proto = protoIn;
+        if (Upload.isImageInformation(result)) {
+            const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
+            const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
+            proto.data_nativeOrientation = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
+            proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+            proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+            if (NumCast(proto.data_nativeOrientation) >= 5) {
+                proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
+                proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
+            }
+            proto.data_exif = JSON.stringify(result.exifData?.data);
+            proto.data_contentSize = result.contentSize;
+            // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
+            const latitude = result.exifData?.data?.GPSLatitude;
+            const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
+            const longitude = result.exifData?.data?.GPSLongitude;
+            const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
+            if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
+                proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
+                proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
+            }
+        }
+    }
+
+    async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) {
+        if (result instanceof Error) {
+            alert(`Upload failed: ${result.message}`);
+            return;
+        }
+        const full = { ...options, _width: 400, title: name };
+        const pathname = result.accessPaths.agnostic.client;
+        const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc);
+        if (doc) {
+            const proto = Doc.GetProto(doc);
+            proto.text = result.rawText;
+            !(result instanceof Error) && DocUtils.assignImageInfo(result, proto);
+            if (Upload.isVideoInformation(result)) {
+                proto.data_duration = result.duration;
+            }
+            if (overwriteDoc) {
+                LoadingBox.removeCurrentlyLoading(overwriteDoc);
+            }
+            generatedDocuments.push(doc);
+        }
+    }
+
+    export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) {
+        const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout);
+        const tbox = Docs.Create.TextDocument('', {
+            annotationOn,
+            backgroundColor,
+            x,
+            y,
+            title,
+            ...(defaultTextTemplate
+                ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance
+                : {
+                      _width: width || 200,
+                      _height: 35,
+                      _layout_centered: BoolCast(Doc.UserDoc()._layout_centered),
+                      _layout_fitWidth: true,
+                      _layout_autoHeight: true,
+                  }),
+        });
+
+        if (defaultTextTemplate) {
+            tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title);
+            Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = defaultTextTemplate; // set the text doc's layout to render with the text template
+            tbox[DocData].proto = defaultTextTemplate; // and also set the text doc to inherit from the template (this allows the template to specify default field values)
+        }
+        return tbox;
+    }
+
+    export function uploadYoutubeVideoLoading(videoId: string, options: DocumentOptions, overwriteDoc?: Doc) {
+        const generatedDocuments: Doc[] = [];
+        Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => {
+            const {
+                source: { newFilename, mimetype },
+                result,
+            } = upfiles.lastElement();
+            if ((result as any).message) {
+                if (overwriteDoc) {
+                    overwriteDoc.isLoading = false;
+                    overwriteDoc.loadingError = (result as any).message;
+                    LoadingBox.removeCurrentlyLoading(overwriteDoc);
+                }
+            } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype ?? '', result, options, overwriteDoc);
+        });
+    }
+
+    /**
+     * uploadFilesToDocs will take in an array of Files, and creates documents for the
+     * new files.
+     *
+     * @param files an array of files that will be uploaded
+     * @param options options to use while uploading
+     * @returns
+     */
+    export async function uploadFilesToDocs(files: File[], options: DocumentOptions) {
+        const generatedDocuments: Doc[] = [];
+
+        // These files do not have overwriteDocs, so we do not set the guid and let the client generate one.
+        const fileNoGuidPairs: Networking.FileGuidPair[] = files.map(file => ({ file }));
+
+        const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs);
+        upfiles.forEach(({ source: { newFilename, mimetype }, result }) => {
+            newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options);
+        });
+        return generatedDocuments;
+    }
+
+    export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) {
+        const generatedDocuments: Doc[] = [];
+        // Since this file has an overwriteDoc, we can set the client tracking guid to the overwriteDoc's guid.
+        Networking.UploadFilesToServer([{ file, guid: overwriteDoc[Id] }]).then(upfiles => {
+            const {
+                source: { newFilename, mimetype },
+                result,
+            } = upfiles.lastElement() ?? { source: { newFilename: '', mimetype: '' }, result: { message: 'upload failed' } };
+            if ((result as any).message) {
+                if (overwriteDoc) {
+                    overwriteDoc.loadingError = (result as any).message;
+                    LoadingBox.removeCurrentlyLoading(overwriteDoc);
+                }
+            } else newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc);
+        });
+    }
+
+    // copies the specified drag factory document
+    export function copyDragFactory(dragFactory: Doc) {
+        if (!dragFactory) return undefined;
+        const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
+        if (ndoc && dragFactory.dragFactory_count !== undefined) {
+            dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1;
+            Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString(), true);
+        }
+
+        return ndoc;
+    }
+    export function delegateDragFactory(dragFactory: Doc) {
+        const ndoc = Doc.MakeDelegateWithProto(dragFactory);
+        if (ndoc && dragFactory.dragFactory_count !== undefined) {
+            dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1;
+            Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString();
+        }
+        return ndoc;
+    }
+
+    export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
+        const { clone, map, linkMap } = await Doc.MakeClone(doc);
+        const proms = new Set<string>();
+        function replacer(key: any, value: any) {
+            if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
+            if (value?.__type === 'image') {
+                const extension = value.url.replace(/.*\./, '');
+                proms.add(value.url.replace('.' + extension, '_o.' + extension));
+                return SerializationHelper.Serialize(new ImageField(value.url));
+            }
+            if (value?.__type === 'pdf') {
+                proms.add(value.url);
+                return SerializationHelper.Serialize(new PdfField(value.url));
+            }
+            if (value?.__type === 'audio') {
+                proms.add(value.url);
+                return SerializationHelper.Serialize(new AudioField(value.url));
+            }
+            if (value?.__type === 'video') {
+                proms.add(value.url);
+                return SerializationHelper.Serialize(new VideoField(value.url));
+            }
+            if (
+                value instanceof Doc ||
+                value instanceof ScriptField ||
+                value instanceof RichTextField ||
+                value instanceof InkField ||
+                value instanceof CsvField ||
+                value instanceof WebField ||
+                value instanceof DateField ||
+                value instanceof ProxyField ||
+                value instanceof ComputedField
+            ) {
+                return SerializationHelper.Serialize(value);
+            }
+            if (value instanceof Array && key !== ListFieldName && key !== InkDataFieldName) return { fields: value, __type: 'list' };
+            return value;
+        }
+
+        const docs: { [id: string]: any } = {};
+        const links: { [id: string]: any } = {};
+        Array.from(map.entries()).forEach(f => {
+            docs[f[0]] = f[1];
+        });
+        Array.from(linkMap.entries()).forEach(l => {
+            links[l[0]] = l[1];
+        });
+        const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer));
+
+        const zip = new JSZip();
+        let count = 0;
+        const promArr = Array.from(proms)
+            .filter(url => url?.startsWith('/files'))
+            .map(url => url.replace('/', '')); // window.location.origin));
+        console.log(promArr.length);
+        if (!promArr.length) {
+            zip.file('docs.json', jsonDocs);
+            zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
+        } else
+            promArr.forEach((url, i) => {
+                // loading a file and add it in a zip file
+                JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => {
+                    if (err) throw err; // or handle the error
+                    // // Generate a directory within the Zip file structure
+                    // const assets = zip.folder("assets");
+                    // assets.file(filename, data, {binary: true});
+                    const assetPathOnServer = promArr[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%');
+                    zip.file(assetPathOnServer, data, { binary: true });
+                    console.log(' => ' + url);
+                    if (++count === promArr.length) {
+                        zip.file('docs.json', jsonDocs);
+                        zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
+                        // const a = document.createElement("a");
+                        // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+                        // a.href = url;
+                        // a.download = `DocExport-${this.props.Document[Id]}.zip`;
+                        // a.click();
+                    }
+                });
+            });
+    }
+}
+
+ScriptingGlobals.add('Docs', Docs);
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) {
+    return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory;
+});
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function makeDelegate(proto: any) {
+    const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title });
+    return d;
+});
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function generateLinkTitle(link: Doc) {
+    const linkAnchor1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : '<?>';
+    const linkAnchor2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>';
+    const relation = link.link_relationship || 'to';
+    return `${linkAnchor1title} (${relation}) ${linkAnchor2title}`;
+});
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0e7f911c7..a3fea1ce4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,79 +1,27 @@
 /* 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 { reaction } from 'mobx';
 import { basename } from 'path';
 import { ClientUtils, OmitKeys } from '../../ClientUtils';
-import * as JSZipUtils from '../../JSZipUtils';
-import { decycle } from '../../decycler/decycler';
 import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, FieldType, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
-import { DocData, Initializing } from '../../fields/DocSymbols';
-import { Id } from '../../fields/FieldSymbols';
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, CreateLinkToActiveAudio, Doc, FieldType, Opt, updateCachedAcls } from '../../fields/Doc';
+import { Initializing } from '../../fields/DocSymbols';
 import { HtmlField } from '../../fields/HtmlField';
-import { InkDataFieldName, InkField } from '../../fields/InkField';
-import { List, ListFieldName } from '../../fields/List';
-import { ProxyField } from '../../fields/Proxy';
+import { InkField } from '../../fields/InkField';
+import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
 import { SchemaHeaderField } from '../../fields/SchemaHeaderField';
 import { ComputedField, ScriptField } from '../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types';
+import { ScriptCast, StrCast } from '../../fields/Types';
 import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from '../../fields/URLField';
-import { SharingPermissions, inheritParentAcls } from '../../fields/util';
+import { SharingPermissions } from '../../fields/util';
 import { PointData } from '../../pen-gestures/GestureTypes';
-import { Upload } from '../../server/SharedMediaTypes';
 import { DocServer } from '../DocServer';
-import { Networking } from '../Network';
-import { DragManager } from '../util/DragManager';
 import { dropActionType } from '../util/DropActionTypes';
-import { FollowLinkScript } from '../util/LinkFollower';
 import { LinkManager } from '../util/LinkManager';
-import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { SerializationHelper } from '../util/SerializationHelper';
-import { UndoManager, undoable } from '../util/UndoManager';
-import { ContextMenu } from '../views/ContextMenu';
-import { ContextMenuProps } from '../views/ContextMenuItem';
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { CollectionView } from '../views/collections/CollectionView';
-import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView';
-import { AudioBox, mediaState } from '../views/nodes/AudioBox';
-import { ComparisonBox } from '../views/nodes/ComparisonBox';
-import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
-import { OpenWhere } from '../views/nodes/DocumentView';
-import { EquationBox } from '../views/nodes/EquationBox';
-import { FieldViewProps } from '../views/nodes/FieldView';
-import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
-import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox';
-import { ImageBox } from '../views/nodes/ImageBox';
-import { KeyValueBox } from '../views/nodes/KeyValueBox';
-import { LabelBox } from '../views/nodes/LabelBox';
-import { LinkBox } from '../views/nodes/LinkBox';
-import { LinkDescriptionPopup } from '../views/nodes/LinkDescriptionPopup';
-import { LoadingBox } from '../views/nodes/LoadingBox';
-import { MapBox } from '../views/nodes/MapBox/MapBox';
-import { MapPushpinBox } from '../views/nodes/MapBox/MapPushpinBox';
-import { PDFBox } from '../views/nodes/PDFBox';
-import { PhysicsSimulationBox } from '../views/nodes/PhysicsBox/PhysicsSimulationBox';
-import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox';
-import { ScreenshotBox } from '../views/nodes/ScreenshotBox';
-import { ScriptingBox } from '../views/nodes/ScriptingBox';
-import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
-import { VideoBox } from '../views/nodes/VideoBox';
-import { WebBox } from '../views/nodes/WebBox';
-import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox';
-import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
-import { PresBox } from '../views/nodes/trails/PresBox';
-import { PresElementBox } from '../views/nodes/trails/PresElementBox';
-import { SearchBox } from '../views/search/SearchBox';
 import { CollectionViewType, DocumentType } from './DocumentTypes';
 
-const { DFLT_IMAGE_NATIVE_DIM } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
-
-const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
 class EmptyBox {
     public static LayoutString() {
         return '';
@@ -146,7 +94,7 @@ class DocInfo extends FInfo {
 }
 class DimInfo extends FInfo {
     fieldType? = FInfoFieldType.enumeration;
-    values? = [DimUnit.Pixel, DimUnit.Ratio];
+    values? = []; // DimUnit.Pixel, DimUnit.Ratio];
     readOnly = false;
     filterable = false;
     override searchable = () => false;
@@ -202,7 +150,7 @@ type STRt = StrInfo | string;
 type LISTt = ListInfo | List<any>;
 type DOCt = DocInfo | Doc;
 type RTFt = RtfInfo | RichTextField;
-type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio;
+type DIMt = DimInfo; // | typeof DimUnit.Pixel | typeof DimUnit.Ratio;
 type PEVt = PEInfo | 'none' | 'all';
 type COLLt = CTypeInfo | CollectionViewType;
 type DROPt = DAInfo | dropActionType;
@@ -245,6 +193,7 @@ export class DocumentOptions {
     _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)', false);
     _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)', false);
 
+    acl?: STRt = new StrInfo('unused except as a display category in KeyValueBox');
     acl_Guest?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions
     _acl_Guest?: string; // public permissions
     type?: DTYPEt = new DTypeInfo('type of document', true);
@@ -253,6 +202,7 @@ export class DocumentOptions {
     title?: STRt = new StrInfo('title of document', true);
     title_custom?: BOOLt = new BoolInfo('whether title is a default or has been intentionally set');
     caption?: RichTextField;
+    systemIcon?: STRt = new StrInfo("name of icon to use to represent document's type");
     author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable
     author_date?: DATEt = new DateInfo('date the document was created', true);
     annotationOn?: DOCt = new DocInfo('document annotated by this document', false);
@@ -272,6 +222,12 @@ export class DocumentOptions {
     _lockedPosition?: BOOLt = new BoolInfo("lock the x,y coordinates of the document so that it can't be dragged");
     _lockedTransform?: BOOLt = new BoolInfo('lock the freeform_panx,freeform_pany and scale parameters of the document so that it be panned/zoomed');
 
+    dataViz_title?: string;
+    dataViz_line?: string;
+    dataViz_pie?: string;
+    dataViz_histogram?: string;
+    dataViz?: string;
+
     layout?: string | Doc; // default layout string or template document
     layout_isSvg?: BOOLt = new BoolInfo('whether document decorations and other selections should handle pointerEvents for svg content or use doc bounding box');
     layout_keyValue?: STRt = new StrInfo('layout definition for showing keyValue view of document', false);
@@ -325,6 +281,7 @@ export class DocumentOptions {
     _text_fontSize?: string;
     _text_fontFamily?: string;
     _text_fontWeight?: string;
+    fontSize?: string;
     _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views
 
     infoWindowOpen?: BOOLt = new BoolInfo('whether info window corresponding to pin is open (on MapDocuments)');
@@ -333,7 +290,7 @@ export class DocumentOptions {
     _label_maxFontSize?: NUMt = new NumInfo('maximum font size for labelBoxes', false);
     stroke_width?: NUMt = new NumInfo('width of an ink stroke', false);
     stroke_showLabel?: BOOLt = new BoolInfo('show label inside of stroke');
-    mediaState?: STRt = new StrInfo(`status of audio/video media document: ${mediaState.PendingRecording}, ${mediaState.Recording}, ${mediaState.Paused}, ${mediaState.Playing}`, false);
+    mediaState?: STRt = new StrInfo(`status of audio/video media document:`); // ${mediaState.PendingRecording}, ${mediaState.Recording}, ${mediaState.Paused}, ${mediaState.Playing}`, false);
     recording?: BOOLt = new BoolInfo('whether WebCam is recording or not');
     slides?: DOCt = new DocInfo('presentation slide associated with video recording (bcz: should be renamed!!)');
     autoPlayAnchors?: BOOLt = new BoolInfo('whether to play audio/video when an anchor is clicked in a stackedTimeline.');
@@ -403,6 +360,7 @@ export class DocumentOptions {
     // STOPPING HERE
 
     // freeform properties
+    freeform?: STRt = new StrInfo('');
     _freeform_backgroundGrid?: BOOLt = new BoolInfo('whether background grid is shown on freeform collections');
     _freeform_scale_min?: NUMt = new NumInfo('how far out a view can zoom (used by image/videoBoxes that are clipped');
     _freeform_scale_max?: NUMt = new NumInfo('how far in a view can zoom (used by sidebar freeform views');
@@ -433,6 +391,7 @@ export class DocumentOptions {
     flexGap?: NUMt = new NumInfo('Linear view flex gap');
     flexDirection?: 'unset' | 'row' | 'column' | 'row-reverse' | 'column-reverse';
 
+    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');
@@ -528,120 +487,12 @@ export namespace Docs {
         type PrototypeMap = Map<DocumentType, Doc>;
         const defaultDataKey = 'data';
 
-        const TemplateMap: TemplateMap = new Map([
-            [
-                DocumentType.RTF,
-                {
-                    layout: { view: FormattedTextBox, dataField: 'text' },
-                    options: {
-                        acl: '',
-                        _height: 35,
-                        _xMargin: 10,
-                        _yMargin: 10,
-                        layout_nativeDimEditable: true,
-                        layout_reflowVertical: true,
-                        layout_reflowHorizontal: true,
-                        defaultDoubleClick: 'ignore',
-                        systemIcon: 'BsFileEarmarkTextFill',
-                    },
-                },
-            ],
-            [
-                DocumentType.SEARCH,
-                {
-                    layout: { view: SearchBox, dataField: defaultDataKey },
-                    options: { acl: '', _width: 400 },
-                },
-            ],
-            [
-                DocumentType.IMG,
-                {
-                    layout: { view: ImageBox, dataField: defaultDataKey },
-                    options: { acl: '', freeform: '', systemIcon: 'BsFileEarmarkImageFill' },
-                },
-            ],
-            [
-                DocumentType.WEB,
-                {
-                    layout: { view: WebBox, dataField: defaultDataKey },
-                    options: { acl: '', _height: 300, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' },
-                },
-            ],
+        export const TemplateMap: TemplateMap = new Map([
             [
-                DocumentType.COL,
-                {
-                    layout: { view: CollectionView, dataField: defaultDataKey },
-                    options: {
-                        acl: '',
-                        _layout_fitWidth: true,
-                        freeform: '',
-                        _freeform_panX: 0,
-                        _freeform_panY: 0,
-                        _freeform_scale: 1,
-                        layout_nativeDimEditable: true,
-                        layout_reflowHorizontal: true,
-                        layout_reflowVertical: true,
-                        systemIcon: 'BsFillCollectionFill',
-                    },
-                },
-            ],
-            [
-                DocumentType.KVP,
-                {
-                    layout: { view: KeyValueBox, dataField: defaultDataKey },
-                    options: { acl: '', _layout_fitWidth: true, _height: 150 },
-                },
-            ],
-            [
-                DocumentType.VID,
-                {
-                    layout: { view: VideoBox, dataField: defaultDataKey },
-                    options: { acl: '', _layout_currentTimecode: 0, systemIcon: 'BsFileEarmarkPlayFill' },
-                },
-            ],
-            [
-                DocumentType.AUDIO,
-                {
-                    layout: { view: AudioBox, dataField: defaultDataKey },
-                    options: { acl: '', _height: 100, layout_fitWidth: true, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillVolumeUpFill' },
-                },
-            ],
-            [
-                DocumentType.REC,
-                {
-                    layout: { view: VideoBox, dataField: defaultDataKey },
-                    options: { acl: '', _height: 100, backgroundColor: 'pink', systemIcon: 'BsFillMicFill' },
-                },
-            ],
-            [
-                DocumentType.PDF,
-                {
-                    layout: { view: PDFBox, dataField: defaultDataKey },
-                    options: { acl: '', _layout_curPage: 1, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, systemIcon: 'BsFileEarmarkPdfFill' },
-                },
-            ],
-            [
-                DocumentType.MAP,
-                {
-                    layout: { view: MapBox, dataField: defaultDataKey },
-                    options: { acl: '', map: '', _height: 600, _width: 800, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillPinMapFill' },
-                },
-            ],
-            [
-                DocumentType.LINK,
+                DocumentType.GROUPDB,
                 {
-                    layout: { view: LinkBox, dataField: 'link' },
-                    options: {
-                        acl: '',
-                        childDontRegisterViews: true,
-                        layout_hideLinkAnchors: true,
-                        _height: 1,
-                        _width: 1,
-                        link: '',
-                        link_description: '',
-                        color: 'lightBlue', // lightblue is default color for linking dot and link documents text comment area
-                        _dropPropertiesToRemove: new List(['onClick']),
-                    },
+                    layout: { view: EmptyBox, dataField: defaultDataKey },
+                    options: { acl: '', title: 'Global Group Database' },
                 },
             ],
             [
@@ -652,175 +503,18 @@ export namespace Docs {
                     options: { acl: '', title: 'Global Script Database' },
                 },
             ],
-            [
-                DocumentType.SCRIPTING,
-                {
-                    layout: { view: ScriptingBox, dataField: defaultDataKey },
-                    options: { acl: '', systemIcon: 'BsFileEarmarkCodeFill' },
-                },
-            ],
-            [
-                DocumentType.LABEL,
-                {
-                    layout: { view: LabelBox, dataField: 'title' },
-                    options: { acl: '', _singleLine: true, layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true },
-                },
-            ],
-            [
-                DocumentType.EQUATION,
-                {
-                    layout: { view: EquationBox, dataField: 'text' },
-                    options: { acl: '', fontSize: '14px', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, // systemIcon: 'BsSuperscript' + BsSubscript
-                },
-            ],
-            [
-                DocumentType.FUNCPLOT,
-                {
-                    layout: { view: FunctionPlotBox, dataField: defaultDataKey },
-                    options: { acl: '', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true },
-                },
-            ],
-            [
-                DocumentType.BUTTON,
-                {
-                    layout: { view: LabelBox, dataField: 'title' },
-                    options: { acl: '', layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true },
-                },
-            ],
-            [
-                DocumentType.PRES,
-                {
-                    layout: { view: PresBox, dataField: defaultDataKey },
-                    options: { acl: '', defaultDoubleClick: 'ignore', hideClickBehaviors: true, layout_hideLinkAnchors: true },
-                },
-            ],
-            [
-                DocumentType.FONTICON,
-                {
-                    layout: { view: FontIconBox, dataField: 'icon' },
-                    options: { acl: '', defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideContextMenu: true, layout_hideLinkButton: true, _width: 40, _height: 40 },
-                },
-            ],
-            [
-                DocumentType.WEBCAM,
-                {
-                    layout: { view: RecordingBox, dataField: defaultDataKey },
-                    options: { acl: '', systemIcon: 'BsFillCameraVideoFill' },
-                },
-            ],
-            [
-                DocumentType.PRESELEMENT,
-                {
-                    layout: { view: PresElementBox, dataField: defaultDataKey },
-                    options: { acl: '', title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' },
-                },
-            ],
+
             [
                 DocumentType.CONFIG,
-                {
-                    layout: { view: CollectionView, dataField: defaultDataKey },
-                    options: { acl: '', config: '', layout_hideLinkButton: true, layout_unrendered: true },
-                },
-            ],
-            [
-                DocumentType.INK,
-                {
-                    // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
-                    layout: { view: InkingStroke, dataField: 'stroke' },
-                    options: {
-                        acl: '',
-                        systemIcon: 'BsFillPencilFill', //
-                        layout_nativeDimEditable: true,
-                        layout_reflowVertical: true,
-                        layout_reflowHorizontal: true,
-                        layout_hideDecorationTitle: true, // don't show title when selected
-                        fitWidth: false,
-                        layout_isSvg: true,
-                    },
-                },
-            ],
-            [
-                DocumentType.SCREENSHOT,
-                {
-                    layout: { view: ScreenshotBox, dataField: defaultDataKey },
-                    options: { acl: '', layout_nativeDimEditable: true, systemIcon: 'BsCameraFill' },
-                },
-            ],
-            [
-                DocumentType.COMPARISON,
-                {
-                    data: '',
-                    layout: { view: ComparisonBox, dataField: defaultDataKey },
-                    options: {
-                        acl: '',
-                        backgroundColor: 'gray',
-                        dropAction: dropActionType.move,
-                        waitForDoubleClickToClick: 'always',
-                        layout_reflowHorizontal: true,
-                        layout_reflowVertical: true,
-                        layout_nativeDimEditable: true,
-                        systemIcon: 'BsLayoutSplit',
-                    },
-                },
-            ],
-            [
-                DocumentType.GROUPDB,
                 {
                     layout: { view: EmptyBox, dataField: defaultDataKey },
-                    options: { acl: '', title: 'Global Group Database' },
-                },
-            ],
-            [
-                DocumentType.DATAVIZ,
-                {
-                    layout: { view: DataVizBox, dataField: defaultDataKey },
-                    options: {
-                        acl: '',
-                        dataViz_title: '',
-                        dataViz_line: '',
-                        dataViz_pie: '',
-                        dataViz_histogram: '',
-                        dataViz: 'table',
-                        _layout_fitWidth: true,
-                        layout_reflowHorizontal: true,
-                        layout_reflowVertical: true,
-                        layout_nativeDimEditable: true,
-                    },
-                },
-            ],
-            [
-                DocumentType.LOADING,
-                {
-                    layout: { view: LoadingBox, dataField: '' },
-                    options: { acl: '', _layout_fitWidth: true, _fitHeight: true, layout_nativeDimEditable: true },
-                },
-            ],
-            [
-                DocumentType.SIMULATION,
-                {
-                    data: '',
-                    layout: { view: PhysicsSimulationBox, dataField: defaultDataKey, _width: 1000, _height: 800 },
-                    options: { acl: '', _height: 100, mass1: '', mass2: '', layout_nativeDimEditable: true, position: '', acceleration: '', pendulum: '', spring: '', wedge: '', simulation: '', review: '', systemIcon: 'BsShareFill' },
-                },
-            ],
-            [
-                DocumentType.PUSHPIN,
-                {
-                    layout: { view: MapPushpinBox, dataField: defaultDataKey },
-                    options: { acl: '' },
+                    options: { acl: '', config: '', layout_hideLinkButton: true, layout_unrendered: true },
                 },
             ],
             [
                 DocumentType.MAPROUTE,
                 {
-                    layout: { view: CollectionView, dataField: defaultDataKey },
-                    options: { acl: '' },
-                },
-            ],
-            [
-                DocumentType.CALENDAR,
-                {
-                    layout: { view: CalendarBox, dataField: defaultDataKey },
+                    layout: { view: EmptyBox, dataField: defaultDataKey },
                     options: { acl: '' },
                 },
             ],
@@ -1005,7 +699,7 @@ export namespace Docs {
                 }
                 Doc.assign(viewDoc, viewProps, true, true);
                 if (![DocumentType.LINK, DocumentType.CONFIG, DocumentType.LABEL].includes(viewDoc.type as any)) {
-                    DocUtils.MakeLinkToActiveAudio(() => viewDoc);
+                    CreateLinkToActiveAudio(() => viewDoc);
                 }
                 updateCachedAcls(dataDoc);
                 updateCachedAcls(viewDoc);
@@ -1039,7 +733,7 @@ export namespace Docs {
          */
         // eslint-disable-next-line default-param-last
         export function ScriptingDocument(script: Opt<ScriptField> | null, options: DocumentOptions = {}, fieldKey?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script || undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined });
+            return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script || undefined, { ...options, layout: fieldKey ? `<ScriptingBox {...props} fieldKey={'${fieldKey}'}/>` /* ScriptingBox.LayoutString(fieldKey) */ : undefined });
         }
 
         // eslint-disable-next-line default-param-last
@@ -1309,34 +1003,6 @@ export namespace Docs {
             return ret;
         }
 
-        export type DocConfig = {
-            doc: Doc;
-            initialWidth?: number;
-            path?: Doc[];
-        };
-
-        export function StandardCollectionDockingDocument(configs: Array<DocConfig>, options: DocumentOptions, id?: string, type: string = 'row') {
-            const layoutConfig = {
-                content: [
-                    {
-                        type: type,
-                        content: [...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, undefined, config.initialWidth))],
-                    },
-                ],
-            };
-            const doc = DockDocument(
-                configs.map(c => c.doc),
-                JSON.stringify(layoutConfig),
-                ClientUtils.CurrentUserEmail() === 'guest' ? options : { acl_Guest: SharingPermissions.View, ...options },
-                id
-            );
-            configs.forEach(c => {
-                Doc.SetContainer(c.doc, doc);
-                inheritParentAcls(doc, c.doc, false);
-            });
-            return doc;
-        }
-
         export function DelegateDocument(proto: Doc, options: DocumentOptions = {}) {
             return InstanceFromProto(proto, undefined, options);
         }
@@ -1346,879 +1012,3 @@ export namespace Docs {
         }
     }
 }
-
-export namespace DocUtils {
-    function matchFieldValue(doc: Doc, key: string, valueIn: any): boolean {
-        let value = valueIn;
-        const hasFunctionFilter = ClientUtils.HasFunctionFilter(value);
-        if (hasFunctionFilter) {
-            return hasFunctionFilter(StrCast(doc[key]));
-        }
-        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 = (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
-            return  (value === Doc.FilterNone && !allLinks.length) || 
-                    (value === Doc.FilterAny && !!allLinks.length) ||
-                    (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || 
-                                           matchLink(value,DocCast(link.link_anchor_2)) ));
-        }
-        if (typeof value === 'string') {
-            value = value.replace(`,${ClientUtils.noRecursionHack}`, '');
-        }
-        const fieldVal = doc[key];
-        // prettier-ignore
-        if ((value === Doc.FilterAny && fieldVal !== undefined) || 
-            (value === Doc.FilterNone && fieldVal === undefined)) {
-            return true;
-        }
-        const vals = StrListCast(fieldVal); // list typing is very imperfect.  casting to a string list doesn't mean that the entries will actually be strings
-        if (vals.length) {
-            return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
-        }
-        return Field.toString(fieldVal as FieldType).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
-    }
-    /**
-     * @param docs
-     * @param childFilters
-     * @param childFiltersByRanges
-     * @param parentCollection
-     * Given a list of docs and childFilters, @returns the list of Docs that match those filters
-     */
-    export function FilterDocs(childDocs: Doc[], childFilters: string[], childFiltersByRanges: string[], parentCollection?: Doc) {
-        if (!childFilters?.length && !childFiltersByRanges?.length) {
-            return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one
-        }
-
-        const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields
-        childFilters.forEach(filter => {
-            const fields = filter.split(Doc.FilterSep);
-            const key = fields[0];
-            const value = fields[1];
-            const modifiers = fields[2];
-            if (!filterFacets[key]) {
-                filterFacets[key] = {};
-            }
-            filterFacets[key][value] = modifiers;
-        });
-
-        const filteredDocs = childFilters.length
-            ? childDocs.filter(d => {
-                  if (d.z) return true;
-                  // if the document needs a cookie but no filter provides the cookie, then the document does not pass the filter
-                  if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) {
-                      return false;
-                  }
-                  const facetKeys = Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== ClientUtils.noDragDocsFilter.split(Doc.FilterSep)[0]);
-                  // eslint-disable-next-line no-restricted-syntax
-                  for (const facetKey of facetKeys) {
-                      const facet = filterFacets[facetKey];
-
-                      // facets that match some value in the field of the document (e.g. some text field)
-                      const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match');
-
-                      // facets that have a check next to them
-                      const checks = Object.keys(facet).filter(value => facet[value] === 'check');
-
-                      // metadata facets that exist
-                      const exists = Object.keys(facet).filter(value => facet[value] === 'exists');
-
-                      // facets that unset metadata (a hack for making cookies work)
-                      const unsets = Object.keys(facet).filter(value => facet[value] === 'unset');
-
-                      // facets that specify that a field must not match a specific value
-                      const xs = Object.keys(facet).filter(value => facet[value] === 'x');
-
-                      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 satisfiesUnsetsFacets = !unsets.length ? true : d[facetKey] === undefined;
-                      const satisfiesMatchFacets = !matches.length
-                          ? true
-                          : matches.some(value => {
-                                if (facetKey.startsWith('*')) {
-                                    //  fields starting with a '*' are used to match families of related fields.  ie, *modificationDate will match text_modificationDate, data_modificationDate, etc
-                                    const allKeys = Array.from(Object.keys(d));
-                                    allKeys.push(...Object.keys(Doc.GetProto(d)));
-                                    const keys = allKeys.filter(key => key.includes(facetKey.substring(1)));
-                                    return keys.some(key => Field.toString(d[key] as FieldType).includes(value));
-                                }
-                                return Field.toString(d[facetKey] as FieldType).includes(value);
-                            });
-                      // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
-                      if (parentCollection?.childFilters_boolean === 'OR') {
-                          if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
-                      }
-                      // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
-                      else if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
-                  }
-                  return parentCollection?.childFilters_boolean !== 'OR';
-              })
-            : childDocs;
-        const rangeFilteredDocs = filteredDocs.filter(d => {
-            for (let i = 0; i < childFiltersByRanges.length; i += 3) {
-                const key = childFiltersByRanges[i];
-                const min = Number(childFiltersByRanges[i + 1]);
-                const max = Number(childFiltersByRanges[i + 2]);
-                const val = typeof d[key] === 'string' ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], 'number', null);
-                if (val === undefined) {
-                    // console.log("Should 'undefined' pass range filter or not?")
-                } else if (val < min || val > max) return false;
-            }
-            return true;
-        });
-        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' });
-        });
-    }
-
-    export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string; link_displayLine?: boolean }, 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;
-
-        const makeLink = action((linkDoc: Doc, showAt?: number[]) => {
-            if (showAt) {
-                LinkManager.Instance.currentLink = linkDoc;
-
-                TaskCompletionBox.textDisplayed = 'Link Created';
-                TaskCompletionBox.popupX = showAt[0];
-                TaskCompletionBox.popupY = showAt[1] - 33;
-                TaskCompletionBox.taskCompleted = true;
-
-                LinkDescriptionPopup.Instance.popupX = showAt[0];
-                LinkDescriptionPopup.Instance.popupY = showAt[1];
-                LinkDescriptionPopup.Instance.display = true;
-
-                const rect = document.body.getBoundingClientRect();
-                if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
-                    LinkDescriptionPopup.Instance.popupX -= 190;
-                    TaskCompletionBox.popupX -= 40;
-                }
-                if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
-                    LinkDescriptionPopup.Instance.popupY -= 40;
-                    TaskCompletionBox.popupY -= 40;
-                }
-
-                setTimeout(
-                    action(() => {
-                        TaskCompletionBox.taskCompleted = false;
-                    }),
-                    2500
-                );
-            }
-            return linkDoc;
-        });
-
-        const a = source.layout_unrendered ? 'link_anchor_1?.annotationOn' : 'link_anchor_1';
-        const b = target.layout_unrendered ? 'link_anchor_2?.annotationOn' : 'link_anchor_2';
-
-        return makeLink(
-            Docs.Create.LinkDocument(
-                source,
-                target,
-                {
-                    acl_Guest: SharingPermissions.Augment,
-                    _acl_Guest: SharingPermissions.Augment,
-                    title: ComputedField.MakeFunction('generateLinkTitle(this)') as any,
-                    link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined,
-                    link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined,
-                    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,
-                    y: ComputedField.MakeFunction(`((this.${a}?.y||0)+(this.${b}?.y||0))/2`) as any,
-                    link_autoMoveAnchors: true,
-                    _lockedPosition: true,
-                    _layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box)
-                    _layout_showTitle: '',
-                    // _layout_showCaption: 'link_description',
-                    // _layout_showTitle: 'link_relationship',
-                },
-                id
-            ),
-            showPopup
-        );
-    }
-
-    export function AssignScripts(doc: Doc, scripts?: { [key: string]: string | undefined }, funcs?: { [key: string]: string }) {
-        scripts &&
-            Object.keys(scripts).forEach(key => {
-                const script = scripts[key];
-                if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) {
-                    (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, {
-                        self: Doc.name,
-                        this: Doc.name,
-                        dragData: DragManager.DocumentDragData.name,
-                        value: 'any',
-                        _readOnly_: 'boolean',
-                        scriptContext: 'any',
-                        documentView: Doc.name,
-                        heading: Doc.name,
-                        checked: 'boolean',
-                        containingTreeView: Doc.name,
-                        altKey: 'boolean',
-                        ctrlKey: 'boolean',
-                        shiftKey: 'boolean',
-                    });
-                }
-            });
-        funcs &&
-            Object.keys(funcs)
-                .filter(key => !key.endsWith('-setter'))
-                .forEach(key => {
-                    const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-                    if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) {
-                        const setFunc = Cast(funcs[key + '-setter'], 'string', null);
-                        (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }, setFunc) : undefined;
-                    }
-                });
-        return doc;
-    }
-    export function AssignOpts(doc: Doc | undefined, reqdOpts: DocumentOptions, items?: Doc[]) {
-        if (doc) {
-            const compareValues = (val1: any, val2: any) => {
-                if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) {
-                    return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v));
-                }
-                return val1 === val2;
-            };
-            Object.entries(reqdOpts).forEach(pair => {
-                const targetDoc = pair[0].startsWith('_') ? doc : Doc.GetProto(doc as Doc);
-                if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/, '')) || !compareValues(pair[1], targetDoc[pair[0]])) {
-                    targetDoc[pair[0]] = pair[1];
-                }
-            });
-            items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item));
-            items && DocListCast(doc.data).forEach(item => Doc.IsSystem(item) && !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item));
-        }
-        return doc;
-    }
-    export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) {
-        // eslint-disable-next-line no-return-assign
-        return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs);
-    }
-
-    export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined {
-        let created: Doc | undefined;
-        const field = target[fieldKey];
-        const resolved = options ?? {};
-        if (field instanceof ImageField) {
-            created = Docs.Create.ImageDocument(field.url.href, resolved);
-            created.layout = ImageBox.LayoutString(fieldKey);
-        } else if (field instanceof Doc) {
-            created = field;
-        } else if (field instanceof VideoField) {
-            created = Docs.Create.VideoDocument(field.url.href, resolved);
-            created.layout = VideoBox.LayoutString(fieldKey);
-        } else if (field instanceof PdfField) {
-            created = Docs.Create.PdfDocument(field.url.href, resolved);
-            created.layout = PDFBox.LayoutString(fieldKey);
-        } else if (field instanceof AudioField) {
-            created = Docs.Create.AudioDocument(field.url.href, resolved);
-            created.layout = AudioBox.LayoutString(fieldKey);
-        } else if (field instanceof InkField) {
-            created = Docs.Create.InkDocument(field.inkData, resolved);
-            created.layout = InkingStroke.LayoutString(fieldKey);
-        } else if (field instanceof List && field[0] instanceof Doc) {
-            created = Docs.Create.StackingDocument(DocListCast(field), resolved);
-            created.layout = CollectionView.LayoutString(fieldKey);
-        } else {
-            created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _layout_autoHeight: true }, ...resolved });
-            created.layout = FormattedTextBox.LayoutString(fieldKey);
-        }
-        if (created) {
-            created.title = fieldKey;
-            proto && created.proto && (created.proto = Doc.GetProto(proto));
-        }
-        return created;
-    }
-
-    /**
-     *
-     * @param type the type of file.
-     * @param path the path to the file.
-     * @param options the document options.
-     * @param overwriteDoc the placeholder loading doc.
-     * @returns
-     */
-    export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise<Opt<Doc>> {
-        let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined;
-        if (type.indexOf('image') !== -1) {
-            ctor = Docs.Create.ImageDocument;
-            if (!options._width) options._width = 300;
-        }
-        if (type.indexOf('video') !== -1) {
-            ctor = Docs.Create.VideoDocument;
-            if (!options._width) options._width = 600;
-            if (!options._height) options._height = ((options._width as number) * 2) / 3;
-        }
-        if (type.indexOf('audio') !== -1) {
-            ctor = Docs.Create.AudioDocument;
-        }
-        if (type.indexOf('pdf') !== -1) {
-            ctor = Docs.Create.PdfDocument;
-            if (!options._width) options._width = 400;
-            if (!options._height) options._height = ((options._width as number) * 1200) / 927;
-        }
-        if (type.indexOf('csv') !== -1) {
-            ctor = Docs.Create.DataVizDocument;
-            if (!options._width) options._width = 400;
-            if (!options._height) options._height = ((options._width as number) * 1200) / 927;
-        }
-        // TODO:al+glr
-        // if (type.indexOf("map") !== -1) {
-        //     ctor = Docs.Create.MapDocument;
-        //     if (!options._width) options._width = 800;
-        //     if (!options._height) options._height = (options._width as number) * 3 / 4;
-        // }
-        if (type.indexOf('html') !== -1) {
-            if (path.includes(window.location.hostname)) {
-                const s = path.split('/');
-                const id = s[s.length - 1];
-                return DocServer.GetRefField(id).then(field => {
-                    if (field instanceof Doc) {
-                        const embedding = Doc.MakeEmbedding(field);
-                        embedding.x = (options.x as number) || 0;
-                        embedding.y = (options.y as number) || 0;
-                        embedding._width = (options._width as number) || 300;
-                        embedding._height = (options._height as number) || (options._width as number) || 300;
-                        return embedding;
-                    }
-                    return undefined;
-                });
-            }
-            ctor = Docs.Create.WebDocument;
-            // eslint-disable-next-line no-param-reassign
-            options = { ...options, _width: 400, _height: 512, title: path };
-        }
-
-        return ctor ? ctor(path, overwriteDoc ? { ...options, title: StrCast(overwriteDoc.title, path) } : options, overwriteDoc) : undefined;
-    }
-
-    export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void {
-        const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data)
-            .filter(btnDoc => !btnDoc.hidden)
-            .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
-            .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title)
-            .map(dragDoc => ({
-                description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
-                event: undoable(() => {
-                    const newDoc = DocUtils.copyDragFactory(dragDoc);
-                    if (newDoc) {
-                        newDoc.author = ClientUtils.CurrentUserEmail();
-                        newDoc.x = x;
-                        newDoc.y = y;
-                        EquationBox.SelectOnLoad = newDoc[Id];
-                        if (newDoc.type === DocumentType.RTF) FormattedTextBox.SetSelectOnLoad(newDoc);
-                        if (pivotField) {
-                            newDoc[pivotField] = pivotValue;
-                        }
-                        docAdder?.(newDoc);
-                    }
-                }, StrCast(dragDoc.title)),
-                icon: Doc.toIcon(dragDoc),
-            })) as ContextMenuProps[];
-        ContextMenu.Instance.addItem({
-            description: 'Create document',
-            subitems: documentList,
-            icon: 'file',
-        });
-        !simpleMenu &&
-            ContextMenu.Instance.addItem({
-                description: 'Styled Notes',
-                subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map(note => ({
-                    description: ':' + StrCast(note.title),
-                    event: undoable(() => {
-                        const textDoc = Docs.Create.TextDocument('', {
-                            _width: 200,
-                            x,
-                            y,
-                            _layout_autoHeight: note._layout_autoHeight !== false,
-                            title: StrCast(note.title) + '#' + (note.embeddingCount = NumCast(note.embeddingCount) + 1),
-                        });
-                        textDoc.layout_fieldKey = 'layout_' + note.title;
-                        textDoc[textDoc.layout_fieldKey] = note;
-                        if (pivotField) {
-                            textDoc[pivotField] = pivotValue;
-                        }
-                        docTextAdder(textDoc);
-                    }, 'create quick note'),
-                    icon: StrCast(note.icon) as IconProp,
-                })) as ContextMenuProps[],
-                icon: 'sticky-note',
-            });
-        const userDocList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[1]?.data)
-            .filter(btnDoc => !btnDoc.hidden)
-            .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
-            .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title)
-            .map(dragDoc => ({
-                description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
-                event: undoable(() => {
-                    const newDoc = DocUtils.delegateDragFactory(dragDoc);
-                    if (newDoc) {
-                        newDoc.author = ClientUtils.CurrentUserEmail();
-                        newDoc.x = x;
-                        newDoc.y = y;
-                        EquationBox.SelectOnLoad = newDoc[Id];
-                        if (newDoc.type === DocumentType.RTF) FormattedTextBox.SetSelectOnLoad(newDoc);
-                        if (pivotField) {
-                            newDoc[pivotField] = pivotValue;
-                        }
-                        docAdder?.(newDoc);
-                    }
-                }, StrCast(dragDoc.title)),
-                icon: Doc.toIcon(dragDoc),
-            })) as ContextMenuProps[];
-        ContextMenu.Instance.addItem({
-            description: 'User Templates',
-            subitems: userDocList,
-            icon: 'file',
-        });
-    }
-
-    // applies a custom template to a document.  the template is identified by it's short name (e.g, slideView not layout_slideView)
-
-    /**
-     * Applies a template to a Doc and logs the action with the UndoManager
-     * If the template already exists and has been registered, it can be specified by it's signature name (e.g., 'icon' not 'layout_icon').
-     * Alternatively, the signature can be omitted and the template can be provided.
-     * @param doc the Doc to apply the template to.
-     * @param creator a function that will create the template if it doesn't exist
-     * @param templateSignature the signature name for a template that has already been created and registered on the userDoc.  (can be "" if template is provide)
-     * @param template the template to use (optional if templateSignature is provided)
-     * @returns doc
-     */
-    export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', template?: Doc) {
-        const batch = UndoManager.StartBatch('makeCustomViewClicked');
-        createCustomView(doc, creator, templateSignature || StrCast(template?.title), template);
-        batch.end();
-        return doc;
-    }
-    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);
-        const noteTypes = DocListCast(Cast(Doc.UserDoc().template_notes, Doc, null)?.data);
-        const userTypes = DocListCast(Cast(Doc.UserDoc().template_user, Doc, null)?.data);
-        const clickFuncs = DocListCast(Cast(Doc.UserDoc().template_clickFuncs, Doc, null)?.data);
-        const allTemplates = iconViews
-            .concat(templBtns)
-            .concat(noteTypes)
-            .concat(userTypes)
-            .concat(clickFuncs)
-            .map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc)
-            .filter(doc => doc.isTemplateDoc);
-        // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document.  but type is not reliable and there could be other types of template searches so this should be generalized
-        // first try to find a template that matches the specific document type (<typeName>_<templateName>).  otherwise, fallback to a general match on <templateName>
-        !docLayoutTemplate &&
-            allTemplates.forEach(tempDoc => {
-                StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc);
-            });
-        !docLayoutTemplate &&
-            allTemplates.forEach(tempDoc => {
-                StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc);
-            });
-        return docLayoutTemplate;
-    }
-    export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) {
-        const templateName = templateSignature.replace(/\(.*\)/, '');
-        doc.layout_fieldKey = 'layout_' + (templateSignature || (docLayoutTemplate?.title ?? ''));
-        // eslint-disable-next-line no-param-reassign
-        docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type));
-
-        const customName = 'layout_' + templateSignature;
-        const _width = NumCast(doc._width);
-        const _height = NumCast(doc._height);
-        const options = { title: 'data', backgroundColor: StrCast(doc.backgroundColor), _layout_autoHeight: true, _width, x: -_width / 2, y: -_height / 2, _layout_showSidebar: false };
-
-        if (docLayoutTemplate) {
-            if (docLayoutTemplate !== doc[customName]) {
-                Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined);
-            }
-        } else {
-            let fieldTemplate: Opt<Doc>;
-            if (doc.data instanceof RichTextField || typeof doc.data === 'string') {
-                fieldTemplate = Docs.Create.TextDocument('', options);
-            } else if (doc.data instanceof PdfField) {
-                fieldTemplate = Docs.Create.PdfDocument('http://www.msn.com', options);
-            } else if (doc.data instanceof VideoField) {
-                fieldTemplate = Docs.Create.VideoDocument('http://www.cs.brown.edu', options);
-            } else if (doc.data instanceof AudioField) {
-                fieldTemplate = Docs.Create.AudioDocument('http://www.cs.brown.edu', options);
-            } else if (doc.data instanceof ImageField) {
-                fieldTemplate = Docs.Create.ImageDocument('http://www.cs.brown.edu', options);
-            }
-            const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + '(' + doc.title + ')', isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
-            fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
-            docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
-        }
-    }
-    export function makeCustomView(doc: Doc, custom: boolean, layout: string) {
-        Doc.setNativeView(doc);
-        if (custom) {
-            makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined);
-        }
-    }
-    export function iconify(doc: Doc) {
-        const layoutFieldKey = Cast(doc.layout_fieldKey, 'string', null);
-        DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined);
-        if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') doc.deiconifyLayout = layoutFieldKey.replace('layout_', '');
-    }
-
-    export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) {
-        runInAction(() => {
-            docList.forEach((doc, i) => {
-                const d = doc;
-                DocUtils.iconify(d);
-                d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size - size;
-                d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size - size;
-                d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-            });
-        });
-        if (create) {
-            const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', _freeform_noZoom: true, x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true, _layout_fitWidth: false });
-            newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size;
-            newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size;
-            newCollection._width = newCollection._height = size * 2;
-            return newCollection;
-        }
-        return undefined;
-    }
-    export function makeIntoPortal(doc: Doc, layoutDoc: Doc, allLinks: Doc[]) {
-        const portalLink = allLinks.find(d => d.link_anchor_1 === doc && d.link_relationship === 'portal to:portal from');
-        if (!portalLink) {
-            DocUtils.MakeLink(
-                doc,
-                Docs.Create.FreeformDocument([], {
-                    _width: NumCast(layoutDoc._width) + 10,
-                    _height: Math.max(NumCast(layoutDoc._height), NumCast(layoutDoc._width) + 10),
-                    _isLightbox: true,
-                    _layout_fitWidth: true,
-                    title: StrCast(doc.title) + ' [Portal]',
-                }),
-                { link_relationship: 'portal to:portal from' }
-            );
-        }
-        doc.followLinkLocation = OpenWhere.lightbox;
-        doc.onClick = FollowLinkScript();
-    }
-
-    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));
-        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: '',
-                annotationOn: Cast(doc.annotationOn, Doc, null),
-                followLinkToggle: true,
-                icon: 'map-pin',
-                x: Cast(doc.x, 'number', null),
-                y: Cast(doc.y, 'number', null),
-                backgroundColor: '#ACCEF7',
-                layout_hideAllLinks: true,
-                _width: 15,
-                _height: 15,
-                _xPadding: 0,
-                onClick: FollowLinkScript(),
-                _timecodeToShow: Cast(doc._timecodeToShow, 'number', null),
-            });
-            Doc.AddDocToList(context, annotationField, pushpin);
-            DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, '');
-            doc._timecodeToShow = undefined;
-            return pushpin;
-        }
-        return undefined;
-    }
-
-    // /**
-    //  *
-    //  * @param dms Degree Minute Second format exif gps data
-    //  * @param ref ref that determines negativity of decimal coordinates
-    //  * @returns a decimal format of gps latitude / longitude
-    //  */
-    // function getDecimalfromDMS(dms?: number[], ref?: string) {
-    //     if (dms && ref) {
-    //         let degrees = dms[0] / dms[1];
-    //         let minutes = dms[2] / dms[3] / 60.0;
-    //         let seconds = dms[4] / dms[5] / 3600.0;
-
-    //         if (['S', 'W'].includes(ref)) {
-    //             degrees = -degrees; minutes = -minutes; seconds = -seconds
-    //         }
-    //         return (degrees + minutes + seconds).toFixed(5);
-    //     }
-    // }
-
-    function ConvertDMSToDD(degrees: number, minutes: number, seconds: number, direction: string) {
-        let dd = degrees + minutes / 60 + seconds / (60 * 60);
-        if (direction === 'S' || direction === 'W') {
-            dd *= -1;
-        } // Don't do anything for N or E
-        return dd;
-    }
-
-    export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) {
-        const proto = protoIn;
-        if (Upload.isImageInformation(result)) {
-            const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim);
-            const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase();
-            proto.data_nativeOrientation = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined);
-            proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
-            proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
-            if (NumCast(proto.data_nativeOrientation) >= 5) {
-                proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
-                proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
-            }
-            proto.data_exif = JSON.stringify(result.exifData?.data);
-            proto.data_contentSize = result.contentSize;
-            // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
-            const latitude = result.exifData?.data?.GPSLatitude;
-            const latitudeDirection = result.exifData?.data?.GPSLatitudeRef;
-            const longitude = result.exifData?.data?.GPSLongitude;
-            const longitudeDirection = result.exifData?.data?.GPSLongitudeRef;
-            if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) {
-                proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection);
-                proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection);
-            }
-        }
-    }
-
-    async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) {
-        if (result instanceof Error) {
-            alert(`Upload failed: ${result.message}`);
-            return;
-        }
-        const full = { ...options, _width: 400, title: name };
-        const pathname = result.accessPaths.agnostic.client;
-        const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc);
-        if (doc) {
-            const proto = Doc.GetProto(doc);
-            proto.text = result.rawText;
-            !(result instanceof Error) && DocUtils.assignImageInfo(result, proto);
-            if (Upload.isVideoInformation(result)) {
-                proto.data_duration = result.duration;
-            }
-            if (overwriteDoc) {
-                LoadingBox.removeCurrentlyLoading(overwriteDoc);
-            }
-            generatedDocuments.push(doc);
-        }
-    }
-
-    export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) {
-        const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout);
-        const tbox = Docs.Create.TextDocument('', {
-            annotationOn,
-            backgroundColor,
-            x,
-            y,
-            title,
-            ...(defaultTextTemplate
-                ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance
-                : {
-                      _width: width || 200,
-                      _height: 35,
-                      _layout_centered: BoolCast(Doc.UserDoc()._layout_centered),
-                      _layout_fitWidth: true,
-                      _layout_autoHeight: true,
-                  }),
-        });
-
-        if (defaultTextTemplate) {
-            tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title);
-            Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = defaultTextTemplate; // set the text doc's layout to render with the text template
-            tbox[DocData].proto = defaultTextTemplate; // and also set the text doc to inherit from the template (this allows the template to specify default field values)
-        }
-        return tbox;
-    }
-
-    export function uploadYoutubeVideoLoading(videoId: string, options: DocumentOptions, overwriteDoc?: Doc) {
-        const generatedDocuments: Doc[] = [];
-        Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => {
-            const {
-                source: { newFilename, mimetype },
-                result,
-            } = upfiles.lastElement();
-            if ((result as any).message) {
-                if (overwriteDoc) {
-                    overwriteDoc.isLoading = false;
-                    overwriteDoc.loadingError = (result as any).message;
-                    LoadingBox.removeCurrentlyLoading(overwriteDoc);
-                }
-            } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype ?? '', result, options, overwriteDoc);
-        });
-    }
-
-    /**
-     * uploadFilesToDocs will take in an array of Files, and creates documents for the
-     * new files.
-     *
-     * @param files an array of files that will be uploaded
-     * @param options options to use while uploading
-     * @returns
-     */
-    export async function uploadFilesToDocs(files: File[], options: DocumentOptions) {
-        const generatedDocuments: Doc[] = [];
-
-        // These files do not have overwriteDocs, so we do not set the guid and let the client generate one.
-        const fileNoGuidPairs: Networking.FileGuidPair[] = files.map(file => ({ file }));
-
-        const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs);
-        upfiles.forEach(({ source: { newFilename, mimetype }, result }) => {
-            newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options);
-        });
-        return generatedDocuments;
-    }
-
-    export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) {
-        const generatedDocuments: Doc[] = [];
-        // Since this file has an overwriteDoc, we can set the client tracking guid to the overwriteDoc's guid.
-        Networking.UploadFilesToServer([{ file, guid: overwriteDoc[Id] }]).then(upfiles => {
-            const {
-                source: { newFilename, mimetype },
-                result,
-            } = upfiles.lastElement() ?? { source: { newFilename: '', mimetype: '' }, result: { message: 'upload failed' } };
-            if ((result as any).message) {
-                if (overwriteDoc) {
-                    overwriteDoc.loadingError = (result as any).message;
-                    LoadingBox.removeCurrentlyLoading(overwriteDoc);
-                }
-            } else newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc);
-        });
-    }
-
-    // copies the specified drag factory document
-    export function copyDragFactory(dragFactory: Doc) {
-        if (!dragFactory) return undefined;
-        const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true);
-        if (ndoc && dragFactory.dragFactory_count !== undefined) {
-            dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1;
-            Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString(), true);
-        }
-
-        return ndoc;
-    }
-    export function delegateDragFactory(dragFactory: Doc) {
-        const ndoc = Doc.MakeDelegateWithProto(dragFactory);
-        if (ndoc && dragFactory.dragFactory_count !== undefined) {
-            dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1;
-            Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString();
-        }
-        return ndoc;
-    }
-
-    export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
-        const { clone, map, linkMap } = await Doc.MakeClone(doc);
-        const proms = new Set<string>();
-        function replacer(key: any, value: any) {
-            if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
-            if (value?.__type === 'image') {
-                const extension = value.url.replace(/.*\./, '');
-                proms.add(value.url.replace('.' + extension, '_o.' + extension));
-                return SerializationHelper.Serialize(new ImageField(value.url));
-            }
-            if (value?.__type === 'pdf') {
-                proms.add(value.url);
-                return SerializationHelper.Serialize(new PdfField(value.url));
-            }
-            if (value?.__type === 'audio') {
-                proms.add(value.url);
-                return SerializationHelper.Serialize(new AudioField(value.url));
-            }
-            if (value?.__type === 'video') {
-                proms.add(value.url);
-                return SerializationHelper.Serialize(new VideoField(value.url));
-            }
-            if (
-                value instanceof Doc ||
-                value instanceof ScriptField ||
-                value instanceof RichTextField ||
-                value instanceof InkField ||
-                value instanceof CsvField ||
-                value instanceof WebField ||
-                value instanceof DateField ||
-                value instanceof ProxyField ||
-                value instanceof ComputedField
-            ) {
-                return SerializationHelper.Serialize(value);
-            }
-            if (value instanceof Array && key !== ListFieldName && key !== InkDataFieldName) return { fields: value, __type: 'list' };
-            return value;
-        }
-
-        const docs: { [id: string]: any } = {};
-        const links: { [id: string]: any } = {};
-        Array.from(map.entries()).forEach(f => {
-            docs[f[0]] = f[1];
-        });
-        Array.from(linkMap.entries()).forEach(l => {
-            links[l[0]] = l[1];
-        });
-        const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer));
-
-        const zip = new JSZip();
-        let count = 0;
-        const promArr = Array.from(proms)
-            .filter(url => url?.startsWith('/files'))
-            .map(url => url.replace('/', '')); // window.location.origin));
-        console.log(promArr.length);
-        if (!promArr.length) {
-            zip.file('docs.json', jsonDocs);
-            zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
-        } else
-            promArr.forEach((url, i) => {
-                // loading a file and add it in a zip file
-                JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => {
-                    if (err) throw err; // or handle the error
-                    // // Generate a directory within the Zip file structure
-                    // const assets = zip.folder("assets");
-                    // assets.file(filename, data, {binary: true});
-                    const assetPathOnServer = promArr[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%');
-                    zip.file(assetPathOnServer, data, { binary: true });
-                    console.log(' => ' + url);
-                    if (++count === promArr.length) {
-                        zip.file('docs.json', jsonDocs);
-                        zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
-                        // const a = document.createElement("a");
-                        // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
-                        // a.href = url;
-                        // a.download = `DocExport-${this.props.Document[Id]}.zip`;
-                        // a.click();
-                    }
-                });
-            });
-    }
-}
-
-ScriptingGlobals.add('Docs', Docs);
-// eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) {
-    return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory;
-});
-// eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function makeDelegate(proto: any) {
-    const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title });
-    return d;
-});
-// eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function generateLinkTitle(link: Doc) {
-    const linkAnchor1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : '<?>';
-    const linkAnchor2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>';
-    const relation = link.link_relationship || 'to';
-    return `${linkAnchor1title} (${relation}) ${linkAnchor2title}`;
-});
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 00279e7e1..96f5172bc 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -8,28 +8,28 @@ import { List } from "../../fields/List";
 import { PrefetchProxy } from "../../fields/Proxy";
 import { RichTextField } from "../../fields/RichTextField";
 import { listSpec } from "../../fields/Schema";
-import { ScriptField } from "../../fields/ScriptField";
+import { FollowLinkScript, ScriptField } from "../../fields/ScriptField";
 import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types";
 import { WebField, nullAudio } from "../../fields/URLField";
 import { SetCachedGroups, SharingPermissions } from "../../fields/util";
 import { Gestures } from "../../pen-gestures/GestureTypes";
 import { DocServer } from "../DocServer";
+import { DocUtils } from '../documents/DocUtils';
 import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
-import { DocUtils, Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents";
+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 { Colors } from "../views/global/globalEnums";
 import { mediaState } from "../views/nodes/AudioBox";
-import { OpenWhere } from "../views/nodes/DocumentView";
 import { ButtonType, FontIconBox } from "../views/nodes/FontIconBox/FontIconBox";
 import { ImageBox } from "../views/nodes/ImageBox";
 import { LabelBox } from "../views/nodes/LabelBox";
+import { OpenWhere } from "../views/nodes/OpenWhere";
 import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
 import { DragManager } from "./DragManager";
 import { dropActionType } from "./DropActionTypes";
 import { MakeTemplate } from "./DropConverter";
-import { FollowLinkScript } from "./LinkFollower";
 import { LinkManager, UPDATE_SERVER_CACHE } from "./LinkManager";
 import { ScriptingGlobals } from "./ScriptingGlobals";
 import { ColorScheme } from "./SettingsManager";
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 9026b368f..97ee628e2 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -4,6 +4,7 @@ import * as interpreter from 'words-to-numbers';
 import type {} from '@types/dom-speech-recognition';
 import { ClientUtils } from '../../ClientUtils';
 import { Doc, Opt } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
 import { listSpec } from '../../fields/Schema';
@@ -12,10 +13,10 @@ import { AudioField, ImageField } from '../../fields/URLField';
 import { DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
 import { DictationOverlay } from '../views/DictationOverlay';
-import { DocumentView, OpenWhere } from '../views/nodes/DocumentView';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { OpenWhere } from '../views/nodes/OpenWhere';
 import { SelectionManager } from './SelectionManager';
 import { UndoManager } from './UndoManager';
-import { DocData } from '../../fields/DocSymbols';
 
 /**
  * This namespace provides a singleton instance of a manager that
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index a893fe627..8f0c2b0bf 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -10,10 +10,11 @@ import { GetEffectiveAcl } from '../../fields/util';
 import { CollectionViewType } from '../documents/DocumentTypes';
 import { TabDocView } from '../views/collections/TabDocView';
 import { LightboxView } from '../views/LightboxView';
-import { DocumentView, DocumentViewInternal, OpenWhere } from '../views/nodes/DocumentView';
-import { FocusViewOptions } from '../views/nodes/FieldView';
+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';
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 3890b7845..34f54be2e 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -18,14 +18,13 @@ import { action, observable, runInAction } from 'mobx';
 import { ClientUtils } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
 import { DateField } from '../../fields/DateField';
-import { Doc, FieldType, Opt, StrListCast } from '../../fields/Doc';
+import { CreateLinkToActiveAudio, Doc, FieldType, Opt, StrListCast } from '../../fields/Doc';
 import { DocData } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
 import { ScriptField } from '../../fields/ScriptField';
 import { ScriptCast } from '../../fields/Types';
-import { DocUtils, Docs } from '../documents/Documents';
-import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView';
+import { Docs } from '../documents/Documents';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { dropActionType } from './DropActionTypes';
 import { ScriptingGlobals } from './ScriptingGlobals';
@@ -74,6 +73,7 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
 }
 
 export namespace DragManager {
+    export const dragClassName = 'collectionFreeFormDocumentView-container';
     let dragDiv: HTMLDivElement;
     let dragLabel: HTMLDivElement;
     export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => boolean>;
@@ -139,11 +139,11 @@ export namespace DragManager {
         constructor(dragDoc: Doc[], dropAction?: dropActionType) {
             this.draggedDocuments = dragDoc;
             this.droppedDocuments = [];
-            this.draggedViews = [];
             this.offset = [0, 0];
             this.dropAction = dropAction;
         }
-        draggedViews: DocumentView[];
+        dragEnding?: () => void;
+        dragStarting?: () => void;
         draggedDocuments: Doc[];
         droppedDocuments: Doc[];
         treeViewDoc?: Doc;
@@ -157,6 +157,7 @@ export namespace DragManager {
         removeDocument?: RemoveFunction;
         isDocDecorationMove?: boolean; // Flags that Document decorations are used to drag document which allows suppression of onDragStart scripts
     }
+    Doc.SetDocDragDataName(DocumentDragData.name);
     export class LinkDragData {
         constructor(dragView: DocumentView, linkSourceGetAnchor: () => Doc) {
             this.linkDragView = dragView;
@@ -224,12 +225,12 @@ export namespace DragManager {
     export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => any) {
         const addAudioTag = (dropDoc: any) => {
             dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField());
-            dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
+            dropDoc instanceof Doc && CreateLinkToActiveAudio(() => dropDoc);
             return dropDoc;
         };
         const finishDrag = async (e: DragCompleteEvent) => {
             const { docDragData } = e;
-            setTimeout(() => dragData.draggedViews.forEach(view => view.props.dragEnding?.()));
+            setTimeout(() => dragData.dragEnding?.());
             onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
             if (docDragData && !docDragData.droppedDocuments.length) {
                 docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
@@ -265,7 +266,7 @@ export namespace DragManager {
         };
         dragData.draggedDocuments.map(d => d.dragFactory); // does this help?  trying to make sure the dragFactory Doc is loaded
         StartDrag(eles, dragData, downX, downY, options, finishDrag);
-        dragData.draggedViews.forEach(view => view.props.dragStarting?.());
+        dragData.dragStarting?.();
         return true;
     }
 
@@ -418,7 +419,7 @@ export namespace DragManager {
             // if the parent isn't a freeform view, then the element's width and height are presumed to match the acutal doc's dimensions (eg, dragging from import sidebar menu)
             let rotation: number | undefined;
             for (let parEle: HTMLElement | null | undefined = ele.parentElement; parEle; parEle = parEle?.parentElement) {
-                if (parEle.className === CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName) {
+                if (parEle.className === DragManager.dragClassName) {
                     rotation = (rotation ?? 0) + Number(parEle.style.transform.replace(/.*rotate\(([-0-9.e]*)deg\).*/, '$1') || 0);
                 }
                 parEle = parEle.parentElement;
@@ -630,7 +631,7 @@ export namespace DragManager {
             dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, () => cleanupDrag(false));
         };
         const cleanupDrag = action((undo: boolean) => {
-            (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.dragEnding?.());
+            (dragData as DocumentDragData).dragEnding?.();
             hideDragShowOriginalElements(false);
             document.removeEventListener('pointermove', moveHandler, true);
             document.removeEventListener('pointerup', upHandler, true);
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 8d84dbad8..e1a503ac9 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -19,8 +19,8 @@ import { ObservableReactComponent } from '../views/ObservableReactComponent';
 import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
 import './GroupManager.scss';
 import { GroupMemberView } from './GroupMemberView';
-import { SettingsManager } from './SettingsManager';
 import { SharingManager, User } from './SharingManager';
+import { SnappingManager } from './SnappingManager';
 
 /**
  * Interface for options for the react-select component
@@ -289,7 +289,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
      */
     private get groupCreationModal() {
         const contents = (
-            <div className="group-create" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+            <div className="group-create" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }}>
                 <div className="group-heading" style={{ marginBottom: 0 }}>
                     <p>
                         <b>New Group</b>
@@ -384,7 +384,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
         const groups = this.groupSort === 'ascending' ? this.allGroups.sort(sortGroups) : this.groupSort === 'descending' ? this.allGroups.sort(sortGroups).reverse() : this.allGroups;
 
         return (
-            <div className="group-interface" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+            <div className="group-interface" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }}>
                 {this.groupCreationModal}
                 {this.currentGroup ? (
                     <GroupMemberView
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index c703c3f98..da9e1aa28 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -11,7 +11,7 @@ import { StrCast } from '../../fields/Types';
 import { MainViewModal } from '../views/MainViewModal';
 import { GroupManager, UserOptions } from './GroupManager';
 import './GroupMemberView.scss';
-import { SettingsManager } from './SettingsManager';
+import { SnappingManager } from './SnappingManager';
 
 interface GroupMemberViewProps {
     group: Doc;
@@ -34,7 +34,7 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
         const hasEditAccess = GroupManager.Instance.hasEditAccess(this.group);
 
         return !this.group ? null : (
-            <div className="editing-interface" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+            <div className="editing-interface" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }}>
                 <div className="editing-header">
                     <input
                         className="group-title"
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 09262f5ac..7ab476591 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,11 +1,9 @@
 import { action, runInAction } from 'mobx';
-import { Doc, DocListCast, FieldType, FieldResult, Opt } from '../../fields/Doc';
-import { ScriptField } from '../../fields/ScriptField';
+import { Doc, DocListCast, FieldResult, FieldType, Opt } from '../../fields/Doc';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
-import { OpenWhere } from '../views/nodes/DocumentView';
-import { FocusViewOptions } from '../views/nodes/FieldView';
-import { PresBox } from '../views/nodes/trails';
+import { FocusViewOptions } from '../views/nodes/FocusViewOptions';
+import { OpenWhere } from '../views/nodes/OpenWhere';
 import { DocumentManager } from './DocumentManager';
 import { LinkManager } from './LinkManager';
 import { ScriptingGlobals } from './ScriptingGlobals';
@@ -90,8 +88,9 @@ export class LinkFollower {
                     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 as PresBox).PlayTrail(containerDocContext))) {
-                            PresBox.OpenPresMinimized(target, [0, 0]);
+                        if (!DocumentManager.Instance.AddViewRenderedCb(target, dv => containerDocContext.length && dv.ComponentView?.playTrail?.(containerDocContext))) {
+                            alert('OPEN Pres minimized');
+                            // PresBox.OpenPresMinimized(target, [0, 0]);
                         }
                         finished?.();
                     } else {
@@ -138,9 +137,6 @@ ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) {
     return LinkFollower.FollowLink(undefined, doc, altKey) ? undefined : { select: true };
 });
 
-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(');
 }
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 05fb849fd..248fda7e3 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -2,7 +2,7 @@ import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { MainViewModal } from '../views/MainViewModal';
-import { SettingsManager } from './SettingsManager';
+import { SnappingManager } from './SnappingManager';
 
 @observer
 export class RTFMarkup extends React.Component<{}> {
@@ -10,6 +10,10 @@ export class RTFMarkup extends React.Component<{}> {
     static Instance: RTFMarkup;
     @observable private isOpen = false; // whether the SharingManager modal is open or not
 
+    public setOpen = action((status: boolean) => {
+        this.isOpen = status;
+    });
+
     constructor(props: {}) {
         super(props);
         makeObservable(this);
@@ -21,7 +25,7 @@ export class RTFMarkup extends React.Component<{}> {
      */
     @computed get cheatSheet() {
         return (
-            <div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}>
+            <div style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, textAlign: 'initial', height: '100%' }}>
                 <p>
                     <b style={{ fontSize: 'larger' }}>(@wiki:phrase)</b>
                     {` display wikipedia page for entered text (terminate with carriage return)`}
@@ -122,24 +126,14 @@ 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' }}
+                dialogueBoxStyle={{ backgroundColor: SnappingManager.userBackgroundColor, alignContent: 'normal', color: SnappingManager.userColor, padding: '16px' }}
                 contents={this.cheatSheet}
                 isDisplayed={this.isOpen}
                 interactive
-                closeOnExternalClick={this.close}
+                closeOnExternalClick={() => this.setOpen(false)}
             />
         );
     }
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index 2c8fdf483..dcc6aabab 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -2,7 +2,7 @@ import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { Doc, IdToDoc } from '../../fields/Doc';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
-import { OpenWhereMod } from '../views/nodes/DocumentView';
+import { OpenWhereMod } from '../views/nodes/OpenWhere';
 import { VideoBox } from '../views/nodes/VideoBox';
 import { DocumentManager } from './DocumentManager';
 import { Movement, Presentation } from './TrackMovements';
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 8347844f7..8cb97fe50 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -14,9 +14,7 @@ import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types';
 import { DocServer } from '../DocServer';
 import { Networking } from '../Network';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
-import { GestureOverlay } from '../views/GestureOverlay';
 import { MainViewModal } from '../views/MainViewModal';
-import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
 import { GroupManager } from './GroupManager';
 import './SettingsManager.scss';
 import { SnappingManager } from './SnappingManager';
@@ -256,25 +254,14 @@ export class SettingsManager extends React.Component<{}> {
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                 />
-                <Toggle
-                    formLabel="Show Button Labels"
-                    formLabelPlacement="right"
-                    toggleType={ToggleType.SWITCH}
-                    onClick={() => {
-                        FontIconBox.ShowIconLabels = !FontIconBox.ShowIconLabels;
-                    }}
-                    toggleStatus={FontIconBox.ShowIconLabels}
-                    size={Size.XSMALL}
-                    color={SettingsManager.userColor}
-                />
                 <Toggle
                     formLabel="Recognize Ink Gestures"
                     formLabelPlacement="right"
                     toggleType={ToggleType.SWITCH}
                     onClick={() => {
-                        GestureOverlay.RecognizeGestures = !GestureOverlay.RecognizeGestures;
+                        Doc.UserDoc().recognizeGestures = !Doc.UserDoc().recognizeGestures;
                     }}
-                    toggleStatus={GestureOverlay.RecognizeGestures}
+                    toggleStatus={BoolCast(Doc.UserDoc().recognizeGestures)}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                 />
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 19a7948e6..b0140b73c 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -18,7 +18,6 @@ import { Id } from '../../fields/FieldSymbols';
 import { StrCast } from '../../fields/Types';
 import { GetEffectiveAcl, SharingPermissions, TraceMobx, distributeAcls, normalizeEmail } from '../../fields/util';
 import { DocServer } from '../DocServer';
-import { DictationOverlay } from '../views/DictationOverlay';
 import { MainViewModal } from '../views/MainViewModal';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
@@ -27,8 +26,8 @@ import { GroupManager, UserOptions } from './GroupManager';
 import { GroupMemberView } from './GroupMemberView';
 import { SearchUtil } from './SearchUtil';
 import { SelectionManager } from './SelectionManager';
-import { SettingsManager } from './SettingsManager';
 import './SharingManager.scss';
+import { SnappingManager } from './SnappingManager';
 import { undoable } from './UndoManager';
 
 export interface User {
@@ -238,14 +237,14 @@ export class SharingManager extends React.Component<{}> {
             const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
 
             return !permissions ? null : (
-                <div key={groupKey} className="container" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+                <div key={groupKey} className="container" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }}>
                     <div className="padding">{StrCast(group.title)}</div>
                     &nbsp;
                     {group instanceof Doc ? (
                         <IconButton
                             icon={<FontAwesomeIcon icon="info-circle" />}
                             size={Size.XSMALL}
-                            color={SettingsManager.userColor}
+                            color={SnappingManager.userColor}
                             onClick={action(() => {
                                 GroupManager.Instance.currentGroup = group;
                             })}
@@ -279,10 +278,10 @@ export class SharingManager extends React.Component<{}> {
                 <div
                     className="sharing-contents"
                     style={{
-                        background: SettingsManager.userBackgroundColor,
+                        background: SnappingManager.userBackgroundColor,
                         color: StrCast(Doc.UserDoc().userColor),
                     }}>
-                    <p className="share-title" style={{ color: SettingsManager.userColor }}>
+                    <p className="share-title" style={{ color: SnappingManager.userColor }}>
                         <div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}>
                             <FontAwesomeIcon icon="question-circle" size="sm" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} />
                         </div>
@@ -290,10 +289,10 @@ export class SharingManager extends React.Component<{}> {
                         {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
                     </p>
                     <div className="share-copy-link">
-                        <Button type={Type.TERT} color={SettingsManager.userColor} icon={<FontAwesomeIcon icon="copy" size="sm" />} iconPlacement="left" text="Copy Guest URL" onClick={this.copyURL} />
+                        <Button type={Type.TERT} color={SnappingManager.userColor} icon={<FontAwesomeIcon icon="copy" size="sm" />} iconPlacement="left" text="Copy Guest URL" onClick={this.copyURL} />
                     </div>
                     <div className="close-button">
-                        <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.close} color={SettingsManager.userColor} />
+                        <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.close} color={SnappingManager.userColor} />
                     </div>
                     {admin ? (
                         <div className="share-container">
@@ -335,7 +334,7 @@ export class SharingManager extends React.Component<{}> {
                                     </select>
                                 </div>
                                 <div className="share-button">
-                                    <Button text="SHARE" type={Type.TERT} color={SettingsManager.userColor} onClick={this.share} />
+                                    <Button text="SHARE" type={Type.TERT} color={SnappingManager.userColor} onClick={this.share} />
                                 </div>
                             </div>
                             <div className="sort-checkboxes">
@@ -514,7 +513,6 @@ export class SharingManager extends React.Component<{}> {
         setTimeout(
             action(() => {
                 // this.copied = false;
-                DictationOverlay.Instance.hasActiveModal = false;
                 this.targetDoc = undefined;
             }),
             500
@@ -528,7 +526,6 @@ export class SharingManager extends React.Component<{}> {
         runInAction(() => {
             this.targetDocView = target;
             this.targetDoc = targetDoc || target?.Document;
-            DictationOverlay.Instance.hasActiveModal = true;
             this.isOpen = this.targetDoc !== undefined;
             this.permissions = SharingPermissions.Augment;
             this.upgradeNested = true;
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index c4855d2e7..d92a73b63 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -15,9 +15,10 @@ import { PrefetchProxy } from '../../fields/Proxy';
 import { listSpec } from '../../fields/Schema';
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, ImageCast, StrCast } from '../../fields/Types';
-import { normalizeEmail } from '../../fields/util';
+import { SharingPermissions, inheritParentAcls, normalizeEmail } from '../../fields/util';
 import { DocServer } from '../DocServer';
-import { DocUtils, Docs, DocumentOptions } from '../documents/Documents';
+import { DocUtils } from '../documents/DocUtils';
+import { Docs, DocumentOptions } from '../documents/Documents';
 import { dropActionType } from '../util/DropActionTypes';
 import { HistoryUtil } from '../util/History';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
@@ -36,11 +37,51 @@ enum DashboardGroup {
     SharedDashboards,
 }
 
+export type DocConfig = {
+    doc: Doc;
+    initialWidth?: number;
+    path?: Doc[];
+};
+
 // DashboardView is the view with the dashboard previews, rendered when the app first loads
 
 @observer
 export class DashboardView extends ObservableReactComponent<{}> {
     public static _urlState: HistoryUtil.DocUrl;
+    public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) {
+        return {
+            type: 'react-component',
+            component: 'DocumentFrameRenderer',
+            title: document.title,
+            width: width,
+            props: {
+                documentId: document[Id],
+                keyValue,
+                panelName, // name of tab that can be used to close or replace its contents
+            },
+        };
+    }
+    static StandardCollectionDockingDocument(configs: Array<DocConfig>, options: DocumentOptions, id?: string, type: string = 'row') {
+        const layoutConfig = {
+            content: [
+                {
+                    type: type,
+                    content: [...configs.map(config => DashboardView.makeDocumentConfig(config.doc, undefined, config.initialWidth))],
+                },
+            ],
+        };
+        const doc = Docs.Create.DockDocument(
+            configs.map(c => c.doc),
+            JSON.stringify(layoutConfig),
+            ClientUtils.CurrentUserEmail() === 'guest' ? options : { acl_Guest: SharingPermissions.View, ...options },
+            id
+        );
+        configs.forEach(c => {
+            Doc.SetContainer(c.doc, doc);
+            inheritParentAcls(doc, c.doc, false);
+        });
+        return doc;
+    }
     constructor(props: any) {
         super(props);
         makeObservable(this);
@@ -389,7 +430,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
 
         const title = name || `Dashboard ${dashboardCount}`;
         const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
-        const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
+        const dashboardDoc = DashboardView.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
 
         Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc, undefined, undefined, true);
         dashboardDoc.pane_count = 1;
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 391c2f694..a0e3d2ddd 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -12,8 +12,10 @@ import { DocumentType } from '../documents/DocumentTypes';
 import { DragManager } from '../util/DragManager';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { PinProps } from './PinFuncs';
-import { DocumentView, OpenWhere } from './nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from './nodes/FieldView';
+import { DocumentView } from './nodes/DocumentView';
+import { FocusViewOptions } from './nodes/FocusViewOptions';
+import { FieldViewProps } from './nodes/FieldView';
+import { OpenWhere } from './nodes/OpenWhere';
 // import { DocUtils } from '../documents/Documents';
 
 /**
@@ -41,6 +43,7 @@ export interface ViewBoxInterface {
     onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
     getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
     setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
+    playTrail?: (docs: Doc[]) => void;
     playFrom?: (time: number, endTime?: number) => void;
     Pause?: () => void; // pause a media document (eg, audio/video)
     IsPlaying?: () => boolean; // is a media document playing
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c5358dffe..dd744f272 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -14,7 +14,7 @@ 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/Documents';
+import { DocUtils } from '../documents/DocUtils';
 import { CalendarManager } from '../util/CalendarManager';
 import { DragManager } from '../util/DragManager';
 import { dropActionType } from '../util/DropActionTypes';
@@ -22,15 +22,16 @@ import { IsFollowLinkScript } from '../util/LinkFollower';
 import { SelectionManager } from '../util/SelectionManager';
 import { SharingManager } from '../util/SharingManager';
 import { UndoManager, undoBatch } from '../util/UndoManager';
-import { PinProps } from './DocComponent';
 import './DocumentButtonBar.scss';
 import { ObservableReactComponent } from './ObservableReactComponent';
+import { 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, OpenWhere } from './nodes/DocumentView';
+import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
+import { OpenWhere } from './nodes/OpenWhere';
 import { DashFieldView } from './nodes/formattedText/DashFieldView';
 
 @observer
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 1e6eb1aeb..ef493fb69 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -33,9 +33,10 @@ import { CollectionDockingView } from './collections/CollectionDockingView';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { Colors } from './global/globalEnums';
 import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView';
-import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
+import { DocumentView } from './nodes/DocumentView';
 import { ImageBox } from './nodes/ImageBox';
 import { KeyValueBox } from './nodes/KeyValueBox';
+import { OpenWhereMod } from './nodes/OpenWhere';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 
 interface DocumentDecorationsProps {
@@ -448,7 +449,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 => CollectionFreeFormDocumentView.from(docView)?.CollectionFreeFormView?.dragStarting(false, false));
+        SelectionManager.Views.forEach(docView => CollectionFreeFormView.from(docView)?.dragStarting(false, false));
     };
 
     projectDragToAspect = (e: PointerEvent, docView: DocumentView, fixedAspect: number) => {
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index bf5b51a2d..0d1573ea3 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -4,17 +4,6 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
-import { Doc, Opt } from '../../fields/Doc';
-import { InkData, InkTool } from '../../fields/InkField';
-import { BoolCast, NumCast } from '../../fields/Types';
-import MobileInkOverlay from '../../mobile/MobileInkOverlay';
-import { Gestures } from '../../pen-gestures/GestureTypes';
-import { GestureUtils } from '../../pen-gestures/GestureUtils';
-import { MobileInkOverlayContent } from '../../server/Message';
-import { InteractionUtils } from '../util/InteractionUtils';
-import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { Transform } from '../util/Transform';
-import './GestureOverlay.scss';
 import {
     ActiveArrowEnd,
     ActiveArrowScale,
@@ -24,12 +13,24 @@ import {
     ActiveInkBezierApprox,
     ActiveInkColor,
     ActiveInkWidth,
+    Doc,
+    Opt,
     SetActiveArrowStart,
     SetActiveDash,
     SetActiveFillColor,
     SetActiveInkColor,
     SetActiveInkWidth,
-} from './InkingStroke';
+} from '../../fields/Doc';
+import { InkData, InkTool } from '../../fields/InkField';
+import { NumCast } from '../../fields/Types';
+// import MobileInkOverlay from '../../mobile/MobileInkOverlay';
+import { Gestures } from '../../pen-gestures/GestureTypes';
+import { GestureUtils } from '../../pen-gestures/GestureUtils';
+// import { MobileInkOverlayContent } from '../../server/Message';
+import { InteractionUtils } from '../util/InteractionUtils';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { Transform } from '../util/Transform';
+import './GestureOverlay.scss';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { DocumentView } from './nodes/DocumentView';
 
@@ -49,13 +50,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
     // eslint-disable-next-line no-use-before-define
     static Instances: GestureOverlay[] = [];
 
-    public static set RecognizeGestures(active) {
-        Doc.UserDoc().recognizeGestures = active;
-    }
-    public static get RecognizeGestures() {
-        return BoolCast(Doc.UserDoc().recognizeGestures);
-    }
-
     @observable public InkShape: Opt<Gestures> = undefined;
     @observable public SavedColor?: string = undefined;
     @observable public SavedWidth?: number = undefined;
@@ -80,7 +74,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         return this.Tool !== ToolglassTools.None;
     }
 
-    @observable private showMobileInkOverlay: boolean = false;
+    // @observable private showMobileInkOverlay: boolean = false;
 
     private _overlayRef = React.createRef<HTMLDivElement>();
     private _d1: Doc | undefined;
@@ -158,7 +152,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                 // need to decide when to turn gestures back on
                 const result = points.length > 2 && GestureUtils.GestureRecognizer.Recognize([points]);
                 let actionPerformed = false;
-                if (GestureOverlay.RecognizeGestures && result && result.Score > 0.7) {
+                if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) {
                     switch (result.Name) {
                         case Gestures.Line:
                         case Gestures.Triangle:
@@ -502,15 +496,10 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         this._clipboardDoc = undefined;
     };
 
-    @action
-    enableMobileInkOverlay = (content: MobileInkOverlayContent) => {
-        this.showMobileInkOverlay = content.enableOverlay;
-    };
-
     render() {
         return (
             <div className="gestureOverlay-cont" style={{ pointerEvents: this._props.isActive ? 'all' : 'none' }} ref={this._overlayRef} onPointerDown={this.onPointerDown}>
-                {this.showMobileInkOverlay ? <MobileInkOverlay /> : null}
+                {/* {this.showMobileInkOverlay ? <MobileInkOverlay /> : null} */}
                 {this.elements}
 
                 <div
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index ebd61db7d..0f444094a 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -21,8 +21,9 @@ import { MainView } from './MainView';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
+import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { OpenWhereMod } from './nodes/DocumentView';
+import { OpenWhereMod } from './nodes/OpenWhere';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { AnchorMenu } from './pdf/AnchorMenu';
 
@@ -88,8 +89,8 @@ export class KeyManager {
     private handleGreedy = action((/* keyname: string */) => {});
 
     nudge = (x: number, y: number, label: string) => {
-        const nudgeable = SelectionManager.Views.some(dv => dv.CollectionFreeFormDocumentView?.nudge);
-        nudgeable && UndoManager.RunInBatch(() => SelectionManager.Views.map(dv => dv.CollectionFreeFormDocumentView?.nudge(x, y)), label);
+        const nudgeable = SelectionManager.Views.some(dv => CollectionFreeFormDocumentView.from(dv)?.nudge);
+        nudgeable && UndoManager.RunInBatch(() => SelectionManager.Views.map(dv => CollectionFreeFormDocumentView.from(dv)?.nudge(x, y)), label);
         return { stopPropagation: nudgeable, preventDefault: nudgeable };
     };
 
@@ -105,7 +106,7 @@ export class KeyManager {
             case 'g':
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
                     const selected = SelectionManager.Views;
-                    const cv = selected.reduce((col, dv) => (!col || dv.CollectionFreeFormView === col ? dv.CollectionFreeFormView : undefined), undefined as undefined | CollectionFreeFormView);
+                    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');
                 }
                 break;
@@ -205,7 +206,7 @@ export class KeyManager {
         switch (keyname) {
             case 'ƒ':
             case 'f':
-                UndoManager.RunInBatch(() => SelectionManager.Views?.[0]?.CollectionFreeFormDocumentView?.float(), 'float');
+                UndoManager.RunInBatch(() => CollectionFreeFormDocumentView.from(SelectionManager.Views?.[0])?.float(), 'float');
                 break;
             default:
         }
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 98cf33c41..109a9cad4 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -13,6 +13,7 @@ import { DocumentManager } from '../util/DocumentManager';
 import { undoBatch } from '../util/UndoManager';
 import { FitOneCurve } from '../util/bezierFit';
 import { InkingStroke } from './InkingStroke';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { DocumentView } from './nodes/DocumentView';
 
 export class InkStrokeProperties {
@@ -380,7 +381,7 @@ export class InkStrokeProperties {
     };
 
     snapToAllCurves = (screenDragPt: { X: number; Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number; Y: number }; distance: number }, ink: InkData, controlIndex: number) => {
-        const containingCollection = inkView.CollectionFreeFormView;
+        const containingCollection = CollectionFreeFormView.from(inkView);
         const containingDocView = containingCollection?.DocumentView?.();
         containingCollection?.childDocs
             .filter(doc => doc.type === DocumentType.INK)
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index cf609d8f9..884512539 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -30,6 +30,7 @@ import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
 import { CognitiveServices } from '../cognitive_services/CognitiveServices';
 import { Docs } from '../documents/Documents';
+import { DocumentType } from '../documents/DocumentTypes';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
@@ -491,67 +492,17 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
         );
     }
 }
-export function ActiveInkPen(): Doc {
-    return Doc.UserDoc();
-}
-export function ActiveInkColor(): string {
-    return StrCast(ActiveInkPen()?.activeInkColor, 'black');
-}
-export function ActiveFillColor(): string {
-    return StrCast(ActiveInkPen()?.activeFillColor, '');
-}
-export function ActiveIsInkMask(): boolean {
-    return BoolCast(ActiveInkPen()?.activeIsInkMask, false);
-}
-export function ActiveInkHideTextLabels(): boolean {
-    return BoolCast(ActiveInkPen().activeInkHideTextLabels, false);
-}
-export function ActiveArrowStart(): string {
-    return StrCast(ActiveInkPen()?.activeArrowStart, '');
-}
-export function ActiveArrowEnd(): string {
-    return StrCast(ActiveInkPen()?.activeArrowEnd, '');
-}
-export function ActiveArrowScale(): number {
-    return NumCast(ActiveInkPen()?.activeArrowScale, 1);
-}
-export function ActiveDash(): string {
-    return StrCast(ActiveInkPen()?.activeDash, '0');
-}
-export function ActiveInkWidth(): number {
-    return Number(ActiveInkPen()?.activeInkWidth);
-}
-export function ActiveInkBezierApprox(): string {
-    return StrCast(ActiveInkPen()?.activeInkBezier);
-}
-
-export function SetActiveInkWidth(width: string): void {
-    !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
-}
-export function SetActiveBezierApprox(bezier: string): void {
-    ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier);
-}
-export function SetActiveInkColor(value: string) {
-    ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
-}
-export function SetActiveIsInkMask(value: boolean) {
-    ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
-}
-export function SetActiveInkHideTextLabels(value: boolean) {
-    ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value);
-}
-export function SetActiveFillColor(value: string) {
-    ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
-}
-export function SetActiveArrowStart(value: string) {
-    ActiveInkPen() && (ActiveInkPen().activeArrowStart = value);
-}
-export function SetActiveArrowEnd(value: string) {
-    ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value);
-}
-export function SetActiveArrowScale(value: number) {
-    ActiveInkPen() && (ActiveInkPen().activeArrowScale = value);
-}
-export function SetActiveDash(dash: string): void {
-    !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash);
-}
+Docs.Prototypes.TemplateMap.set(DocumentType.INK, {
+    // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
+    layout: { view: InkingStroke, dataField: 'stroke' },
+    options: {
+        acl: '',
+        systemIcon: 'BsFillPencilFill', //
+        _layout_nativeDimEditable: true,
+        _layout_reflowVertical: true,
+        _layout_reflowHorizontal: true,
+        layout_hideDecorationTitle: true, // don't show title when selected
+        _layout_fitWidth: false,
+        layout_isSvg: true,
+    },
+});
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 7bf6bb9e5..020525ef8 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -9,11 +9,10 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
-import { Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc';
+import { CreateLinkToActiveAudio, Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { InkTool } from '../../fields/InkField';
 import { Cast, NumCast, toList } from '../../fields/Types';
-import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
 import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
@@ -27,7 +26,8 @@ import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
 import { TabDocView } from './collections/TabDocView';
-import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
+import { DocumentView } from './nodes/DocumentView';
+import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
 
 interface LightboxViewProps {
     PanelWidth: number;
@@ -88,7 +88,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                 savedKeys.forEach(key => {
                     this._savedState[key] = Doc.Get(doc, key, true);
                 });
-            const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
+            const l = CreateLinkToActiveAudio(() => doc).lastElement();
             l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
             CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
             this._history.push({ doc, target });
@@ -136,7 +136,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
         const target = (this._docTarget = this._future.pop());
         const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target);
         if (targetDocView && target) {
-            const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
+            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 });
             if (this._history.lastElement().target !== target) this._history.push({ doc: lightDoc, target });
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d3dd51ac6..aec553baa 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -11,7 +11,7 @@ import * as React from 'react';
 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, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, GetDocFromUrl, Opt } from '../../fields/Doc';
 import { DocData } from '../../fields/DocSymbols';
 import { DocCast, StrCast, toList } from '../../fields/Types';
 import { DocServer } from '../DocServer';
@@ -55,28 +55,28 @@ import { CollectionDockingView } from './collections/CollectionDockingView';
 import { CollectionMenu } from './collections/CollectionMenu';
 import { TabDocView } from './collections/TabDocView';
 import './collections/TreeView.scss';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
 import { CollectionLinearView } from './collections/collectionLinear';
 import { LinkMenu } from './linking/LinkMenu';
 import { AudioBox } from './nodes/AudioBox';
 import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
 import { DocButtonState } from './nodes/DocumentLinksButton';
-import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from './nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from './nodes/DocumentView';
 import { ImageEditorData as ImageEditor } from './nodes/ImageBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
 import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
 import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu';
+import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
 import { TaskCompletionBox } from './nodes/TaskCompletedBox';
 import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
-import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
 import GenerativeFill from './nodes/generativeFill/GenerativeFill';
 import { PresBox } from './nodes/trails';
 import { AnchorMenu } from './pdf/AnchorMenu';
 import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
 import { TopBar } from './topbar/TopBar';
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
 
 const _global = (window /* browser */ || global) /* node */ as any;
 const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
@@ -242,7 +242,7 @@ export class MainView extends ObservableReactComponent<{}> {
         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 = FormattedTextBox.GetDocFromUrl(e.detail);
+            const id = GetDocFromUrl(e.detail);
             DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.showDocument(doc, { willPan: false }) : null));
         });
         document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener);
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index ea73a53a9..06cae6d04 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -4,16 +4,16 @@ import * as React from 'react';
 import { Doc, Opt } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocData } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
+import { FollowLinkScript } from '../../fields/ScriptField';
 import { NumCast } from '../../fields/Types';
 import { GetEffectiveAcl } from '../../fields/util';
 import { unimplementedFunction, Utils } from '../../Utils';
-import { Docs, DocUtils } from '../documents/Documents';
+import { Docs } from '../documents/Documents';
+import { DocUtils } from '../documents/DocUtils';
 import { DragManager } from '../util/DragManager';
-import { FollowLinkScript } from '../util/LinkFollower';
 import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
 import './MarqueeAnnotator.scss';
 import { DocumentView } from './nodes/DocumentView';
-import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { AnchorMenu } from './pdf/AnchorMenu';
 
@@ -197,7 +197,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
 
             const targetCreator = (annotationOn: Doc | undefined) => {
                 const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
-                FormattedTextBox.SetSelectOnLoad(target);
+                Doc.SetSelectOnLoad(target);
                 return target;
             };
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index fddb40624..034ade50b 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -3,7 +3,8 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { lightOrDark, returnFalse } from '../../ClientUtils';
 import { Doc, Opt } from '../../fields/Doc';
-import { DocUtils, Docs, DocumentOptions } from '../documents/Documents';
+import { Docs, DocumentOptions } from '../documents/Documents';
+import { DocUtils } from '../documents/DocUtils';
 import { ImageUtils } from '../util/Import & Export/ImageUtils';
 import { Transform } from '../util/Transform';
 import { UndoManager, undoBatch } from '../util/UndoManager';
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index d83fea2a5..cbbf48c75 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -20,8 +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 { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
-import { DocUtils } from '../documents/Documents';
 import { IsFollowLinkScript } from '../util/LinkFollower';
 import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
@@ -30,7 +30,8 @@ import { undoBatch, undoable } from '../util/UndoManager';
 import { InkingStroke } from './InkingStroke';
 import './PropertiesButtons.scss';
 import { Colors } from './global/globalEnums';
-import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { DocumentView } from './nodes/DocumentView';
+import { OpenWhere } from './nodes/OpenWhere';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 
 @observer
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index e079b5cde..a806995f2 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -12,7 +12,7 @@ import { SettingsManager } from '../util/SettingsManager';
 import './PropertiesDocBacklinksSelector.scss';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { LinkMenu } from './linking/LinkMenu';
-import { OpenWhere } from './nodes/DocumentView';
+import { OpenWhere } from './nodes/OpenWhere';
 
 type PropertiesDocBacklinksSelectorProps = {
     Document: Doc;
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 2d04f2fe3..722b1f90a 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -11,7 +11,8 @@ import { DocFocusOrOpen } from '../util/DocumentManager';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import './PropertiesDocContextSelector.scss';
 import { CollectionDockingView } from './collections/CollectionDockingView';
-import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { DocumentView } from './nodes/DocumentView';
+import { OpenWhere } from './nodes/OpenWhere';
 
 type PropertiesDocContextSelectorProps = {
     DocView?: DocumentView;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index f4ded8367..ff5dcd1b8 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -14,7 +14,7 @@ import { ColorResult, SketchPicker } from 'react-color';
 import * as Icons from 'react-icons/bs'; // {BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
 import { ClientUtils, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
-import { Doc, Field, FieldType, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
+import { Doc, Field, FieldResult, FieldType, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
 import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { InkField } from '../../fields/InkField';
@@ -41,9 +41,10 @@ import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
 import { PropertiesSection } from './PropertiesSection';
 import './PropertiesView.scss';
 import { DefaultStyleProvider } from './StyleProvider';
-import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { DocumentView } from './nodes/DocumentView';
 import { StyleProviderFuncType } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
+import { OpenWhere } from './nodes/OpenWhere';
 import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
 
 const _global = (window /* browser */ || global) /* node */ as any;
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 6195dcde8..3648e613d 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -12,7 +12,8 @@ import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
 import { DocCast, NumCast, StrCast } from '../../fields/Types';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
-import { DocUtils, Docs } from '../documents/Documents';
+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';
@@ -83,7 +84,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
             _text_fontSize: StrCast(Doc.UserDoc().fontSize),
             _text_fontFamily: StrCast(Doc.UserDoc().fontFamily),
         });
-        FormattedTextBox.SetSelectOnLoad(target);
+        Doc.SetSelectOnLoad(target);
         FormattedTextBox.DontSelectInitialText = true;
         const link = DocUtils.MakeLink(anchor, target, { link_relationship: 'inline comment:comment on' });
         link && (link.link_displayLine = false);
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 1d02568dd..5a7c2ef5b 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -8,7 +8,8 @@ import { ScriptField } from '../../fields/ScriptField';
 import { Cast, DocCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
 import { emptyFunction } from '../../Utils';
-import { Docs, DocUtils } from '../documents/Documents';
+import { Docs } from '../documents/Documents';
+import { DocUtils } from '../documents/DocUtils';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { Transform } from '../util/Transform';
 import { CollectionTreeView } from './collections/CollectionTreeView';
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 8d8f41126..d8232c132 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -14,7 +14,7 @@ import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { StyleProp } from '../StyleProvider';
 import { DocumentView } from '../nodes/DocumentView';
-import { FocusViewOptions } from '../nodes/FieldView';
+import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import './CollectionCarousel3DView.scss';
 import { CollectionSubView } from './CollectionSubView';
 
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index fefaf6591..b98aceb16 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -2,7 +2,7 @@ import { action, IReactionDisposer, makeObservable, observable, reaction } from
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, incrementTitleCopy } from '../../../ClientUtils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, incrementTitleCopy, UpdateIcon } from '../../../ClientUtils';
 import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
 import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -25,12 +25,11 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
 import { DashboardView } from '../DashboardView';
 import { LightboxView } from '../LightboxView';
-import { OpenWhere, OpenWhereMod } 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 { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionSubView } from './CollectionSubView';
 import { TabDocView } from './TabDocView';
 
@@ -40,19 +39,6 @@ const _global = (window /* browser */ || global) /* node */ as any;
 export class CollectionDockingView extends CollectionSubView() {
     // eslint-disable-next-line no-use-before-define
     @observable public static Instance: CollectionDockingView | undefined = undefined;
-    public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) {
-        return {
-            type: 'react-component',
-            component: 'DocumentFrameRenderer',
-            title: document.title,
-            width: width,
-            props: {
-                documentId: document[Id],
-                keyValue,
-                panelName, // name of tab that can be used to close or replace its contents
-            },
-        };
-    }
 
     private _reactionDisposer?: IReactionDisposer;
     private _lightboxReactionDisposer?: IReactionDisposer;
@@ -86,7 +72,7 @@ export class CollectionDockingView extends CollectionSubView() {
      */
     public StartOtherDrag = (e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => {
         this._flush = this._flush ?? UndoManager.StartBatch('golden layout drag');
-        const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) : { type: 'row', content: dragDocs.map(doc => CollectionDockingView.makeDocumentConfig(doc)) };
+        const config = dragDocs.length === 1 ? DashboardView.makeDocumentConfig(dragDocs[0]) : { type: 'row', content: dragDocs.map(doc => DashboardView.makeDocumentConfig(doc)) };
         const dragSource = CollectionDockingView.Instance?._goldenLayout.createDragSource(document.createElement('div'), config);
         this.tabDragStart(dragSource, finishDrag);
         dragSource._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 });
@@ -139,7 +125,7 @@ export class CollectionDockingView extends CollectionSubView() {
     public static ReplaceTab(document: Doc, mods: OpenWhereMod, stack: any, panelName: string, addToSplit?: boolean, keyValue?: boolean): boolean {
         const instance = CollectionDockingView.Instance;
         if (!instance) return false;
-        const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue);
+        const newConfig = DashboardView.makeDocumentConfig(document, panelName, undefined, keyValue);
         if (!panelName && stack) {
             const activeContentItemIndex = stack.contentItems.findIndex((item: any) => item.config === stack._activeContentItem.config);
             const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout);
@@ -180,7 +166,7 @@ export class CollectionDockingView extends CollectionSubView() {
         const instance = CollectionDockingView.Instance;
         const glayRoot = instance._goldenLayout.root;
         if (!instance) return false;
-        const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue);
+        const docContentConfig = DashboardView.makeDocumentConfig(document, panelName, undefined, keyValue);
 
         CollectionDockingView.Instance._flush = CollectionDockingView.Instance._flush ?? UndoManager.StartBatch('Add Split');
         setTimeout(CollectionDockingView.Instance.endUndoBatch, 100);
@@ -461,7 +447,7 @@ export class CollectionDockingView extends CollectionSubView() {
         if (content) {
             const _width = DivWidth(content);
             const _height = DivHeight(content);
-            return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', iconFile => {
+            return UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', iconFile => {
                 const proto = this.dataDoc; // Cast(img.proto, Doc, null)!;
                 proto.thumb_nativeWidth = _width;
                 proto.thumb_nativeHeight = _height;
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 8803f6f79..9a6f1e2eb 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -166,7 +166,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
         this._createEmbeddingSelected = false;
         const { pivotField } = this._props;
         const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
-        FormattedTextBox.SetSelectOnLoad(newDoc);
+        Doc.SetSelectOnLoad(newDoc);
         FormattedTextBox.SelectOnLoadChar = value;
         pivotField && (newDoc[DocData][pivotField] = this.getValue(this._props.heading));
         const docs = this._props.parent.childDocList;
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 9212a6609..e940a1aef 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -11,7 +11,8 @@ import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { Docs } from '../../documents/Documents';
+import { DocUtils } from '../../documents/DocUtils';
 import { DragManager } from '../../util/DragManager';
 import { dropActionType } from '../../util/DropActionTypes';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -22,9 +23,10 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { FieldsDropdown } from '../FieldsDropdown';
 import { Colors } from '../global/globalEnums';
 import { LightboxView } from '../LightboxView';
+import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
 import { DocumentView } from '../nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { FieldViewProps } from '../nodes/FieldView';
+import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import { StyleProp } from '../StyleProvider';
 import './CollectionNoteTakingView.scss';
 import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn';
@@ -440,7 +442,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
             e.stopPropagation?.();
             const newDoc = Doc.MakeCopy(fieldProps.Document, true);
             newDoc[DocData].text = undefined;
-            FormattedTextBox.SetSelectOnLoad(newDoc);
+            Doc.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
         return undefined;
@@ -472,7 +474,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 }
                 return true;
             }
-        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
+        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && CollectionFreeFormDocumentView.from(de.complete.linkDragData?.linkDragView)) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
             if (!this._props.addDocument?.(source)) e.preventDefault();
             de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 95aecc7d0..c098c033b 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -11,7 +11,9 @@ import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { Cast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { DocUtils, Docs } from '../../documents/Documents';
+import { DocumentFromField } from '../../documents/DocFromField';
+import { DocUtils } from '../../documents/DocUtils';
+import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
@@ -20,8 +22,8 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionNoteTakingView.scss';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 
 interface CSVFieldColumnProps {
     Document: Doc;
@@ -149,7 +151,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
         const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
         const colValue = this.getValue(this._props.heading);
         newDoc[key] = colValue;
-        FormattedTextBox.SetSelectOnLoad(newDoc);
+        Doc.SetSelectOnLoad(newDoc);
         FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
         return this._props.addDocument?.(newDoc) || false;
     };
@@ -178,7 +180,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
             doc => {
                 const key = this._props.pivotField;
                 doc[key] = this.getValue(this._props.heading);
-                FormattedTextBox.SetSelectOnLoad(doc);
+                Doc.SetSelectOnLoad(doc);
                 return this._props.addDocument?.(doc);
             },
             this._props.addDocument,
@@ -195,7 +197,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
                 docItems.push({
                     description: ':' + fieldKey,
                     event: () => {
-                        const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
+                        const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
                         if (created) {
                             if (this._props.Document.isTemplateDoc) {
                                 Doc.MakeMetadataFieldTemplate(created, this._props.Document);
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 6ccbd6208..e02570d3e 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -8,11 +8,11 @@ import { Doc, DocListCast } from '../../../fields/Doc';
 import { ScriptField } from '../../../fields/ScriptField';
 import { NumCast, StrCast, toList } from '../../../fields/Types';
 import { emptyFunction } from '../../../Utils';
-import { DocUtils } from '../../documents/Documents';
+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/DocumentView';
+import { OpenWhere } from '../nodes/OpenWhere';
 import { computePassLayout, computeStarburstLayout } from './collectionFreeForm';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import './CollectionPileView.scss';
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 7adf44a5c..50a66aa41 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -13,7 +13,7 @@ import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
-import { ComputedField, ScriptField } from '../../../fields/ScriptField';
+import { ComputedField, FollowLinkScript, ScriptField } from '../../../fields/ScriptField';
 import { Cast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { emptyFunction, formatTime } from '../../../Utils';
@@ -21,21 +21,23 @@ import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
-import { FollowLinkScript, IsFollowLinkScript, LinkFollower } from '../../util/LinkFollower';
+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';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { CollectionSubView } from './CollectionSubView';
 import { LightboxView } from '../LightboxView';
 import { AudioWaveform } from '../nodes/audio/AudioWaveform';
-import { DocumentView, OpenWhere } from '../nodes/DocumentView';
-import { FocusFuncType, FocusViewOptions, StyleProviderFuncType } from '../nodes/FieldView';
+import { DocumentView } from '../nodes/DocumentView';
+import { FocusFuncType, StyleProviderFuncType } from '../nodes/FieldView';
+import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import { LabelBox } from '../nodes/LabelBox';
+import { OpenWhere } from '../nodes/OpenWhere';
 import { VideoBox } from '../nodes/VideoBox';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import './CollectionStackedTimeline.scss';
+import { CollectionSubView } from './CollectionSubView';
 
 export type CollectionStackedTimelineProps = {
     Play: () => void;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ad9960989..8ae0f2832 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -15,8 +15,9 @@ import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { Docs } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/DocUtils';
 import { DragManager } from '../../util/DragManager';
 import { dropActionType } from '../../util/DropActionTypes';
 import { SettingsManager } from '../../util/SettingsManager';
@@ -28,8 +29,8 @@ import { EditableView } from '../EditableView';
 import { LightboxView } from '../LightboxView';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
 import { DocumentView } from '../nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { FieldViewProps } from '../nodes/FieldView';
+import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import { StyleProp } from '../StyleProvider';
 import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
 import './CollectionStackingView.scss';
@@ -296,7 +297,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 newDoc[layoutFieldKey] = fieldProps.Document[layoutFieldKey];
             }
             newDoc[DocData].text = undefined;
-            FormattedTextBox.SetSelectOnLoad(newDoc);
+            Doc.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
         return false;
@@ -481,7 +482,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 }
                 return true;
             }
-        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
+        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && CollectionFreeFormDocumentView.from(de.complete.linkDragData?.linkDragView)) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
             if (!this._props.addDocument?.(source)) e.preventDefault();
             de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index cb463077c..e2ad5b31d 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -14,8 +14,10 @@ import { BoolCast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentFromField } from '../../documents/DocFromField';
+import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/DocUtils';
 import { DragManager } from '../../util/DragManager';
 import { dropActionType } from '../../util/DropActionTypes';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -160,7 +162,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         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);
+        Doc.SetSelectOnLoad(newDoc);
         FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
         return this._props.addDocument?.(newDoc) || false;
     };
@@ -240,7 +242,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         const height = this._ele ? DivHeight(this._ele) : 0;
         DocUtils.addDocumentCreatorMenuItems(
             doc => {
-                FormattedTextBox.SetSelectOnLoad(doc);
+                Doc.SetSelectOnLoad(doc);
                 return this._props.addDocument?.(doc);
             },
             this._props.addDocument,
@@ -255,7 +257,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                 docItems.push({
                     description: ':' + fieldKey,
                     event: () => {
-                        const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
+                        const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
                         if (created) {
                             if (this._props.Document.isTemplateDoc) {
                                 Doc.MakeMetadataFieldTemplate(created, this._props.Document);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index f3dedaedf..7c08aedb1 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -3,7 +3,7 @@ import * as React from 'react';
 import * as rp from 'request-promise';
 import { ClientUtils, returnFalse } from '../../../ClientUtils';
 import CursorField from '../../../fields/CursorField';
-import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc';
 import { AclPrivate } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
@@ -15,8 +15,9 @@ import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { DocServer } from '../../DocServer';
 import { Networking } from '../../Network';
+import { DocUtils } from '../../documents/DocUtils';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents';
+import { Docs, DocumentOptions } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { dropActionType } from '../../util/DropActionTypes';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
@@ -26,7 +27,6 @@ import { UndoManager, undoBatch } from '../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { FieldViewProps } from '../nodes/FieldView';
 import { LoadingBox } from '../nodes/LoadingBox';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 
 export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> {
     isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
@@ -317,10 +317,10 @@ export function CollectionSubView<X>() {
             const addDocument = (doc: Doc | Doc[]) => this.addDocument(doc);
 
             if (html) {
-                if (FormattedTextBox.IsFragment(html)) {
-                    const href = FormattedTextBox.GetHref(html);
+                if (RTFIsFragment(html)) {
+                    const href = GetHrefFromHTML(html);
                     if (href) {
-                        const docId = FormattedTextBox.GetDocFromUrl(href);
+                        const docId = GetDocFromUrl(href);
                         if (docId) {
                             // prosemirror text containing link to dash document
                             DocServer.GetRefField(docId).then(f => {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 365b5acfd..d015e73ad 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -12,7 +12,8 @@ import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, Utils } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+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';
@@ -189,7 +190,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1];
                 this._props.removeDocument?.(docIn);
                 if (ind > 0 && prev) {
-                    FormattedTextBox.SetSelectOnLoad(prev);
+                    Doc.SetSelectOnLoad(prev);
                     DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false);
                 }
                 return true;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index d6cbe0dab..7cadd072b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -7,14 +7,15 @@ import { Doc, DocListCast } from '../../../fields/Doc';
 import { ObjectField } from '../../../fields/ObjectField';
 import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { CollectionViewType } from '../../documents/DocumentTypes';
-import { DocUtils } from '../../documents/Documents';
+import { DocUtils } from '../../documents/DocUtils';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
-import { OpenWhere } from '../nodes/DocumentView';
 import { FieldView } from '../nodes/FieldView';
+import { OpenWhere } from '../nodes/OpenWhere';
 import { CollectionCalendarView } from './CollectionCalendarView';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
 import { CollectionCarouselView } from './CollectionCarouselView';
@@ -234,3 +235,19 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.COL, {
+    layout: { view: CollectionView, dataField: 'data' },
+    options: {
+        acl: '',
+        _layout_fitWidth: true,
+        freeform: '',
+        _freeform_panX: 0,
+        _freeform_panY: 0,
+        _freeform_scale: 1,
+        _layout_nativeDimEditable: true,
+        _layout_reflowHorizontal: true,
+        _layout_reflowVertical: true,
+        systemIcon: 'BsFillCollectionFill',
+    },
+});
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index e7b8237a5..2d900bb73 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -28,12 +28,13 @@ import { UndoManager, undoable } from '../../util/UndoManager';
 import { DashboardView } from '../DashboardView';
 import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { PinProps, PinDocView } from '../PinFuncs';
+import { PinDocView, PinProps } from '../PinFuncs';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
 import { Colors } from '../global/globalEnums';
-import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView';
+import { DocumentView, returnEmptyDocViewList } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
+import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere';
 import { PresBox, PresMovement } from '../nodes/trails';
 import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionView } from './CollectionView';
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index a8dd60ae3..2f5af0564 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -18,8 +18,9 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
+import { DocUtils } from '../../documents/DocUtils';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DocUtils, Docs } from '../../documents/Documents';
+import { Docs } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { dropActionType } from '../../util/DropActionTypes';
@@ -31,9 +32,10 @@ import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
 import { EditableView } from '../EditableView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
-import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView';
+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';
@@ -1300,7 +1302,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     const fieldKey = Doc.LayoutFieldKey(newParent);
                     if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) {
                         remove(child);
-                        FormattedTextBox.SetSelectOnLoad(child);
+                        Doc.SetSelectOnLoad(child);
                         TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined;
                         Doc.AddDocToList(newParent, fieldKey, child, addAfter, false);
                         newParent.treeView_Open = true;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 653a01a04..f55d5a23f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -9,7 +9,7 @@ import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils';
 import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, FieldType, Opt } from '../../../../fields/Doc';
+import { ActiveInkWidth, Doc, DocListCast, Field, FieldType, Opt, SetActiveInkColor, SetActiveInkWidth } from '../../../../fields/Doc';
 import { DocData, Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkData, InkField, InkTool, Segment } from '../../../../fields/InkField';
@@ -23,8 +23,9 @@ import { TraceMobx } from '../../../../fields/util';
 import { Gestures, PointData } from '../../../../pen-gestures/GestureTypes';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
 import { aggregateBounds, emptyFunction, intersectRect, Utils } from '../../../../Utils';
-import { Docs, DocUtils } from '../../../documents/Documents';
+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';
@@ -39,13 +40,15 @@ import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
 import { Timeline } from '../../animationtimeline/Timeline';
 import { ContextMenu } from '../../ContextMenu';
 import { GestureOverlay } from '../../GestureOverlay';
-import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { InkingStroke } from '../../InkingStroke';
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
 import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
-import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
+import { DocumentView } from '../../nodes/DocumentView';
+import { FieldViewProps } from '../../nodes/FieldView';
+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 { CollectionSubView } from '../CollectionSubView';
@@ -285,7 +288,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
     isAnyChildContentActive = () => this._props.isAnyChildContentActive();
     addLiveTextBox = (newDoc: Doc) => {
-        FormattedTextBox.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+        Doc.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
         this.addDocument(newDoc);
     };
     selectDocuments = (docs: Doc[]) => {
@@ -1052,7 +1055,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         newDoc[DocData][Doc.LayoutFieldKey(newDoc, fieldProps.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
         newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
         newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
-        FormattedTextBox.SetSelectOnLoad(newDoc);
+        Doc.SetSelectOnLoad(newDoc);
         FormattedTextBox.DontSelectInitialText = true;
         return this.addDocument?.(newDoc);
     }, 'copied text note');
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 2f9cc49e0..3ab04f403 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -14,18 +14,19 @@ import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { GetEffectiveAcl } from '../../../../fields/util';
 import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
+import { DocUtils } from '../../../documents/DocUtils';
 import { DocumentType } from '../../../documents/DocumentTypes';
-import { DocUtils, Docs, DocumentOptions } from '../../../documents/Documents';
+import { Docs, DocumentOptions } from '../../../documents/Documents';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { freeformScrollMode } from '../../../util/SettingsManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { UndoManager, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
-import { MarqueeViewBounds } from '../../DocComponent';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { MarqueeViewBounds } from '../../PinFuncs';
 import { PreviewCursor } from '../../PreviewCursor';
-import { OpenWhere } from '../../nodes/DocumentView';
+import { OpenWhere } from '../../nodes/OpenWhere';
 import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { SubCollectionViewProps } from '../CollectionSubView';
@@ -162,7 +163,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             const slide = DocUtils.copyDragFactory(DocCast(Doc.UserDoc().emptySlide))!;
             slide.x = x;
             slide.y = y;
-            FormattedTextBox.SelectOnLoad = slide[Id];
+            Doc.SetSelectOnLoad(slide);
             TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined };
             this._props.addDocument?.(slide);
             e.stopPropagation();
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 1634daaf7..2d9191dd7 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -1,11 +1,11 @@
 import { action, computed, Lambda, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction } from '../../../../Utils';
-import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
@@ -13,7 +13,6 @@ import { undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
 import { DocumentView } from '../../nodes/DocumentView';
-import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionGridView.scss';
 import Grid, { Layout } from './Grid';
@@ -364,7 +363,7 @@ export class CollectionGridView extends CollectionSubView() {
                         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.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(clickEv.clientX, clickEv.clientY))));
                             })
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index ee79812a1..406a7d626 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -13,7 +13,8 @@ import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { ColumnType } from '../../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { DocUtils, Docs, DocumentOptions, FInfo } from '../../../documents/Documents';
+import { 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';
@@ -26,7 +27,8 @@ import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
 import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../../nodes/DocumentView';
-import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
+import { FieldViewProps } from '../../nodes/FieldView';
+import { FocusViewOptions } from '../../nodes/FocusViewOptions';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 61afe08cf..32b48e4d1 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -5,8 +5,8 @@ import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import { CgClose, CgLock, CgLockUnlock } from 'react-icons/cg';
 import { FaExternalLinkAlt } from 'react-icons/fa';
-import { emptyFunction } from '../../../../Utils';
 import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
 import { Doc } from '../../../../fields/Doc';
 import { BoolCast } from '../../../../fields/Types';
 import { DragManager } from '../../../util/DragManager';
@@ -15,8 +15,8 @@ import { Transform } from '../../../util/Transform';
 import { undoable } from '../../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
-import { OpenWhere } from '../../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
+import { OpenWhere } from '../../nodes/OpenWhere';
 import { CollectionSchemaView } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
 import { SchemaTableCell } from './SchemaTableCell';
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 231bac541..d17d4ff7c 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,7 +1,7 @@
 import { Colors } from 'browndash-components';
 import { action, runInAction } from 'mobx';
 import { aggregateBounds } from '../../../Utils';
-import { Doc, Opt } from '../../../fields/Doc';
+import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, Doc, Opt, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
@@ -13,7 +13,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { UndoManager, undoable } from '../../util/UndoManager';
 import { GestureOverlay } from '../GestureOverlay';
-import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
+import { InkingStroke } from '../InkingStroke';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
 import { DocumentView } from '../nodes/DocumentView';
@@ -111,7 +111,7 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
         if (NumCast(selected?.Document.z) >= 1) return true;
         return false;
     }
-    selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
+    selected ? CollectionFreeFormDocumentView.from(selected)?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
     return undefined;
 });
 
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 278d30d6a..df3accb0d 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -20,8 +20,9 @@ import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
 import { LinkInfo } from '../nodes/LinkDocPreview';
+import { OpenWhere } from '../nodes/OpenWhere';
 import './LinkMenuItem.scss';
 
 interface LinkMenuItemProps {
diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
index bce2b296f..72d81e9eb 100644
--- a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
+++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
@@ -5,7 +5,7 @@ import { InkTool } from '../../../../fields/InkField';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { OpenWhereMod } from '../../nodes/DocumentView';
+import { OpenWhereMod } from '../../nodes/OpenWhere';
 import { NewLightboxView } from '../NewLightboxView';
 import './ButtonMenu.scss';
 import { IButtonMenu } from './utils';
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index 6616abad1..dc6bf3e9c 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -6,10 +6,9 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { CreateLinkToActiveAudio, Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast, toList } from '../../../fields/Types';
-import { DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { LinkManager } from '../../util/LinkManager';
 import { SelectionManager } from '../../util/SelectionManager';
@@ -20,7 +19,8 @@ import { LightboxView } from '../LightboxView';
 import { DefaultStyleProvider } from '../StyleProvider';
 import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
 import { TabDocView } from '../collections/TabDocView';
-import { DocumentView, OpenWhere } from '../nodes/DocumentView';
+import { DocumentView } from '../nodes/DocumentView';
+import { OpenWhere } from '../nodes/OpenWhere';
 import { ExploreView } from './ExploreView';
 import { IBounds, emptyBounds } from './ExploreView/utils';
 import { NewLightboxHeader } from './Header';
@@ -108,7 +108,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
             Doc.ActiveTool = InkTool.None;
             SnappingManager.SetExploreMode(false);
         } else {
-            const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
+            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 });
@@ -150,7 +150,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
         const target = (NewLightboxView._docTarget = this._future?.pop());
         const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target);
         if (targetDocView && target) {
-            const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
+            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 });
             if (NewLightboxView._history?.lastElement().target !== target) NewLightboxView._history?.push({ doc, target });
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 1f618135f..4697491e0 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -13,7 +13,9 @@ import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DateCast, NumCast } from '../../../fields/Types';
 import { AudioField, nullAudio } from '../../../fields/URLField';
 import { formatTime } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { Docs } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/DocUtils';
 import { Networking } from '../../Network';
 import { DragManager } from '../../util/DragManager';
 import { LinkManager } from '../../util/LinkManager';
@@ -24,8 +26,8 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { PinDocView, PinProps } from '../PinFuncs';
 import './AudioBox.scss';
-import { OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
+import { OpenWhere } from './OpenWhere';
 
 /**
  * AudioBox
@@ -761,3 +763,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.AUDIO, {
+    layout: { view: AudioBox, dataField: 'data' },
+    options: { acl: '', _height: 100, _layout_fitWidth: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, systemIcon: 'BsFillVolumeUpFill' },
+});
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 9c4d748bd..691d07e31 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -11,14 +11,16 @@ 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 './CollectionFreeFormDocumentView.scss';
-import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
+import { DocumentView, DocumentViewProps } from './DocumentView';
 import { FieldViewProps } from './FieldView';
+import { OpenWhere } from './OpenWhere';
 
 /// Ugh, typescript has no run-time way of iterating through the keys of an interface.  so we need
 /// manaully keep this list of keys in synch wih the fields of the freeFormProps interface
@@ -48,7 +50,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     get displayName() { // this makes mobx trace() statements more descriptive
         return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
     } // prettier-ignore
-    public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
+    public static CollectionFreeFormDocViewClassName = DragManager.dragClassName;
     public static animFields: { key: string; val?: number }[] = [
         { key: 'x' },
         { key: 'y' },
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index bc151f6a5..c42ca4468 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -7,12 +7,14 @@ import { emptyFunction } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { RichTextField } from '../../../fields/RichTextField';
 import { DocCast, NumCast, RTFCast, StrCast, toList } from '../../../fields/Types';
-import { DocUtils, Docs } from '../../documents/Documents';
+import { DocUtils } from '../../documents/DocUtils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { dropActionType } from '../../util/DropActionTypes';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
-import { PinProps, PinDocView } from '../PinFuncs';
+import { PinDocView, PinProps } from '../PinFuncs';
 import { StyleProp } from '../StyleProvider';
 import './ComparisonBox.scss';
 import { DocumentView } from './DocumentView';
@@ -272,3 +274,18 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.COMPARISON, {
+    data: '',
+    layout: { view: ComparisonBox, dataField: 'data' },
+    options: {
+        acl: '',
+        backgroundColor: 'gray',
+        dropAction: dropActionType.move,
+        waitForDoubleClickToClick: 'always',
+        _layout_reflowHorizontal: true,
+        _layout_reflowVertical: true,
+        _layout_nativeDimEditable: true,
+        systemIcon: 'BsLayoutSplit',
+    },
+});
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 113a857c3..ecfdcc229 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -13,22 +13,26 @@ import { List } from '../../../../fields/List';
 import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { CsvField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
-import { DocUtils, Docs } from '../../../documents/Documents';
+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 { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
+import { ContextMenu } from '../../ContextMenu';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
 import { MarqueeAnnotator } from '../../MarqueeAnnotator';
+import { PinProps } from '../../PinFuncs';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup';
 import { DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
 import './DataVizBox.scss';
 import { Histogram } from './components/Histogram';
 import { LineChart } from './components/LineChart';
 import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
-import { ContextMenu } from '../../ContextMenu';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -522,3 +526,18 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
         );
     }
 }
+Docs.Prototypes.TemplateMap.set(DocumentType.DATAVIZ, {
+    layout: { view: DataVizBox, dataField: 'data' },
+    options: {
+        acl: '',
+        dataViz_title: '',
+        dataViz_line: '',
+        dataViz_pie: '',
+        dataViz_histogram: '',
+        dataViz: 'table',
+        _layout_fitWidth: true,
+        _layout_reflowHorizontal: true,
+        _layout_reflowVertical: true,
+        _layout_nativeDimEditable: true,
+    },
+});
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 9b4e36509..977899589 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -10,13 +10,13 @@ import { emptyFunction } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { StrCast } from '../../../fields/Types';
-import { DocUtils } from '../../documents/Documents';
+import { 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 { PinProps } from '../DocComponent';
 import { ObservableReactComponent } from '../ObservableReactComponent';
+import { PinProps } from '../PinFuncs';
 import './DocumentLinksButton.scss';
 import { DocumentView } from './DocumentView';
 import { LinkDescriptionPopup } from './LinkDescriptionPopup';
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a603de10b..e8d1e582e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -16,20 +16,20 @@ import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { PrefetchProxy } from '../../../fields/Proxy';
 import { listSpec } from '../../../fields/Schema';
-import { ScriptField } from '../../../fields/ScriptField';
+import { FollowLinkScript, 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 { DocServer } from '../../DocServer';
 import { Networking } from '../../Network';
+import { DocUtils } from '../../documents/DocUtils';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DocUtils, Docs } from '../../documents/Documents';
+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 { FollowLinkScript } from '../../util/LinkFollower';
 import { LinkManager, UPDATE_SERVER_CACHE } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SearchUtil } from '../../util/SearchUtil';
@@ -48,38 +48,14 @@ import { AudioAnnoState, StyleProp } from '../StyleProvider';
 import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
 import { DocumentLinksButton } from './DocumentLinksButton';
 import './DocumentView.scss';
-import { FieldViewProps, FieldViewSharedProps, FocusViewOptions } from './FieldView';
+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';
 
-export enum OpenWhereMod {
-    none = '',
-    left = 'left',
-    right = 'right',
-    top = 'top',
-    bottom = 'bottom',
-    keyvalue = 'keyValue',
-}
-export enum OpenWhere {
-    lightbox = 'lightbox',
-    add = 'add',
-    addLeft = 'add:left',
-    addRight = 'add:right',
-    addBottom = 'add:bottom',
-    close = 'close',
-    toggle = 'toggle',
-    toggleRight = 'toggle:right',
-    replace = 'replace',
-    replaceRight = 'replace:right',
-    replaceLeft = 'replace:left',
-    inParent = 'inParent',
-    inParentFromScreen = 'inParentFromScreen',
-    overlay = 'overlay',
-    addRightKeyvalue = 'add:right:keyValue',
-}
-
 export interface DocumentViewProps extends FieldViewSharedProps {
     hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
     hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected
@@ -292,7 +268,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             dragData.dropAction = dropAction;
             dragData.removeDocument = this._props.removeDocument;
             dragData.moveDocument = this._props.moveDocument;
-            dragData.draggedViews = [docView];
+            dragData.dragEnding = () => docView.props.dragEnding?.();
+            dragData.dragStarting = () => docView.props.dragStarting?.();
             dragData.canEmbed = !!(this.Document.dragAction ?? this._props.dragAction);
             (this._props.dragConfig ?? this._componentView?.dragConfig)?.(dragData);
             DragManager.StartDocumentDrag(
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 9c216cba4..32d08fbe7 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -3,10 +3,12 @@ import { action, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DivHeight, DivWidth } from '../../../ClientUtils';
-import { Id } from '../../../fields/FieldSymbols';
+import { Doc } from '../../../fields/Doc';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { DocUtils, Docs } from '../../documents/Documents';
+import { DocUtils } from '../../documents/DocUtils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { LightboxView } from '../LightboxView';
@@ -19,7 +21,6 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(EquationBox, fieldKey);
     }
-    public static SelectOnLoad: string = '';
     _ref: React.RefObject<EquationEditor> = React.createRef();
 
     constructor(props: FieldViewProps) {
@@ -29,12 +30,12 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     componentDidMount() {
         this._props.setContentViewBox?.(this);
-        if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) {
+        if (Doc.SelectOnLoad === this.Document && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) {
             this._props.select(false);
 
             this._ref.current!.mathField.focus();
             this.dataDoc.text === 'x' && this._ref.current!.mathField.select();
-            EquationBox.SelectOnLoad = '';
+            Doc.SetSelectOnLoad(undefined);
         }
         reaction(
             () => StrCast(this.dataDoc.text),
@@ -69,7 +70,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 x: NumCast(this.layoutDoc.x),
                 y: NumCast(this.layoutDoc.y) + _height + 10,
             });
-            EquationBox.SelectOnLoad = nextEq[Id];
+            Doc.SetSelectOnLoad(nextEq);
             this._props.addDocument?.(nextEq);
             e.stopPropagation();
         }
@@ -130,3 +131,8 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.EQUATION, {
+    layout: { view: EquationBox, dataField: 'text' },
+    options: { acl: '', fontSize: '14px', _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, // systemIcon: 'BsSuperscript' + BsSubscript
+});
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ab0850790..c6c77d8d2 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -10,29 +10,12 @@ import { ScriptField } from '../../../fields/ScriptField';
 import { WebField } from '../../../fields/URLField';
 import { dropActionType } from '../../util/DropActionTypes';
 import { Transform } from '../../util/Transform';
-import { PinProps, ViewBoxInterface } from '../DocComponent';
-import { DocumentView, OpenWhere } from './DocumentView';
+import { ViewBoxInterface } from '../DocComponent';
+import { PinProps } from '../PinFuncs';
+import { DocumentView } from './DocumentView';
+import { FocusViewOptions } from './FocusViewOptions';
+import { OpenWhere } from './OpenWhere';
 
-export interface FocusViewOptions {
-    willPan?: boolean; // determines whether to pan to target document
-    willZoomCentered?: boolean; // determines whether to zoom in on target document.  if zoomScale is 0, this just centers the document
-    zoomScale?: number; // percent of containing frame to zoom into document
-    zoomTime?: number;
-    didMove?: boolean; // whether a document was changed during the showDocument process
-    docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom.  when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
-    instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
-    preview?: boolean; // whether changes should be previewed by the componentView or written to the document
-    effect?: Doc; // animation effect for focus // bcz: needs to be changed to something more generic than a Doc
-    noSelect?: boolean; // whether target should be selected after focusing
-    playAudio?: boolean; // whether to play audio annotation on focus
-    playMedia?: boolean; // whether to play start target videos
-    openLocation?: OpenWhere; // where to open a missing document
-    zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
-    toggleTarget?: boolean; // whether to toggle target on and off
-    easeFunc?: 'linear' | 'ease'; // transition method for scrolling
-    pointFocus?: { X: number; Y: number }; // clientX and clientY coordinates to focus on instead of a document target (used by explore mode)
-    contextPath?: Doc[]; // path of inner documents that will also be focused
-}
 export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>;
 // eslint-disable-next-line no-use-before-define
 export type StyleProviderFuncType = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => any;
diff --git a/src/client/views/nodes/FocusViewOptions.ts b/src/client/views/nodes/FocusViewOptions.ts
new file mode 100644
index 000000000..bb0d2b03c
--- /dev/null
+++ b/src/client/views/nodes/FocusViewOptions.ts
@@ -0,0 +1,24 @@
+import { Doc } from '../../../fields/Doc';
+import { Transform } from '../../util/Transform';
+import { OpenWhere } from './OpenWhere';
+
+export interface FocusViewOptions {
+    willPan?: boolean; // determines whether to pan to target document
+    willZoomCentered?: boolean; // determines whether to zoom in on target document.  if zoomScale is 0, this just centers the document
+    zoomScale?: number; // percent of containing frame to zoom into document
+    zoomTime?: number;
+    didMove?: boolean; // whether a document was changed during the showDocument process
+    docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom.  when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
+    instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
+    preview?: boolean; // whether changes should be previewed by the componentView or written to the document
+    effect?: Doc; // animation effect for focus // bcz: needs to be changed to something more generic than a Doc
+    noSelect?: boolean; // whether target should be selected after focusing
+    playAudio?: boolean; // whether to play audio annotation on focus
+    playMedia?: boolean; // whether to play start target videos
+    openLocation?: OpenWhere; // where to open a missing document
+    zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
+    toggleTarget?: boolean; // whether to toggle target on and off
+    easeFunc?: 'linear' | 'ease'; // transition method for scrolling
+    pointFocus?: { X: number; Y: number }; // clientX and clientY coordinates to focus on instead of a document target (used by explore mode)
+    contextPath?: Doc[]; // path of inner documents that will also be focused
+}
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 1b2aefbe2..d83690cdd 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -9,6 +9,7 @@ import { ClientUtils, returnTrue, setupMoveUpEvents } from '../../../../ClientUt
 import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
 import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
@@ -18,8 +19,8 @@ import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { SelectedDocView } from '../../selectedDoc';
 import { StyleProp } from '../../StyleProvider';
-import { OpenWhere } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
+import { OpenWhere } from '../OpenWhere';
 import './FontIconBox.scss';
 import TrailsIcon from './TrailsIcon';
 
@@ -51,15 +52,6 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         super(props);
         makeObservable(this);
     }
-    //
-    // This controls whether fontIconButtons will display labels under their icons or not
-    //
-    public static get ShowIconLabels() {
-        return BoolCast(Doc.UserDoc()._showLabel);
-    }
-    public static set ShowIconLabels(show: boolean) {
-        Doc.UserDoc()._showLabel = show;
-    }
 
     @observable noTooltip = false;
 
@@ -397,3 +389,8 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.FONTICON, {
+    layout: { view: FontIconBox, dataField: 'icon' },
+    options: { acl: '', defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideContextMenu: true, layout_hideLinkButton: true, _width: 40, _height: 40 },
+});
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 31faa7ac3..f32d39aaf 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -7,12 +7,14 @@ import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { Cast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { DocUtils, Docs } from '../../documents/Documents';
+import { 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 { PinProps, PinDocView } from '../PinFuncs';
+import { PinDocView, PinProps } from '../PinFuncs';
 import { FieldView, FieldViewProps } from './FieldView';
 
 @observer
@@ -138,3 +140,8 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.FUNCPLOT, {
+    layout: { view: FunctionPlotBox, dataField: 'data' },
+    options: { acl: '', _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true },
+});
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 8bd5ab03d..d317f46bb 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -15,8 +15,9 @@ import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+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';
@@ -30,9 +31,10 @@ import { OverlayView } from '../OverlayView';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PinDocView, PinProps } from '../PinFuncs';
 import { StyleProp } from '../StyleProvider';
-import { OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
+import { FieldView, FieldViewProps } from './FieldView';
+import { FocusViewOptions } from './FocusViewOptions';
 import './ImageBox.scss';
+import { OpenWhere } from './OpenWhere';
 
 export class ImageEditorData {
     // eslint-disable-next-line no-use-before-define
@@ -546,3 +548,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         input.click();
     };
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.IMG, {
+    layout: { view: ImageBox, dataField: 'data' },
+    options: { acl: '', freeform: '', systemIcon: 'BsFileEarmarkImageFill' },
+});
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index b8296ce51..46bb16e50 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -3,12 +3,13 @@ import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnAlways, returnTrue } from '../../../ClientUtils';
-import { Doc, Field, FieldType, FieldResult } from '../../../fields/Doc';
+import { Doc, Field, FieldResult, FieldType } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { DocCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
 import { SetupDrag } from '../../util/DragManager';
 import { CompiledScript } from '../../util/Scripting';
@@ -17,11 +18,11 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DocumentIconContainer } from './DocumentIcon';
-import { OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { ImageBox } from './ImageBox';
 import './KeyValueBox.scss';
 import { KeyValuePair } from './KeyValuePair';
+import { OpenWhere } from './OpenWhere';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 
 export type KVPScript = {
@@ -339,3 +340,8 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.KVP, {
+    layout: { view: KeyValueBox, dataField: 'data' },
+    options: { acl: '', _layout_fitWidth: true, _height: 150 },
+});
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index a9aa017a1..878b0e54c 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -14,10 +14,11 @@ import { ContextMenu } from '../ContextMenu';
 import { EditableView } from '../EditableView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere, returnEmptyDocViewList } from './DocumentView';
+import { returnEmptyDocViewList } from './DocumentView';
 import { KeyValueBox } from './KeyValueBox';
 import './KeyValueBox.scss';
 import './KeyValuePair.scss';
+import { OpenWhere } from './OpenWhere';
 
 // Represents one row in a key value plane
 
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 89270652c..4dec3506c 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -5,13 +5,14 @@ import { Doc, DocListCast, Field, FieldType } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxBaseComponent } from '../DocComponent';
-import { PinProps, PinDocView } from '../PinFuncs';
+import { PinDocView, PinProps } from '../PinFuncs';
 import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import BigText from './LabelBigText';
@@ -203,3 +204,12 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.LABEL, {
+    layout: { view: LabelBox, dataField: 'title' },
+    options: { acl: '', _singleLine: true, _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true },
+});
+Docs.Prototypes.TemplateMap.set(DocumentType.BUTTON, {
+    layout: { view: LabelBox, dataField: 'title' },
+    options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true },
+});
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index f01905ee1..559b1fcae 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -7,9 +7,12 @@ import { DashColor, lightOrDark, returnFalse } from '../../../ClientUtils';
 import { FieldResult } from '../../../fields/Doc';
 import { DocCss, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
 import { DocCast, NumCast, StrCast } from '../../../fields/Types';
 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';
@@ -263,3 +266,18 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.LINK, {
+    layout: { view: LinkBox, dataField: 'link' },
+    options: {
+        acl: '',
+        childDontRegisterViews: true,
+        layout_hideLinkAnchors: true,
+        _height: 1,
+        _width: 1,
+        link: '',
+        link_description: '',
+        color: 'lightBlue', // lightblue is default color for linking dot and link documents text comment area
+        _dropPropertiesToRemove: new List(['onClick']),
+    },
+});
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index a9cfe6c0e..0936acc15 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -19,9 +19,10 @@ import { SearchUtil } from '../../util/SearchUtil';
 import { SettingsManager } from '../../util/SettingsManager';
 import { Transform } from '../../util/Transform';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DocumentView, OpenWhere } from './DocumentView';
+import { DocumentView } from './DocumentView';
 import { StyleProviderFuncType } from './FieldView';
 import './LinkDocPreview.scss';
+import { OpenWhere } from './OpenWhere';
 
 interface LinkDocPreviewProps {
     linkDoc?: Doc;
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index 501831bca..aa89398f3 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -10,6 +10,8 @@ import { DocumentManager } from '../../util/DocumentManager';
 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
@@ -89,3 +91,7 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         );
     }
 }
+Docs.Prototypes.TemplateMap.set(DocumentType.LOADING, {
+    layout: { view: LoadingBox, dataField: '' },
+    options: { acl: '', _layout_fitWidth: true, _layout_nativeDimEditable: true },
+});
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 60dad314f..50831f8ea 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -18,20 +18,21 @@ import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils';
 import { emptyFunction } from '../../../../Utils';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
 import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types';
+import { DocUtils } from '../../../documents/DocUtils';
 import { DocumentType } from '../../../documents/DocumentTypes';
-import { DocUtils, Docs } from '../../../documents/Documents';
+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 { PinProps, PinDocView } from '../../PinFuncs';
+import { PinDocView, PinProps } from '../../PinFuncs';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
-import { FormattedTextBox } from '../formattedText/FormattedTextBox';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
 import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
 import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility';
 import { MapAnchorMenu } from './MapAnchorMenu';
@@ -355,7 +356,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 
         const targetCreator = (annotationOn: Doc | undefined) => {
             const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
-            FormattedTextBox.SetSelectOnLoad(target);
+            Doc.SetSelectOnLoad(target);
             return target;
         };
         const docView = this.DocumentView?.();
@@ -1360,3 +1361,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.MAP, {
+    layout: { view: MapBox, dataField: 'data' },
+    options: { acl: '', map: '', _height: 600, _width: 800, _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, systemIcon: 'BsFillPinMapFill' },
+});
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 6ccbbbe1c..c69cd8e89 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -32,7 +32,7 @@
 //     addNoteClick = (e: React.PointerEvent) => {
 //         setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => {
 //             const newDoc = Docs.Create.TextDocument('Note', { _layout_autoHeight: true });
-//             FormattedTextBox.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+//             Doc.SetSelectOnLoad(newDoc); // 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.props.place, 'data', newDoc);
 //             this._stack?.scrollToBottom();
 //             e.stopPropagation();
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index 8ebc90157..f3dc44755 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -2,6 +2,8 @@ import * as React from 'react';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { MapBoxContainer } from '../MapboxMapBox/MapboxContainer';
+import { Docs } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
 
 /**
  * Map Pushpin doc class
@@ -28,3 +30,8 @@ export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return <div />;
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.PUSHPIN, {
+    layout: { view: MapPushpinBox, dataField: 'data' },
+    options: { acl: '' },
+});
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index d899fcb9a..3b4ffd4bd 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -10,23 +10,24 @@ import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
 import { DocCss, Highlight } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types';
+import { DocUtils } from '../../../documents/DocUtils';
 import { DocumentType } from '../../../documents/DocumentTypes';
-import { DocUtils, Docs } from '../../../documents/Documents';
+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';
-import { PinProps, PinDocView } from '../../PinFuncs';
+import { PinDocView, PinProps } from '../../PinFuncs';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
 import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
 import '../MapBox/MapBox.scss';
-import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 
 /**
  * MapBox architecture:
@@ -237,7 +238,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
 
         const targetCreator = (annotationOn: Doc | undefined) => {
             const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
-            FormattedTextBox.SetSelectOnLoad(target);
+            Doc.SetSelectOnLoad(target);
             return target;
         };
         const docView = this.DocumentView?.();
diff --git a/src/client/views/nodes/OpenWhere.ts b/src/client/views/nodes/OpenWhere.ts
new file mode 100644
index 000000000..e2a5f1f2a
--- /dev/null
+++ b/src/client/views/nodes/OpenWhere.ts
@@ -0,0 +1,25 @@
+export enum OpenWhereMod {
+    none = '',
+    left = 'left',
+    right = 'right',
+    top = 'top',
+    bottom = 'bottom',
+    keyvalue = 'keyValue',
+}
+export enum OpenWhere {
+    lightbox = 'lightbox',
+    add = 'add',
+    addLeft = 'add:left',
+    addRight = 'add:right',
+    addBottom = 'add:bottom',
+    close = 'close',
+    toggle = 'toggle',
+    toggleRight = 'toggle:right',
+    replace = 'replace',
+    replaceRight = 'replace:right',
+    replaceLeft = 'replace:left',
+    inParent = 'inParent',
+    inParentFromScreen = 'inParentFromScreen',
+    overlay = 'overlay',
+    addRightKeyvalue = 'add:right:keyValue',
+}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index fbf5e018c..5d1874aca 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -16,8 +16,9 @@ import { Cast, FieldValue, ImageCast, NumCast, StrCast, toList } from '../../../
 import { ImageField, PdfField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+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';
@@ -31,9 +32,11 @@ import { Colors } from '../global/globalEnums';
 import { PDFViewer } from '../pdf/PDFViewer';
 import { PinDocView, PinProps } from '../PinFuncs';
 import { SidebarAnnos } from '../SidebarAnnos';
-import { DocumentView, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
+import { DocumentView } from './DocumentView';
+import { FieldView, FieldViewProps } from './FieldView';
+import { FocusViewOptions } from './FocusViewOptions';
 import { ImageBox } from './ImageBox';
+import { OpenWhere } from './OpenWhere';
 import './PDFBox.scss';
 import { CreateImage } from './WebBoxRenderer';
 
@@ -665,3 +668,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         return pdfView ?? this.renderTitleBox;
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.PDF, {
+    layout: { view: PDFBox, dataField: 'data' },
+    options: { acl: '', _layout_curPage: 1, _layout_fitWidth: true, _layout_nativeDimEditable: true, _layout_reflowVertical: true, systemIcon: 'BsFileEarmarkPdfFill' },
+});
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
index ae674d604..f88eb3bca 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
@@ -1,3 +1,11 @@
+/* eslint-disable camelcase */
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable @typescript-eslint/no-unused-vars */
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/no-array-index-key */
+/* eslint-disable react/jsx-props-no-spreading */
+/* eslint-disable no-return-assign */
 import ArrowLeftIcon from '@mui/icons-material/ArrowLeft';
 import ArrowRightIcon from '@mui/icons-material/ArrowRight';
 import PauseIcon from '@mui/icons-material/Pause';
@@ -13,13 +21,15 @@ import { NumListCast } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
 import { ViewBoxAnnotatableComponent } from '../../DocComponent';
-import { FieldView, FieldViewProps } from './../FieldView';
+import { FieldView, FieldViewProps } from '../FieldView';
 import './PhysicsSimulationBox.scss';
 import InputField from './PhysicsSimulationInputField';
 import questions from './PhysicsSimulationQuestions.json';
 import tutorials from './PhysicsSimulationTutorial.json';
 import Wall from './PhysicsSimulationWall';
 import Weight from './PhysicsSimulationWeight';
+import { Docs } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
 
 interface IWallProps {
     length: number;
@@ -204,7 +214,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
 
     componentDidUpdate(prevProps: Readonly<FieldViewProps>) {
         super.componentDidUpdate(prevProps);
-        if (this.xMax !== this._props.PanelWidth() * 0.6 || this.yMax != this._props.PanelHeight()) {
+        if (this.xMax !== this._props.PanelWidth() * 0.6 || this.yMax !== this._props.PanelHeight()) {
             this.xMax = this._props.PanelWidth() * 0.6;
             this.yMax = this._props.PanelHeight();
             this.setupSimulation();
@@ -219,16 +229,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
 
     @action
     setupSimulation = () => {
-        const simulationType = this.simulationType;
+        const { simulationType } = this;
         const mode = this.simulationMode;
         this.dataDoc.simulation_paused = true;
-        if (simulationType != 'Circular Motion') {
+        if (simulationType !== 'Circular Motion') {
             this.dataDoc.mass1_velocityXstart = 0;
             this.dataDoc.mass1_velocityYstart = 0;
             this.dataDoc.mass1_velocityX = 0;
             this.dataDoc.mass1_velocityY = 0;
         }
-        if (mode == 'Freeform') {
+        if (mode === 'Freeform') {
             this.dataDoc.simulation_showForceMagnitudes = true;
             // prettier-ignore
             switch (simulationType) {
@@ -247,9 +257,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                 case 'Circular Motion': this.setupCircular(20); break;
                 case 'Pulley':          this.setupPulley(); break;
                 case 'Suspension':      this.setupSuspension();break;
+                default:
             }
             this._simReset++;
-        } else if (mode == 'Review') {
+        } else if (mode === 'Review') {
             this.dataDoc.simulation_showComponentForces = false;
             this.dataDoc.simulation_showForceMagnitudes = true;
             this.dataDoc.simulation_showAcceleration = false;
@@ -265,12 +276,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                 case 'Circular Motion':  this.setupCircular(0); break;  // TODO - circular motion review problems
                 case 'Pulley':  this.setupPulley(); break; // TODO - pulley tutorial review problems
                 case 'Suspension': this.setupSuspension(); break;  // TODO - suspension tutorial review problems
+                default:
             }
-        } else if (mode == 'Tutorial') {
+        } else if (mode === 'Tutorial') {
             this.dataDoc.simulation_showComponentForces = false;
             this.dataDoc.tutorial_stepNumber = 0;
             this.dataDoc.simulation_showAcceleration = false;
-            if (this.simulationType != 'Circular Motion') {
+            if (this.simulationType !== 'Circular Motion') {
                 this.dataDoc.mass1_velocityX = 0;
                 this.dataDoc.mass1_velocityY = 0;
                 this.dataDoc.simulation_showVelocity = false;
@@ -333,6 +345,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                     this.dataDoc.mass1_forcesStart = JSON.stringify(tutorials.suspension.steps[0].forces);
                     this.dataDoc.simulation_showForceMagnitudes = tutorials.suspension.steps[0].showMagnitude;
                     break;
+                default:
             }
             this._simReset++;
         }
@@ -349,7 +362,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
             magnitude: Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass1,
             directionInDegrees: 180 - 90 - (Math.atan(height / width) * 180) / Math.PI,
         };
-        let frictionForce: IForce = {
+        const frictionForce: IForce = {
             description: 'Static Friction Force',
             magnitude: coefficient * Math.abs(this.gravity) * Math.cos(Math.atan(height / width)) * this.mass1,
             directionInDegrees: 180 - (Math.atan(height / width) * 180) / Math.PI,
@@ -378,7 +391,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
             directionInDegrees: 360 - (Math.atan(height / width) * 180) / Math.PI,
         };
         const gravityForce = this.gravityForce(this.mass1);
-        if (coefficient != 0) {
+        if (coefficient !== 0) {
             this.dataDoc.mass1_forcesStart = JSON.stringify([gravityForce, normalForce, frictionForce]);
             this.dataDoc.mass1_forcesUpdated = JSON.stringify([gravityForce, normalForce, frictionForce]);
             this.dataDoc.mass1_componentForces = JSON.stringify([frictionForce, normalForceComponent, gravityParallel, gravityPerpendicular]);
@@ -396,12 +409,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
         this.dataDoc.wedge_height = Math.tan(radAng) * this.dataDoc.wedge_width;
 
         // update weight position based on updated wedge width/height
-        let yPos = this.yMax - this.dataDoc.wedge_height - this.mass1Radius * Math.cos(radAng) - this.mass1Radius;
-        let xPos = this.xMax * 0.25 + this.mass1Radius * Math.sin(radAng) - this.mass1Radius;
+        const yPos = this.yMax - this.dataDoc.wedge_height - this.mass1Radius * Math.cos(radAng) - this.mass1Radius;
+        const xPos = this.xMax * 0.25 + this.mass1Radius * Math.sin(radAng) - this.mass1Radius;
 
         this.dataDoc.mass1_positionXstart = xPos;
         this.dataDoc.mass1_positionYstart = yPos;
-        if (this.simulationMode == 'Freeform') {
+        if (this.simulationMode === 'Freeform') {
             this.updateForcesWithFriction(NumCast(this.dataDoc.coefficientOfStaticFriction), this.dataDoc.wedge_width, Math.tan(radAng) * this.dataDoc.wedge_width);
         }
     };
@@ -409,7 +422,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
     // In review mode, update forces when coefficient of static friction changed
     updateReviewForcesBasedOnCoefficient = (coefficient: number) => {
         let theta = this.wedgeAngle;
-        let index = this.selectedQuestion.variablesForQuestionSetup.indexOf('theta - max 45');
+        const index = this.selectedQuestion.variablesForQuestionSetup.indexOf('theta - max 45');
         if (index >= 0) {
             theta = NumListCast(this.dataDoc.questionVariables)[index];
         }
@@ -467,26 +480,26 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
             const description = question.answerSolutionDescriptions[i];
             if (!isNaN(NumCast(description))) {
                 solutions.push(NumCast(description));
-            } else if (description == 'solve normal force angle from wedge angle') {
+            } else if (description === 'solve normal force angle from wedge angle') {
                 solutions.push(90 - theta);
-            } else if (description == 'solve normal force magnitude from wedge angle') {
+            } else if (description === 'solve normal force magnitude from wedge angle') {
                 solutions.push(Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI));
-            } else if (description == 'solve static force magnitude from wedge angle given equilibrium') {
-                let normalForceMagnitude = Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI);
-                let normalForceAngle = 90 - theta;
-                let frictionForceAngle = 180 - theta;
-                let frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin((frictionForceAngle * Math.PI) / 180);
+            } else if (description === 'solve static force magnitude from wedge angle given equilibrium') {
+                const normalForceMagnitude = Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI);
+                const normalForceAngle = 90 - theta;
+                const frictionForceAngle = 180 - theta;
+                const frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin((frictionForceAngle * Math.PI) / 180);
                 solutions.push(frictionForceMagnitude);
-            } else if (description == 'solve static force angle from wedge angle given equilibrium') {
+            } else if (description === 'solve static force angle from wedge angle given equilibrium') {
                 solutions.push(180 - theta);
-            } else if (description == 'solve minimum static coefficient from wedge angle given equilibrium') {
-                let normalForceMagnitude = Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI);
-                let normalForceAngle = 90 - theta;
-                let frictionForceAngle = 180 - theta;
-                let frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin((frictionForceAngle * Math.PI) / 180);
-                let frictionCoefficient = frictionForceMagnitude / normalForceMagnitude;
+            } else if (description === 'solve minimum static coefficient from wedge angle given equilibrium') {
+                const normalForceMagnitude = Math.abs(this.gravity) * Math.cos((theta / 180) * Math.PI);
+                const normalForceAngle = 90 - theta;
+                const frictionForceAngle = 180 - theta;
+                const frictionForceMagnitude = (-normalForceMagnitude * Math.sin((normalForceAngle * Math.PI) / 180) + Math.abs(this.gravity)) / Math.sin((frictionForceAngle * Math.PI) / 180);
+                const frictionCoefficient = frictionForceMagnitude / normalForceMagnitude;
                 solutions.push(frictionCoefficient);
-            } else if (description == 'solve maximum wedge angle from coefficient of static friction given equilibrium') {
+            } else if (description === 'solve maximum wedge angle from coefficient of static friction given equilibrium') {
                 solutions.push((Math.atan(muS) * 180) / Math.PI);
             }
         }
@@ -497,38 +510,38 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
     // In review mode, check if input answers match correct answers and optionally generate alert
     checkAnswers = (showAlert: boolean = true) => {
         let error: boolean = false;
-        let epsilon: number = 0.01;
+        const epsilon: number = 0.01;
         if (this.selectedQuestion) {
             for (let i = 0; i < this.selectedQuestion.answerParts.length; i++) {
-                if (this.selectedQuestion.answerParts[i] == 'force of gravity') {
+                if (this.selectedQuestion.answerParts[i] === 'force of gravity') {
                     if (Math.abs(NumCast(this.dataDoc.review_GravityMagnitude) - this.selectedSolutions[i]) > epsilon) {
                         error = true;
                     }
-                } else if (this.selectedQuestion.answerParts[i] == 'angle of gravity') {
+                } else if (this.selectedQuestion.answerParts[i] === 'angle of gravity') {
                     if (Math.abs(NumCast(this.dataDoc.review_GravityAngle) - this.selectedSolutions[i]) > epsilon) {
                         error = true;
                     }
-                } else if (this.selectedQuestion.answerParts[i] == 'normal force') {
+                } else if (this.selectedQuestion.answerParts[i] === 'normal force') {
                     if (Math.abs(NumCast(this.dataDoc.review_NormalMagnitude) - this.selectedSolutions[i]) > epsilon) {
                         error = true;
                     }
-                } else if (this.selectedQuestion.answerParts[i] == 'angle of normal force') {
+                } else if (this.selectedQuestion.answerParts[i] === 'angle of normal force') {
                     if (Math.abs(NumCast(this.dataDoc.review_NormalAngle) - this.selectedSolutions[i]) > epsilon) {
                         error = true;
                     }
-                } else if (this.selectedQuestion.answerParts[i] == 'force of static friction') {
+                } else if (this.selectedQuestion.answerParts[i] === 'force of static friction') {
                     if (Math.abs(NumCast(this.dataDoc.review_StaticMagnitude) - this.selectedSolutions[i]) > epsilon) {
                         error = true;
                     }
-                } else if (this.selectedQuestion.answerParts[i] == 'angle of static friction') {
+                } else if (this.selectedQuestion.answerParts[i] === 'angle of static friction') {
                     if (Math.abs(NumCast(this.dataDoc.review_StaticAngle) - this.selectedSolutions[i]) > epsilon) {
                         error = true;
                     }
-                } else if (this.selectedQuestion.answerParts[i] == 'coefficient of static friction') {
+                } else if (this.selectedQuestion.answerParts[i] === 'coefficient of static friction') {
                     if (Math.abs(NumCast(this.dataDoc.coefficientOfStaticFriction) - this.selectedSolutions[i]) > epsilon) {
                         error = true;
                     }
-                } else if (this.selectedQuestion.answerParts[i] == 'wedge angle') {
+                } else if (this.selectedQuestion.answerParts[i] === 'wedge angle') {
                     if (Math.abs(this.wedgeAngle - this.selectedSolutions[i]) > epsilon) {
                         error = true;
                     }
@@ -539,7 +552,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
             this.dataDoc.simulation_paused = false;
             setTimeout(() => (this.dataDoc.simulation_paused = true), 3000);
         }
-        if (this.selectedQuestion.goal == 'noMovement') {
+        if (this.selectedQuestion.goal === 'noMovement') {
             this.dataDoc.noMovement = !error;
         }
     };
@@ -571,12 +584,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
             let wedge_angle = 0;
 
             for (let i = 0; i < question.variablesForQuestionSetup.length; i++) {
-                if (question.variablesForQuestionSetup[i] == 'theta - max 45') {
-                    let randValue = Math.floor(Math.random() * 44 + 1);
+                if (question.variablesForQuestionSetup[i] === 'theta - max 45') {
+                    const randValue = Math.floor(Math.random() * 44 + 1);
                     vars.push(randValue);
                     wedge_angle = randValue;
-                } else if (question.variablesForQuestionSetup[i] == 'coefficient of static friction') {
-                    let randValue = Math.round(Math.random() * 1000) / 1000;
+                } else if (question.variablesForQuestionSetup[i] === 'coefficient of static friction') {
+                    const randValue = Math.round(Math.random() * 1000) / 1000;
                     vars.push(randValue);
                     coefficient = randValue;
                 }
@@ -589,7 +602,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
         let q = '';
         for (let i = 0; i < question.questionSetup.length; i++) {
             q += question.questionSetup[i];
-            if (i != question.questionSetup.length - 1) {
+            if (i !== question.questionSetup.length - 1) {
                 q += vars[i];
                 if (question.variablesForQuestionSetup[i].includes('theta')) {
                     q += ' degree (≈' + Math.round((1000 * (vars[i] * Math.PI)) / 180) / 1000 + ' rad)';
@@ -601,7 +614,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
         this.dataDoc.questionPartOne = q;
         this.dataDoc.questionPartTwo = question.question;
         this.dataDoc.answers = new List<number>(this.getAnswersToQuestion(question, vars));
-        //this.dataDoc.simulation_reset = (!this.dataDoc.simulation_reset);
+        // this.dataDoc.simulation_reset = (!this.dataDoc.simulation_reset);
     };
 
     // Default setup for uniform circular motion simulation
@@ -610,8 +623,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
         this.dataDoc.simulation_showComponentForces = false;
         this.dataDoc.mass1_velocityYstart = 0;
         this.dataDoc.mass1_velocityXstart = value;
-        let xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius;
-        let yPos = (this.yMax + this.yMin) / 2 + this.circularMotionRadius - this.mass1Radius;
+        const xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius;
+        const yPos = (this.yMax + this.yMin) / 2 + this.circularMotionRadius - this.mass1Radius;
         this.dataDoc.mass1_positionYstart = yPos;
         this.dataDoc.mass1_positionXstart = xPos;
         const tensionForce: IForce = {
@@ -680,13 +693,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
     // Default setup for suspension simulation
     @action
     setupSuspension = () => {
-        let xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius;
-        let yPos = this.yMin + 200;
+        const xPos = (this.xMax + this.xMin) / 2 - this.mass1Radius;
+        const yPos = this.yMin + 200;
         this.dataDoc.mass1_positionYstart = yPos;
         this.dataDoc.mass1_positionXstart = xPos;
         this.dataDoc.mass1_positionY = this.getDisplayYPos(yPos);
         this.dataDoc.mass1_positionX = xPos;
-        let tensionMag = (this.mass1 * Math.abs(this.gravity)) / (2 * Math.sin(Math.PI / 4));
+        const tensionMag = (this.mass1 * Math.abs(this.gravity)) / (2 * Math.sin(Math.PI / 4));
         const tensionForce1: IForce = {
             description: 'Tension',
             magnitude: tensionMag,
@@ -891,7 +904,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                     setVelocity={this.setVelocity1}
                                     setAcceleration={this.setAcceleration1}
                                 />
-                                {this.simulationType == 'Pulley' && (
+                                {this.simulationType === 'Pulley' && (
                                     <Weight
                                         {...commonWeightProps}
                                         color="green"
@@ -916,7 +929,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                 )}
                             </div>
                             <div style={{ position: 'absolute', transformOrigin: 'top left', top: 0, left: 0, width: '100%', height: '100%' }}>
-                                {(this.simulationType == 'One Weight' || this.simulationType == 'Inclined Plane') &&
+                                {(this.simulationType === 'One Weight' || this.simulationType === 'Inclined Plane') &&
                                     this.wallPositions?.map((element, index) => <Wall key={index} length={element.length} xPos={element.xPos} yPos={element.yPos} angleInDegrees={element.angleInDegrees} />)}
                             </div>
                         </div>
@@ -927,17 +940,17 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                         style={{ overflow: 'auto', height: `${Math.max(1, 800 / this._props.PanelWidth()) * 100}%`, transform: `scale(${Math.min(1, this._props.PanelWidth() / 850)})` }}>
                         <div className="mechanicsSimulationControls">
                             <Stack direction="row" spacing={1}>
-                                {this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && (
+                                {this.dataDoc.simulation_paused && this.simulationMode !== 'Tutorial' && (
                                     <IconButton onClick={() => (this.dataDoc.simulation_paused = false)}>
                                         <PlayArrowIcon />
                                     </IconButton>
                                 )}
-                                {!this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && (
+                                {!this.dataDoc.simulation_paused && this.simulationMode !== 'Tutorial' && (
                                     <IconButton onClick={() => (this.dataDoc.simulation_paused = true)}>
                                         <PauseIcon />
                                     </IconButton>
                                 )}
-                                {this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && (
+                                {this.dataDoc.simulation_paused && this.simulationMode !== 'Tutorial' && (
                                     <IconButton onClick={action(() => this._simReset++)}>
                                         <ReplayIcon />
                                     </IconButton>
@@ -974,15 +987,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                 </select>
                             </div>
                         </div>
-                        {this.simulationMode == 'Review' && this.simulationType != 'Inclined Plane' && (
+                        {this.simulationMode === 'Review' && this.simulationType !== 'Inclined Plane' && (
                             <div className="wordProblemBox">
-                                <p>
-                                    <>{this.simulationType} review problems in progress!</>
-                                </p>
+                                <p>{this.simulationType} review problems in progress!</p>
                                 <hr />
                             </div>
                         )}
-                        {this.simulationMode == 'Review' && this.simulationType == 'Inclined Plane' && (
+                        {this.simulationMode === 'Review' && this.simulationType === 'Inclined Plane' && (
                             <div>
                                 {!this.dataDoc.hintDialogueOpen && (
                                     <IconButton
@@ -995,7 +1006,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                         <QuestionMarkIcon />
                                     </IconButton>
                                 )}
-                                <Dialog maxWidth={'sm'} fullWidth={true} open={BoolCast(this.dataDoc.hintDialogueOpen)} onClose={() => (this.dataDoc.hintDialogueOpen = false)}>
+                                <Dialog maxWidth="sm" fullWidth open={BoolCast(this.dataDoc.hintDialogueOpen)} onClose={() => (this.dataDoc.hintDialogueOpen = false)}>
                                     <DialogTitle>Hints</DialogTitle>
                                     <DialogContent>
                                         {this.selectedQuestion.hints?.map((hint: any, index: number) => (
@@ -1030,12 +1041,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                 dataDoc={this.dataDoc}
                                                 prop="review_GravityMagnitude"
                                                 step={0.1}
-                                                unit={'N'}
+                                                unit="N"
                                                 upperBound={50}
                                                 value={NumCast(this.dataDoc.review_GravityMagnitude)}
                                                 showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
                                                 correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('force of gravity')]}
-                                                labelWidth={'7em'}
+                                                labelWidth="7em"
                                             />
                                         )}
                                         {this.selectedQuestion.answerParts.includes('angle of gravity') && (
@@ -1045,13 +1056,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                 dataDoc={this.dataDoc}
                                                 prop="review_GravityAngle"
                                                 step={1}
-                                                unit={'°'}
+                                                unit="°"
                                                 upperBound={360}
                                                 value={NumCast(this.dataDoc.review_GravityAngle)}
-                                                radianEquivalent={true}
+                                                radianEquivalent
                                                 showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
                                                 correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of gravity')]}
-                                                labelWidth={'7em'}
+                                                labelWidth="7em"
                                             />
                                         )}
                                         {this.selectedQuestion.answerParts.includes('normal force') && (
@@ -1061,12 +1072,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                 dataDoc={this.dataDoc}
                                                 prop="review_NormalMagnitude"
                                                 step={0.1}
-                                                unit={'N'}
+                                                unit="N"
                                                 upperBound={50}
                                                 value={NumCast(this.dataDoc.review_NormalMagnitude)}
                                                 showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
                                                 correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('normal force')]}
-                                                labelWidth={'7em'}
+                                                labelWidth="7em"
                                             />
                                         )}
                                         {this.selectedQuestion.answerParts.includes('angle of normal force') && (
@@ -1076,13 +1087,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                 dataDoc={this.dataDoc}
                                                 prop="review_NormalAngle"
                                                 step={1}
-                                                unit={'°'}
+                                                unit="°"
                                                 upperBound={360}
                                                 value={NumCast(this.dataDoc.review_NormalAngle)}
-                                                radianEquivalent={true}
+                                                radianEquivalent
                                                 showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
                                                 correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of normal force')]}
-                                                labelWidth={'7em'}
+                                                labelWidth="7em"
                                             />
                                         )}
                                         {this.selectedQuestion.answerParts.includes('force of static friction') && (
@@ -1092,12 +1103,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                 dataDoc={this.dataDoc}
                                                 prop="review_StaticMagnitude"
                                                 step={0.1}
-                                                unit={'N'}
+                                                unit="N"
                                                 upperBound={50}
                                                 value={NumCast(this.dataDoc.review_StaticMagnitude)}
                                                 showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
                                                 correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('force of static friction')]}
-                                                labelWidth={'7em'}
+                                                labelWidth="7em"
                                             />
                                         )}
                                         {this.selectedQuestion.answerParts.includes('angle of static friction') && (
@@ -1107,13 +1118,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                 dataDoc={this.dataDoc}
                                                 prop="review_StaticAngle"
                                                 step={1}
-                                                unit={'°'}
+                                                unit="°"
                                                 upperBound={360}
                                                 value={NumCast(this.dataDoc.review_StaticAngle)}
-                                                radianEquivalent={true}
+                                                radianEquivalent
                                                 showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
                                                 correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of static friction')]}
-                                                labelWidth={'7em'}
+                                                labelWidth="7em"
                                             />
                                         )}
                                         {this.selectedQuestion.answerParts.includes('coefficient of static friction') && (
@@ -1127,7 +1138,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                 dataDoc={this.dataDoc}
                                                 prop="coefficientOfStaticFriction"
                                                 step={0.1}
-                                                unit={''}
+                                                unit=""
                                                 upperBound={1}
                                                 value={NumCast(this.dataDoc.coefficientOfStaticFriction)}
                                                 effect={this.updateReviewForcesBasedOnCoefficient}
@@ -1142,14 +1153,14 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                 dataDoc={this.dataDoc}
                                                 prop="wedge_angle"
                                                 step={1}
-                                                unit={'°'}
+                                                unit="°"
                                                 upperBound={49}
                                                 value={this.wedgeAngle}
                                                 effect={(val: number) => {
                                                     this.changeWedgeBasedOnNewAngle(val);
                                                     this.updateReviewForcesBasedOnAngle(val);
                                                 }}
-                                                radianEquivalent={true}
+                                                radianEquivalent
                                                 showIcon={BoolCast(this.dataDoc.simulation_showIcon)}
                                                 correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('wedge angle')]}
                                             />
@@ -1158,7 +1169,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                 </div>
                             </div>
                         )}
-                        {this.simulationMode == 'Tutorial' && (
+                        {this.simulationMode === 'Tutorial' && (
                             <div className="wordProblemBox">
                                 <div className="question">
                                     <h2>Problem</h2>
@@ -1180,7 +1191,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             this.dataDoc.mass1_forcesUpdated = JSON.stringify(this.tutorial.steps[step].forces);
                                             this.dataDoc.simulation_showForceMagnitudes = this.tutorial.steps[step].showMagnitude;
                                         }}
-                                        disabled={this.dataDoc.tutorial_stepNumber == 0}>
+                                        disabled={this.dataDoc.tutorial_stepNumber === 0}>
                                         <ArrowLeftIcon />
                                     </IconButton>
                                     <div>
@@ -1204,8 +1215,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                     </IconButton>
                                 </div>
                                 <div>
-                                    {(this.simulationType == 'One Weight' || this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') && <p>Resources</p>}
-                                    {this.simulationType == 'One Weight' && (
+                                    {(this.simulationType === 'One Weight' || this.simulationType === 'Inclined Plane' || this.simulationType === 'Pendulum') && <p>Resources</p>}
+                                    {this.simulationType === 'One Weight' && (
                                         <ul>
                                             <li>
                                                 <a
@@ -1233,7 +1244,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             </li>
                                         </ul>
                                     )}
-                                    {this.simulationType == 'Inclined Plane' && (
+                                    {this.simulationType === 'Inclined Plane' && (
                                         <ul>
                                             <li>
                                                 <a
@@ -1261,7 +1272,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             </li>
                                         </ul>
                                     )}
-                                    {this.simulationType == 'Pendulum' && (
+                                    {this.simulationType === 'Pendulum' && (
                                         <ul>
                                             <li>
                                                 <a
@@ -1280,7 +1291,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                 </div>
                             </div>
                         )}
-                        {this.simulationMode == 'Review' && this.simulationType == 'Inclined Plane' && (
+                        {this.simulationMode === 'Review' && this.simulationType === 'Inclined Plane' && (
                             <div
                                 style={{
                                     display: 'flex',
@@ -1318,11 +1329,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                 </div>
                             </div>
                         )}
-                        {this.simulationMode == 'Freeform' && (
+                        {this.simulationMode === 'Freeform' && (
                             <div className="vars">
                                 <FormControl component="fieldset">
                                     <FormGroup>
-                                        {this.simulationType == 'One Weight' && (
+                                        {this.simulationType === 'One Weight' && (
                                             <FormControlLabel
                                                 control={<Checkbox checked={BoolCast(this.dataDoc.elasticCollisions)} onChange={() => (this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions)} />}
                                                 label="Make collisions elastic"
@@ -1334,7 +1345,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             label="Show force vectors"
                                             labelPlacement="start"
                                         />
-                                        {(this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') && (
+                                        {(this.simulationType === 'Inclined Plane' || this.simulationType === 'Pendulum') && (
                                             <FormControlLabel
                                                 control={<Checkbox checked={BoolCast(this.dataDoc.simulation_showComponentForces)} onChange={() => (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />}
                                                 label="Show component force vectors"
@@ -1351,80 +1362,80 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             label="Show velocity vector"
                                             labelPlacement="start"
                                         />
-                                        <InputField label={<Box>Speed</Box>} lowerBound={1} dataDoc={this.dataDoc} prop="simulation_speed" step={1} unit={'x'} upperBound={10} value={NumCast(this.dataDoc.simulation_speed, 2)} labelWidth={'5em'} />
-                                        {this.dataDoc.simulation_paused && this.simulationType != 'Circular Motion' && (
+                                        <InputField label={<Box>Speed</Box>} lowerBound={1} dataDoc={this.dataDoc} prop="simulation_speed" step={1} unit="x" upperBound={10} value={NumCast(this.dataDoc.simulation_speed, 2)} labelWidth="5em" />
+                                        {this.dataDoc.simulation_paused && this.simulationType !== 'Circular Motion' && (
                                             <InputField
                                                 label={<Box>Gravity</Box>}
                                                 lowerBound={-30}
                                                 dataDoc={this.dataDoc}
                                                 prop="gravity"
                                                 step={0.01}
-                                                unit={'m/s2'}
+                                                unit="m/s2"
                                                 upperBound={0}
                                                 value={NumCast(this.dataDoc.simulation_gravity, -9.81)}
                                                 effect={(val: number) => this.setupSimulation()}
-                                                labelWidth={'5em'}
+                                                labelWidth="5em"
                                             />
                                         )}
-                                        {this.dataDoc.simulation_paused && this.simulationType != 'Pulley' && (
+                                        {this.dataDoc.simulation_paused && this.simulationType !== 'Pulley' && (
                                             <InputField
                                                 label={<Box>Mass</Box>}
                                                 lowerBound={1}
                                                 dataDoc={this.dataDoc}
                                                 prop="mass1"
                                                 step={0.1}
-                                                unit={'kg'}
+                                                unit="kg"
                                                 upperBound={5}
                                                 value={this.mass1 ?? 1}
                                                 effect={(val: number) => this.setupSimulation()}
-                                                labelWidth={'5em'}
+                                                labelWidth="5em"
                                             />
                                         )}
-                                        {this.dataDoc.simulation_paused && this.simulationType == 'Pulley' && (
+                                        {this.dataDoc.simulation_paused && this.simulationType === 'Pulley' && (
                                             <InputField
                                                 label={<Box>Red mass</Box>}
                                                 lowerBound={1}
                                                 dataDoc={this.dataDoc}
                                                 prop="mass1"
                                                 step={0.1}
-                                                unit={'kg'}
+                                                unit="kg"
                                                 upperBound={5}
                                                 value={this.mass1 ?? 1}
                                                 effect={(val: number) => this.setupSimulation()}
-                                                labelWidth={'5em'}
+                                                labelWidth="5em"
                                             />
                                         )}
-                                        {this.dataDoc.simulation_paused && this.simulationType == 'Pulley' && (
+                                        {this.dataDoc.simulation_paused && this.simulationType === 'Pulley' && (
                                             <InputField
                                                 label={<Box>Blue mass</Box>}
                                                 lowerBound={1}
                                                 dataDoc={this.dataDoc}
                                                 prop="mass2"
                                                 step={0.1}
-                                                unit={'kg'}
+                                                unit="kg"
                                                 upperBound={5}
                                                 value={this.mass2 ?? 1}
                                                 effect={(val: number) => this.setupSimulation()}
-                                                labelWidth={'5em'}
+                                                labelWidth="5em"
                                             />
                                         )}
-                                        {this.dataDoc.simulation_paused && this.simulationType == 'Circular Motion' && (
+                                        {this.dataDoc.simulation_paused && this.simulationType === 'Circular Motion' && (
                                             <InputField
                                                 label={<Box>Rod length</Box>}
                                                 lowerBound={100}
                                                 dataDoc={this.dataDoc}
                                                 prop="circularMotionRadius"
                                                 step={5}
-                                                unit={'m'}
+                                                unit="m"
                                                 upperBound={250}
                                                 value={this.circularMotionRadius}
                                                 effect={(val: number) => this.setupSimulation()}
-                                                labelWidth={'5em'}
+                                                labelWidth="5em"
                                             />
                                         )}
                                     </FormGroup>
                                 </FormControl>
-                                {this.simulationType == 'Spring' && this.dataDoc.simulation_paused && (
+                                {this.simulationType === 'Spring' && this.dataDoc.simulation_paused && (
                                     <div>
                                         <InputField
                                             label={<Typography color="inherit">Spring stiffness</Typography>}
@@ -1432,13 +1443,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             dataDoc={this.dataDoc}
                                             prop="spring_constant"
                                             step={1}
-                                            unit={'N/m'}
+                                            unit="N/m"
                                             upperBound={500}
                                             value={this.springConstant}
                                             effect={action(() => this._simReset++)}
                                             radianEquivalent={false}
-                                            mode={'Freeform'}
-                                            labelWidth={'7em'}
+                                            mode="Freeform"
+                                            labelWidth="7em"
                                         />
                                         <InputField
                                             label={<Typography color="inherit">Rest length</Typography>}
@@ -1452,7 +1463,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             effect={action(() => this._simReset++)}
                                             radianEquivalent={false}
                                             mode="Freeform"
-                                            labelWidth={'7em'}
+                                            labelWidth="7em"
                                         />
                                         <InputField
                                             label={<Typography color="inherit">Starting displacement</Typography>}
@@ -1470,11 +1481,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             })}
                                             radianEquivalent={false}
                                             mode="Freeform"
-                                            labelWidth={'7em'}
+                                            labelWidth="7em"
                                         />
                                     </div>
                                 )}
-                                {this.simulationType == 'Inclined Plane' && this.dataDoc.simulation_paused && (
+                                {this.simulationType === 'Inclined Plane' && this.dataDoc.simulation_paused && (
                                     <div>
                                         <InputField
                                             label={<Box>&theta;</Box>}
@@ -1482,16 +1493,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             dataDoc={this.dataDoc}
                                             prop="wedge_angle"
                                             step={1}
-                                            unit={'°'}
+                                            unit="°"
                                             upperBound={49}
                                             value={this.wedgeAngle}
                                             effect={action((val: number) => {
                                                 this.changeWedgeBasedOnNewAngle(val);
                                                 this._simReset++;
                                             })}
-                                            radianEquivalent={true}
-                                            mode={'Freeform'}
-                                            labelWidth={'2em'}
+                                            radianEquivalent
+                                            mode="Freeform"
+                                            labelWidth="2em"
                                         />
                                         <InputField
                                             label={
@@ -1503,7 +1514,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             dataDoc={this.dataDoc}
                                             prop="coefficientOfStaticFriction"
                                             step={0.1}
-                                            unit={''}
+                                            unit=""
                                             upperBound={1}
                                             value={NumCast(this.dataDoc.coefficientOfStaticFriction) ?? 0}
                                             effect={action((val: number) => {
@@ -1513,8 +1524,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                 }
                                                 this._simReset++;
                                             })}
-                                            mode={'Freeform'}
-                                            labelWidth={'2em'}
+                                            mode="Freeform"
+                                            labelWidth="2em"
                                         />
                                         <InputField
                                             label={
@@ -1526,16 +1537,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             dataDoc={this.dataDoc}
                                             prop="coefficientOfKineticFriction"
                                             step={0.1}
-                                            unit={''}
+                                            unit=""
                                             upperBound={NumCast(this.dataDoc.coefficientOfStaticFriction)}
                                             value={NumCast(this.dataDoc.coefficientOfKineticFriction) ?? 0}
                                             effect={action(() => this._simReset++)}
-                                            mode={'Freeform'}
-                                            labelWidth={'2em'}
+                                            mode="Freeform"
+                                            labelWidth="2em"
                                         />
                                     </div>
                                 )}
-                                {this.simulationType == 'Inclined Plane' && !this.dataDoc.simulation_paused && (
+                                {this.simulationType === 'Inclined Plane' && !this.dataDoc.simulation_paused && (
                                     <Typography>
                                         <>
                                             &theta;: {Math.round(this.wedgeAngle * 100) / 100}° ≈ {Math.round(((this.wedgeAngle * Math.PI) / 180) * 100) / 100} rad
@@ -1546,12 +1557,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                         </>
                                     </Typography>
                                 )}
-                                {this.simulationType == 'Pendulum' && !this.dataDoc.simulation_paused && (
+                                {this.simulationType === 'Pendulum' && !this.dataDoc.simulation_paused && (
                                     <Typography>
                                         &theta;: {Math.round(this.pendulumAngle * 100) / 100}° ≈ {Math.round(((this.pendulumAngle * Math.PI) / 180) * 100) / 100} rad
                                     </Typography>
                                 )}
-                                {this.simulationType == 'Pendulum' && this.dataDoc.simulation_paused && (
+                                {this.simulationType === 'Pendulum' && this.dataDoc.simulation_paused && (
                                     <div>
                                         <InputField
                                             label={<Box>Angle</Box>}
@@ -1559,13 +1570,13 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             dataDoc={this.dataDoc}
                                             prop="pendulum_angle"
                                             step={1}
-                                            unit={'°'}
+                                            unit="°"
                                             upperBound={59}
                                             value={NumCast(this.dataDoc.pendulum_angle, 30)}
                                             effect={action(value => {
                                                 this.dataDoc.pendulum_angleStart = value;
                                                 this.dataDoc.pendulum_lengthStart = this.dataDoc.pendulum_length;
-                                                if (this.simulationType == 'Pendulum') {
+                                                if (this.simulationType === 'Pendulum') {
                                                     const mag = this.mass1 * Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180);
 
                                                     const forceOfTension: IForce = {
@@ -1598,7 +1609,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                     this._simReset++;
                                                 }
                                             })}
-                                            radianEquivalent={true}
+                                            radianEquivalent
                                             mode="Freeform"
                                             labelWidth="5em"
                                         />
@@ -1612,7 +1623,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             upperBound={400}
                                             value={Math.round(this.pendulumLength)}
                                             effect={action(value => {
-                                                if (this.simulationType == 'Pendulum') {
+                                                if (this.simulationType === 'Pendulum') {
                                                     this.dataDoc.pendulum_angleStart = this.pendulumAngle;
                                                     this.dataDoc.pendulum_lengthStart = value;
                                                     this._simReset++;
@@ -1627,11 +1638,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                             </div>
                         )}
                         <div className="mechanicsSimulationEquation">
-                            {this.simulationMode == 'Freeform' && (
+                            {this.simulationMode === 'Freeform' && (
                                 <table>
                                     <tbody>
                                         <tr>
-                                            <td>{this.simulationType == 'Pulley' ? 'Red Weight' : ''}</td>
+                                            <td>{this.simulationType === 'Pulley' ? 'Red Weight' : ''}</td>
                                             <td>X</td>
                                             <td>Y</td>
                                         </tr>
@@ -1646,36 +1657,34 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             >
                                                 <Box>Position</Box>
                                             </td>
-                                            {(!this.dataDoc.simulation_paused || this.simulationType == 'Inclined Plane' || this.simulationType == 'Circular Motion' || this.simulationType == 'Pulley') && (
-                                                <td style={{ cursor: 'default' }}>
-                                                    <>{this.dataDoc.mass1_positionX} m</>
-                                                </td>
+                                            {(!this.dataDoc.simulation_paused || this.simulationType === 'Inclined Plane' || this.simulationType === 'Circular Motion' || this.simulationType === 'Pulley') && (
+                                                <td style={{ cursor: 'default' }}>{this.dataDoc.mass1_positionX + ''} m</td>
                                             )}{' '}
-                                            {this.dataDoc.simulation_paused && this.simulationType != 'Inclined Plane' && this.simulationType != 'Circular Motion' && this.simulationType != 'Pulley' && (
+                                            {this.dataDoc.simulation_paused && this.simulationType !== 'Inclined Plane' && this.simulationType !== 'Circular Motion' && this.simulationType !== 'Pulley' && (
                                                 <td
                                                     style={{
                                                         cursor: 'default',
                                                     }}>
                                                     <InputField
-                                                        lowerBound={this.simulationType == 'Projectile' ? 1 : (this.xMax + this.xMin) / 4 - this.radius - 15}
+                                                        lowerBound={this.simulationType === 'Projectile' ? 1 : (this.xMax + this.xMin) / 4 - this.radius - 15}
                                                         dataDoc={this.dataDoc}
                                                         prop="mass1_positionX"
                                                         step={1}
-                                                        unit={'m'}
-                                                        upperBound={this.simulationType == 'Projectile' ? this.xMax - 110 : (3 * (this.xMax + this.xMin)) / 4 - this.radius / 2 - 15}
+                                                        unit="m"
+                                                        upperBound={this.simulationType === 'Projectile' ? this.xMax - 110 : (3 * (this.xMax + this.xMin)) / 4 - this.radius / 2 - 15}
                                                         value={NumCast(this.dataDoc.mass1_positionX)}
                                                         effect={value => {
                                                             this.dataDoc.mass1_xChange = value;
-                                                            if (this.simulationType == 'Suspension') {
-                                                                let x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200;
-                                                                let x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius;
-                                                                let deltaX1 = value + this.radius - x1rod;
-                                                                let deltaX2 = x2rod - (value + this.radius);
-                                                                let deltaY = this.getYPosFromDisplay(NumCast(this.dataDoc.mass1_positionY)) + this.radius;
+                                                            if (this.simulationType === 'Suspension') {
+                                                                const x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200;
+                                                                const x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius;
+                                                                const deltaX1 = value + this.radius - x1rod;
+                                                                const deltaX2 = x2rod - (value + this.radius);
+                                                                const deltaY = this.getYPosFromDisplay(NumCast(this.dataDoc.mass1_positionY)) + this.radius;
                                                                 let dir1T = Math.PI - Math.atan(deltaY / deltaX1);
                                                                 let dir2T = Math.atan(deltaY / deltaX2);
-                                                                let tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
-                                                                let tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
+                                                                const tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
+                                                                const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
                                                                 dir1T = (dir1T * 180) / Math.PI;
                                                                 dir2T = (dir2T * 180) / Math.PI;
                                                                 const tensionForce1: IForce = {
@@ -1692,15 +1701,15 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                                 this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]);
                                                             }
                                                         }}
-                                                        small={true}
+                                                        small
                                                         mode="Freeform"
                                                     />
                                                 </td>
                                             )}{' '}
-                                            {(!this.dataDoc.simulation_paused || this.simulationType == 'Inclined Plane' || this.simulationType == 'Circular Motion' || this.simulationType == 'Pulley') && (
+                                            {(!this.dataDoc.simulation_paused || this.simulationType === 'Inclined Plane' || this.simulationType === 'Circular Motion' || this.simulationType === 'Pulley') && (
                                                 <td style={{ cursor: 'default' }}>{`${NumCast(this.dataDoc.mass1_positionY)} m`}</td>
                                             )}{' '}
-                                            {this.dataDoc.simulation_paused && this.simulationType != 'Inclined Plane' && this.simulationType != 'Circular Motion' && this.simulationType != 'Pulley' && (
+                                            {this.dataDoc.simulation_paused && this.simulationType !== 'Inclined Plane' && this.simulationType !== 'Circular Motion' && this.simulationType !== 'Pulley' && (
                                                 <td
                                                     style={{
                                                         cursor: 'default',
@@ -1715,16 +1724,16 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                         value={NumCast(this.dataDoc.mass1_positionY)}
                                                         effect={value => {
                                                             this.dataDoc.mass1_yChange = value;
-                                                            if (this.simulationType == 'Suspension') {
-                                                                let x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200;
-                                                                let x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius;
-                                                                let deltaX1 = NumCast(this.dataDoc.mass1_positionX) + this.radius - x1rod;
-                                                                let deltaX2 = x2rod - (NumCast(this.dataDoc.mass1_positionX) + this.radius);
-                                                                let deltaY = this.getYPosFromDisplay(value) + this.radius;
+                                                            if (this.simulationType === 'Suspension') {
+                                                                const x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200;
+                                                                const x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius;
+                                                                const deltaX1 = NumCast(this.dataDoc.mass1_positionX) + this.radius - x1rod;
+                                                                const deltaX2 = x2rod - (NumCast(this.dataDoc.mass1_positionX) + this.radius);
+                                                                const deltaY = this.getYPosFromDisplay(value) + this.radius;
                                                                 let dir1T = Math.PI - Math.atan(deltaY / deltaX1);
                                                                 let dir2T = Math.atan(deltaY / deltaX2);
-                                                                let tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
-                                                                let tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
+                                                                const tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T));
+                                                                const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T);
                                                                 dir1T = (dir1T * 180) / Math.PI;
                                                                 dir2T = (dir2T * 180) / Math.PI;
                                                                 const tensionForce1: IForce = {
@@ -1741,7 +1750,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                                 this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]);
                                                             }
                                                         }}
-                                                        small={true}
+                                                        small
                                                         mode="Freeform"
                                                     />
                                                 </td>
@@ -1758,10 +1767,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                             >
                                                 <Box>Velocity</Box>
                                             </td>
-                                            {(!this.dataDoc.simulation_paused || (this.simulationType != 'One Weight' && this.simulationType != 'Circular Motion')) && (
+                                            {(!this.dataDoc.simulation_paused || (this.simulationType !== 'One Weight' && this.simulationType !== 'Circular Motion')) && (
                                                 <td style={{ cursor: 'default' }}>{`${NumCast(this.dataDoc.mass1_velocityX)} m/s`}</td>
                                             )}{' '}
-                                            {this.dataDoc.simulation_paused && (this.simulationType == 'One Weight' || this.simulationType == 'Circular Motion') && (
+                                            {this.dataDoc.simulation_paused && (this.simulationType === 'One Weight' || this.simulationType === 'Circular Motion') && (
                                                 <td
                                                     style={{
                                                         cursor: 'default',
@@ -1771,24 +1780,20 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                         dataDoc={this.dataDoc}
                                                         prop="mass1_velocityX"
                                                         step={1}
-                                                        unit={'m/s'}
+                                                        unit="m/s"
                                                         upperBound={50}
                                                         value={NumCast(this.dataDoc.mass1_velocityX)}
                                                         effect={action(value => {
                                                             this.dataDoc.mass1_velocityXstart = value;
                                                             this._simReset++;
                                                         })}
-                                                        small={true}
+                                                        small
                                                         mode="Freeform"
                                                     />
                                                 </td>
                                             )}{' '}
-                                            {(!this.dataDoc.simulation_paused || this.simulationType != 'One Weight') && (
-                                                <td style={{ cursor: 'default' }}>
-                                                    <>{this.dataDoc.mass1_velocityY} m/s</>
-                                                </td>
-                                            )}{' '}
-                                            {this.dataDoc.simulation_paused && this.simulationType == 'One Weight' && (
+                                            {(!this.dataDoc.simulation_paused || this.simulationType !== 'One Weight') && <td style={{ cursor: 'default' }}>{this.dataDoc.mass1_velocityY + ''} m/s</td>}{' '}
+                                            {this.dataDoc.simulation_paused && this.simulationType === 'One Weight' && (
                                                 <td
                                                     style={{
                                                         cursor: 'default',
@@ -1804,7 +1809,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                         effect={value => {
                                                             this.dataDoc.mass1_velocityYstart = -value;
                                                         }}
-                                                        small={true}
+                                                        small
                                                         mode="Freeform"
                                                     />
                                                 </td>
@@ -1822,14 +1827,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                 <Box>Acceleration</Box>
                                             </td>
                                             <td style={{ cursor: 'default' }}>
-                                                <>
-                                                    {this.dataDoc.mass1_accelerationX} m/s<sup>2</sup>
-                                                </>
+                                                {this.dataDoc.mass1_accelerationX + ''} m/s<sup>2</sup>
                                             </td>
                                             <td style={{ cursor: 'default' }}>
-                                                <>
-                                                    {this.dataDoc.mass1_accelerationY} m/s<sup>2</sup>
-                                                </>
+                                                {this.dataDoc.mass1_accelerationY + ''} m/s<sup>2</sup>
                                             </td>
                                         </tr>
                                         <tr>
@@ -1842,7 +1843,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                     </tbody>
                                 </table>
                             )}
-                            {this.simulationMode == 'Freeform' && this.simulationType == 'Pulley' && (
+                            {this.simulationMode === 'Freeform' && this.simulationType === 'Pulley' && (
                                 <table>
                                     <tbody>
                                         <tr>
@@ -1869,14 +1870,10 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                                 <Box>Acceleration</Box>
                                             </td>
                                             <td style={{ cursor: 'default' }}>
-                                                <>
-                                                    {this.dataDoc.mass2_accelerationX} m/s<sup>2</sup>
-                                                </>
+                                                {this.dataDoc.mass2_accelerationX + ''} m/s<sup>2</sup>
                                             </td>
                                             <td style={{ cursor: 'default' }}>
-                                                <>
-                                                    {this.dataDoc.mass2_accelerationY} m/s<sup>2</sup>
-                                                </>
+                                                {this.dataDoc.mass2_accelerationY + ''} m/s<sup>2</sup>
                                             </td>
                                         </tr>
                                         <tr>
@@ -1890,7 +1887,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                 </table>
                             )}
                         </div>
-                        {this.simulationType != 'Pendulum' && this.simulationType != 'Spring' && (
+                        {this.simulationType !== 'Pendulum' && this.simulationType !== 'Spring' && (
                             <div>
                                 <p>Kinematic Equations</p>
                                 <ul>
@@ -1907,7 +1904,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                 </ul>
                             </div>
                         )}
-                        {this.simulationType == 'Spring' && (
+                        {this.simulationType === 'Spring' && (
                             <div>
                                 <p>Harmonic Motion Equations: Spring</p>
                                 <ul>
@@ -1936,7 +1933,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                 </ul>
                             </div>
                         )}
-                        {this.simulationType == 'Pendulum' && (
+                        {this.simulationType === 'Pendulum' && (
                             <div>
                                 <p>Harmonic Motion Equations: Pendulum</p>
                                 <ul>
@@ -1959,11 +1956,11 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                     <svg width={100 + 'px'} height={100 + 'px'}>
                         <defs>
                             <marker id="miniArrow" markerWidth="20" markerHeight="20" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
-                                <path d="M0,0 L0,6 L9,3 z" fill={'#000000'} />
+                                <path d="M0,0 L0,6 L9,3 z" fill="#000000" />
                             </marker>
                         </defs>
-                        <line x1={20} y1={70} x2={70} y2={70} stroke={'#000000'} strokeWidth="2" markerEnd="url(#miniArrow)" />
-                        <line x1={20} y1={70} x2={20} y2={20} stroke={'#000000'} strokeWidth="2" markerEnd="url(#miniArrow)" />
+                        <line x1={20} y1={70} x2={70} y2={70} stroke="#000000" strokeWidth="2" markerEnd="url(#miniArrow)" />
+                        <line x1={20} y1={70} x2={20} y2={20} stroke="#000000" strokeWidth="2" markerEnd="url(#miniArrow)" />
                     </svg>
                     <p
                         style={{
@@ -1971,7 +1968,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                             top: this.yMax - 120 + 40 + 'px',
                             left: this.xMin + 90 - 80 + 'px',
                         }}>
-                        {this.simulationType == 'Circular Motion' ? 'Z' : 'Y'}
+                        {this.simulationType === 'Circular Motion' ? 'Z' : 'Y'}
                     </p>
                     <p
                         style={{
@@ -1986,3 +1983,9 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.SIMULATION, {
+    data: '',
+    layout: { view: PhysicsSimulationBox, dataField: 'data' },
+    options: { acl: '', _width: 1000, _height: 800, mass1: '', mass2: '', layout_nativeDimEditable: true, position: '', acceleration: '', pendulum: '', spring: '', wedge: '', simulation: '', review: '', systemIcon: 'BsShareFill' },
+});
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 2bc749e1b..ae6caf9f4 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -257,3 +257,8 @@ ScriptingGlobals.add(function renderDropdown() {
     }
     return false;
 });
+
+Docs.Prototypes.TemplateMap.set(DocumentType.WEBCAM, {
+    layout: { view: RecordingBox, dataField: 'data' },
+    options: { acl: '', systemIcon: 'BsFillCameraVideoFill' },
+});
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 882f6ba9e..8eb55b2f8 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -16,8 +16,9 @@ import { Cast, DocCast, NumCast } from '../../../fields/Types';
 import { AudioField, VideoField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { Networking } from '../../Network';
+import { DocUtils } from '../../documents/DocUtils';
 import { DocumentType } from '../../documents/DocumentTypes';
-import { DocUtils } from '../../documents/Documents';
+import { Docs } from '../../documents/Documents';
 import { CaptureManager } from '../../util/CaptureManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { TrackMovements } from '../../util/TrackMovements';
@@ -27,9 +28,9 @@ import { CollectionStackedTimeline } from '../collections/CollectionStackedTimel
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { mediaState } from './AudioBox';
 import { FieldView, FieldViewProps } from './FieldView';
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import './ScreenshotBox.scss';
 import { VideoBox } from './VideoBox';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
 
 declare class MediaRecorder {
     constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -357,3 +358,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.SCREENSHOT, {
+    layout: { view: ScreenshotBox, dataField: 'data' },
+    options: { acl: '', _layout_nativeDimEditable: true, systemIcon: 'BsCameraFill' },
+});
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 5ebc50a1b..bc19d7ad1 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -21,6 +21,8 @@ import { OverlayView } from '../OverlayView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { DocumentIconContainer } from './DocumentIcon';
 import './ScriptingBox.scss';
+import { Docs } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
 
 const _global = (window /* browser */ || global) /* node */ as any;
 const ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
@@ -845,3 +847,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.SCRIPTING, {
+    layout: { view: ScriptingBox, dataField: 'data' },
+    options: { acl: '', systemIcon: 'BsFileEarmarkCodeFill' },
+});
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 13ee3250e..16767d11e 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -10,14 +10,15 @@ import { DocData } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
+import { FollowLinkScript } from '../../../fields/ScriptField';
 import { Cast, NumCast, StrCast, toList } from '../../../fields/Types';
 import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
 import { emptyFunction, formatTime } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/DocUtils';
 import { DocumentManager } from '../../util/DocumentManager';
 import { dropActionType } from '../../util/DropActionTypes';
-import { FollowLinkScript } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { ReplayMovements } from '../../util/ReplayMovements';
 import { undoBatch } from '../../util/UndoManager';
@@ -31,7 +32,8 @@ import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PinDocView, PinProps } from '../PinFuncs';
 import { StyleProp } from '../StyleProvider';
 import { DocumentView } from './DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
+import { FieldView, FieldViewProps } from './FieldView';
+import { FocusViewOptions } from './FocusViewOptions';
 import { RecordingBox } from './RecordingBox';
 import './VideoBox.scss';
 
@@ -1162,3 +1164,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.VID, {
+    layout: { view: VideoBox, dataField: 'data' },
+    options: { acl: '', _layout_currentTimecode: 0, systemIcon: 'BsFileEarmarkPlayFill' },
+});
+Docs.Prototypes.TemplateMap.set(DocumentType.REC, {
+    layout: { view: VideoBox, dataField: 'data' },
+    options: { acl: '', _height: 100, backgroundColor: 'pink', systemIcon: 'BsFillMicFill' },
+});
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index dfe237a86..54f246b20 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -18,7 +18,9 @@ import { Cast, NumCast, StrCast, toList, WebCast } from '../../../fields/Types';
 import { ImageField, WebField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, stringHash } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+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';
@@ -37,9 +39,11 @@ import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
 import { PinDocView, PinProps } from '../PinFuncs';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { StyleProp } from '../StyleProvider';
-import { DocumentView, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
+import { DocumentView } from './DocumentView';
+import { FieldView, FieldViewProps } from './FieldView';
+import { FocusViewOptions } from './FocusViewOptions';
 import { LinkInfo } from './LinkDocPreview';
+import { OpenWhere } from './OpenWhere';
 import './WebBox.scss';
 
 const { CreateImage } = require('./WebBoxRenderer');
@@ -1242,3 +1246,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
 ScriptingGlobals.add(function urlHash(url: string) {
     return stringHash(url);
 });
+
+Docs.Prototypes.TemplateMap.set(DocumentType.WEB, {
+    layout: { view: WebBox, dataField: 'data' },
+    options: { acl: '', _height: 300, _layout_fitWidth: true, _layout_nativeDimEditable: true, _layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' },
+});
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 8b9e3cc5d..bd66941c3 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -7,6 +7,8 @@ import * as React from 'react';
 import { dateRangeStrToDates } from '../../../../ClientUtils';
 import { Doc } from '../../../../fields/Doc';
 import { StrCast } from '../../../../fields/Types';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { Docs } from '../../../documents/Documents';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { FieldView, FieldViewProps } from '../FieldView';
 
@@ -119,3 +121,7 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
         );
     }
 }
+Docs.Prototypes.TemplateMap.set(DocumentType.CALENDAR, {
+    layout: { view: CalendarBox, dataField: 'data' },
+    options: { acl: '' },
+});
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index f311b3cdd..93371685d 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -9,11 +9,12 @@ import { Doc } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { NumCast } from '../../../../fields/Types';
 import { DocServer } from '../../../DocServer';
-import { Docs, DocUtils } from '../../../documents/Documents';
+import { Docs } from '../../../documents/Documents';
+import { DocUtils } from '../../../documents/DocUtils';
 import { Transform } from '../../../util/Transform';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DocumentView } from '../DocumentView';
-import { FocusViewOptions } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
 import { FormattedTextBox } from './FormattedTextBox';
 
 const horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index dc388b22a..1c5ea2dd4 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -23,7 +23,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
 import { FilterPanel } from '../../FilterPanel';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { OpenWhere } from '../DocumentView';
+import { OpenWhere } from '../OpenWhere';
 import './DashFieldView.scss';
 import { FormattedTextBox } from './FormattedTextBox';
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9bcf5027f..ad6629fc9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -15,7 +15,7 @@ import * as React from 'react';
 import { BsMarkdownFill } from 'react-icons/bs';
 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 { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
@@ -28,8 +28,9 @@ import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
 import { emptyFunction, numberRange, unimplementedFunction, Utils } from '../../../../Utils';
 import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
 import { DocServer } from '../../../DocServer';
-import { Docs, DocUtils } from '../../../documents/Documents';
-import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { 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';
@@ -54,9 +55,11 @@ import { PinDocView, PinProps } from '../../PinFuncs';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { styleFromLayoutString, StyleProp } from '../../StyleProvider';
 import { mediaState } from '../AudioBox';
-import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
+import { DocumentView, DocumentViewInternal } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
 import { LinkInfo } from '../LinkDocPreview';
+import { OpenWhere } from '../OpenWhere';
 import { DashDocCommentView } from './DashDocCommentView';
 import { DashDocView } from './DashDocView';
 import { DashFieldView } from './DashFieldView';
@@ -182,26 +185,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     private gptRes: string = '';
 
     public static PasteOnLoad: ClipboardEvent | undefined;
-    private static SelectOnLoad: Doc | undefined;
-    public static SetSelectOnLoad(doc: Doc) {
-        FormattedTextBox.SelectOnLoad = doc;
-    }
     public static DontSelectInitialText = false; // whether initial text should be selected or not
     public static SelectOnLoadChar = '';
-    public static IsFragment(html: string) {
-        return html.indexOf('data-pm-slice') !== -1;
-    }
-    public static GetHref(html: string): string {
-        const parser = new DOMParser();
-        const parsedHtml = parser.parseFromString(html, 'text/html');
-        if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
-            return (parsedHtml.body.childNodes[0].childNodes[0] as any).href;
-        }
-        return '';
-    }
-    public static GetDocFromUrl(url: string) {
-        return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
-    }
 
     constructor(props: FormattedTextBoxProps) {
         super(props);
@@ -305,7 +290,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             e.stopPropagation();
             const targetCreator = (annotationOn?: Doc) => {
                 const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn);
-                FormattedTextBox.SetSelectOnLoad(target);
+                Doc.SetSelectOnLoad(target);
                 return target;
             };
 
@@ -992,7 +977,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
         const help = cm.findByDescription('Help...');
         const helpItems = help && 'subitems' in help ? help.subitems : [];
-        helpItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> });
+        helpItems.push({ description: `show markdown options`, event: () => RTFMarkup.Instance.setOpen(true), icon: <BsMarkdownFill /> });
         !help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' });
     };
 
@@ -1062,7 +1047,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     const tanch = Docs.Create.ConfigDocument({ title: 'dictation anchor' });
                     return this.addDocument(tanch) ? tanch : undefined;
                 };
-                const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
+                const link = CreateLinkToActiveAudio(textanchorFunc, false).lastElement();
                 if (link) {
                     link[DocData].isDictation = true;
                     const audioanchor = Cast(link.link_anchor_2, Doc, null);
@@ -1480,10 +1465,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             (this._editorView as any).TextView = this;
         }
 
-        const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()));
+        const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, Doc.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()));
         const selLoadChar = FormattedTextBox.SelectOnLoadChar;
         if (selectOnLoad) {
-            FormattedTextBox.SelectOnLoad = undefined;
+            Doc.SetSelectOnLoad(undefined);
             FormattedTextBox.SelectOnLoadChar = '';
         }
         if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
@@ -2137,3 +2122,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.RTF, {
+    layout: { view: FormattedTextBox, dataField: 'text' },
+    options: {
+        acl: '',
+        _height: 35,
+        _xMargin: 10,
+        _yMargin: 10,
+        _layout_nativeDimEditable: true,
+        _layout_reflowVertical: true,
+        _layout_reflowHorizontal: true,
+        defaultDoubleClick: 'ignore',
+        systemIcon: 'BsFileEarmarkTextFill',
+    },
+});
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 5d448d40e..bc6454146 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -12,7 +12,7 @@ import { GetEffectiveAcl } from '../../../../fields/util';
 import { Docs } from '../../../documents/Documents';
 import { RTFMarkup } from '../../../util/RTFMarkup';
 import { SelectionManager } from '../../../util/SelectionManager';
-import { OpenWhere } from '../DocumentView';
+import { OpenWhere } from '../OpenWhere';
 
 const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
 
@@ -182,7 +182,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
         return true;
     });
     bind('Cmd-?', () => {
-        RTFMarkup.Instance.open();
+        RTFMarkup.Instance.setOpen(true);
         return true;
     });
     bind('Cmd-e', (state: EditorState, dispatch: (tx: Transaction) => void) => {
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 72a2b706d..1ff862859 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -8,7 +8,8 @@ import { List } from '../../../../fields/List';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { Utils } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
-import { Docs, DocUtils } from '../../../documents/Documents';
+import { Docs } from '../../../documents/Documents';
+import { DocUtils } from '../../../documents/DocUtils';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { CollectionView } from '../../collections/CollectionView';
 import { ContextMenu } from '../../ContextMenu';
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 87b7a4069..d5fad296f 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -14,12 +14,13 @@ import { Doc, DocListCast } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { NumCast } from '../../../../fields/Types';
 import { Networking } from '../../../Network';
-import { DocUtils, Docs } from '../../../documents/Documents';
+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 { OpenWhereMod } from '../DocumentView';
 import { ImageEditorData } from '../ImageBox';
+import { OpenWhereMod } from '../OpenWhere';
 import './GenerativeFill.scss';
 import Buttons from './GenerativeFillButtons';
 import { BrushHandler } from './generativeFillUtils/BrushHandler';
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 4cf9e99fa..485ba7367 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -36,8 +36,10 @@ import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
 import { pinDataTypes as dataTypes } from '../../PinFuncs';
-import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
-import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
+import { DocumentView } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { FocusViewOptions } from '../FocusViewOptions';
+import { OpenWhere, OpenWhereMod } from '../OpenWhere';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
@@ -423,7 +425,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 = DocumentManager.Instance.getFirstDocumentView(context)?.CollectionFreeFormView;
+                const ffview = CollectionFreeFormView.from(DocumentManager.Instance.getFirstDocumentView(context));
                 if (ffview?.childDocs) {
                     PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, frameTime);
                     ffview.layoutDoc._currentFrame = NumCast(activeFrame);
@@ -745,7 +747,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     _exitTrail: Opt<() => void>;
-    PlayTrail = (docs: Doc[]) => {
+    playTrail = (docs: Doc[]) => {
         const savedStates = docs.map(doc => {
             switch (doc.type) {
                 case DocumentType.COL:
@@ -2688,3 +2690,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) {
     PresBox.NavigateToTarget(bestTarget, activeItem);
 });
+
+Docs.Prototypes.TemplateMap.set(DocumentType.PRES, {
+    layout: { view: PresBox, dataField: 'data' },
+    options: { acl: '', defaultDoubleClick: 'ignore', hideClickBehaviors: true, layout_hideLinkAnchors: true },
+});
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index cf78a45b7..af0ab3b53 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -12,7 +12,7 @@ import { List } from '../../../../fields/List';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
-import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
@@ -606,3 +606,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return !(this.slideDoc instanceof Doc) || this.targetDoc instanceof Promise ? null : this.mainItem;
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.PRESELEMENT, {
+    layout: { view: PresElementBox, dataField: 'data' },
+    options: { acl: '', title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' },
+});
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index f1cd1a4f7..8b2b179d3 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -9,8 +9,8 @@ import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { undoable } from '../../util/UndoManager';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { OpenWhere } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
+import { OpenWhere } from '../nodes/OpenWhere';
 import { AnchorMenu } from './AnchorMenu';
 import './Annotation.scss';
 
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 56ff2959c..c1bfdf176 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -12,7 +12,8 @@ import { Doc } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { Networking } from '../../../Network';
 import { GPTCallType, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT';
-import { DocUtils, Docs } from '../../../documents/Documents';
+import { Docs } from '../../../documents/Documents';
+import { DocUtils } from '../../../documents/DocUtils';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { AnchorMenu } from '../AnchorMenu';
 import './GPTPopup.scss';
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 22355bc57..15ac73f78 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -6,21 +6,22 @@ import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
 import * as PDFJSViewer from 'pdfjs-dist/web/pdf_viewer.mjs';
 import * as React from 'react';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils';
+import { CreateLinkToActiveAudio, Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { DocData, Height } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils';
-import { DocUtils } from '../../documents/Documents';
+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 { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
+import { FieldViewProps } from '../nodes/FieldView';
+import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import { LinkInfo } from '../nodes/LinkDocPreview';
 import { PDFBox } from '../nodes/PDFBox';
 import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -331,7 +332,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
             this._ignoreScroll = false;
             if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio
             this._scrollTimer = setTimeout(() => {
-                DocUtils.MakeLinkToActiveAudio(() => this._props.pdfBox.getAnchor(true)!, false);
+                CreateLinkToActiveAudio(() => this._props.pdfBox.getAnchor(true)!, false);
                 this._scrollTimer = undefined;
             }, 200);
         }
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 384e6d654..5df934231 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -4,11 +4,14 @@ import { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { ClientUtils } from '../../../ClientUtils';
 import { Doc, DocListCastAsync, Field, FieldType } from '../../../fields/Doc';
 import { DirectLinks, DocData } from '../../../fields/DocSymbols';
+import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, StrCast } from '../../../fields/Types';
+import { DocUtils } from '../../documents/DocUtils';
 import { DocumentType } from '../../documents/DocumentTypes';
-import { DocUtils } from '../../documents/Documents';
+import { Docs } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { LinkManager } from '../../util/LinkManager';
 import { SearchUtil } from '../../util/SearchUtil';
@@ -21,8 +24,6 @@ import { IRecommendation, Recommendation } from '../newlightbox/components';
 import { fetchRecommendations } from '../newlightbox/utils';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import './SearchBox.scss';
-import { Id } from '../../../fields/FieldSymbols';
-import { ClientUtils } from '../../../ClientUtils';
 
 const DAMPENING_FACTOR = 0.9;
 const MAX_ITERATIONS = 25;
@@ -520,3 +521,8 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
         );
     }
 }
+
+Docs.Prototypes.TemplateMap.set(DocumentType.SEARCH, {
+    layout: { view: SearchBox, dataField: 'data' },
+    options: { acl: '', _width: 400 },
+});
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index 7ad7b2927..6ed3b240d 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -8,7 +8,7 @@ import { Doc } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
 import { DocumentManager } from '../../util/DocumentManager';
 import { SettingsManager } from '../../util/SettingsManager';
-import { FocusViewOptions } from '../nodes/FieldView';
+import { FocusViewOptions } from '../nodes/FocusViewOptions';
 
 export interface SelectedDocViewProps {
     selectedDocs: Doc[];
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 9083d6ca3..5fd053eef 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -174,6 +174,49 @@ export function updateCachedAcls(doc: Doc) {
     return undefined;
 }
 
+export function ActiveInkPen(): Doc { return Doc.UserDoc(); } // prettier-ignore
+export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, 'black'); } // prettier-ignore
+export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ''); } // prettier-ignore
+export function ActiveIsInkMask(): boolean { return BoolCast(ActiveInkPen()?.activeIsInkMask, false); } // prettier-ignore
+export function ActiveInkHideTextLabels(): boolean { return BoolCast(ActiveInkPen().activeInkHideTextLabels, false); } // prettier-ignore
+export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ''); } // prettier-ignore
+export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ''); } // prettier-ignore
+export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.activeArrowScale, 1); } // prettier-ignore
+export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, '0'); } // prettier-ignore
+export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); } // prettier-ignore
+export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } // prettier-ignore
+
+export function SetActiveInkWidth(width: string): void {
+    !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
+}
+export function SetActiveBezierApprox(bezier: string): void {
+    ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier);
+}
+export function SetActiveInkColor(value: string) {
+    ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
+}
+export function SetActiveIsInkMask(value: boolean) {
+    ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
+}
+export function SetActiveInkHideTextLabels(value: boolean) {
+    ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value);
+}
+export function SetActiveFillColor(value: string) {
+    ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
+}
+export function SetActiveArrowStart(value: string) {
+    ActiveInkPen() && (ActiveInkPen().activeArrowStart = value);
+}
+export function SetActiveArrowEnd(value: string) {
+    ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value);
+}
+export function SetActiveArrowScale(value: number) {
+    ActiveInkPen() && (ActiveInkPen().activeArrowScale = value);
+}
+export function SetActiveDash(dash: string): void {
+    !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash);
+}
+
 @scriptingGlobal
 @Deserializable('Doc', updateCachedAcls, ['id'])
 export class Doc extends RefField {
@@ -420,6 +463,16 @@ export class Doc extends RefField {
 
 // eslint-disable-next-line no-redeclare
 export namespace Doc {
+    // eslint-disable-next-line import/no-mutable-exports
+    export let SelectOnLoad: Doc | undefined;
+    export function SetSelectOnLoad(doc: Doc | undefined) {
+        SelectOnLoad = doc;
+    }
+    // eslint-disable-next-line import/no-mutable-exports
+    export let DocDragDataName: string = '';
+    export function SetDocDragDataName(name: string) {
+        DocDragDataName = name;
+    }
     export function SetContainer(doc: Doc, container: Doc) {
         if (container !== Doc.MyRecentlyClosed) {
             doc.embedContainer = container;
@@ -1605,6 +1658,29 @@ export namespace Doc {
     }
 }
 
+export function RTFIsFragment(html: string) {
+    return html.indexOf('data-pm-slice') !== -1;
+}
+export function GetHrefFromHTML(html: string): string {
+    const parser = new DOMParser();
+    const parsedHtml = parser.parseFromString(html, 'text/html');
+    if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
+        return (parsedHtml.body.childNodes[0].childNodes[0] as any).href;
+    }
+    return '';
+}
+export function GetDocFromUrl(url: string) {
+    return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
+}
+
+let activeAudioLinker: (f: () => Doc | undefined, broadcast?: boolean) => (Doc | undefined)[];
+export function SetActiveAudioLinker(func: (f: () => Doc | undefined, broadcast?: boolean) => (Doc | undefined)[]) {
+    activeAudioLinker = func;
+}
+export function CreateLinkToActiveAudio(func: () => Doc | undefined, broadcast?: boolean) {
+    return activeAudioLinker(func, broadcast);
+}
+
 export function IdToDoc(id: string) {
     return DocCast(DocServer.GetCachedRefField(id));
 }
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index a2cc47a43..5eb60a2f8 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -12,7 +12,8 @@ import { DocServer } from '../client/DocServer';
 import { Networking } from '../client/Network';
 import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils';
 import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils';
-import { DocUtils, Docs } from '../client/documents/Documents';
+import { Docs } from '../client/documents/Documents';
+import { DocUtils } from '../client/documents/DocUtils';
 import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox';
 import { schema } from '../client/views/nodes/formattedText/schema_rts';
 import { Doc, Opt } from './Doc';
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 8a3787768..5e5f5527f 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -277,6 +277,10 @@ export class ComputedField extends ScriptField {
     }
 }
 
+export function FollowLinkScript() {
+    return ScriptField.MakeScript('return followLink(this,altKey)', { altKey: 'boolean' });
+}
+
 ScriptingGlobals.add(
     // eslint-disable-next-line prefer-arrow-callback
     function setIndexVal(list: any[], index: number, value: any) {
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index b355e70a7..55a85ed1b 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -127,6 +127,7 @@ import './AudioUpload.scss';
 import { Uploader } from './ImageUpload';
 import './ImageUpload.scss';
 import './MobileInterface.scss';
+import { DashboardView } from '../client/views/DashboardView';
 
 library.add(
     ...[
@@ -583,7 +584,7 @@ export class MobileInterface extends React.Component {
         };
 
         const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
-        const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, 'row');
+        const dashboardDoc = DashboardView.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, 'row');
 
         const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
         const cloneDashboard = ScriptField.MakeScript(`cloneDashboard()`);
-- 
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/PDFViewer.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 139600ab7e8a82a31744cd3798247236cd5616fc Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 3 May 2024 22:08:56 -0400
Subject: fixed pdf rendering.   fixed cropping images.  fixed zooming out of
 an image that is fitWidth to set scroll bar properly.

---
 package-lock.json                                  | 4990 ++++++++++----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   87 +-
 src/client/views/nodes/ImageBox.tsx                |   14 +-
 src/client/views/nodes/PDFBox.tsx                  |    8 +-
 src/client/views/nodes/WebBox.tsx                  |    1 -
 src/client/views/pdf/PDFViewer.tsx                 |    2 +-
 6 files changed, 2666 insertions(+), 2436 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index c53a3219e..f756563ae 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -303,75 +303,69 @@
                 "node": ">=12.0.0"
             }
         },
-        "node_modules/@aashutoshrathi/word-wrap": {
-            "version": "1.2.6",
-            "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
-            "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/@adobe/react-spectrum": {
-            "version": "3.34.1",
-            "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.34.1.tgz",
-            "integrity": "sha512-J1HOjntW+H8xusfc5xLnIlUXNOzllp4f7qzh3LlDOsZuH8oBH8sIYmBVp4ijVhRFUKa10qg088role1On3UGbg==",
-            "dependencies": {
-                "@internationalized/string": "^3.2.1",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/ssr": "^3.9.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-aria/visually-hidden": "^3.8.10",
-                "@react-spectrum/actionbar": "^3.4.3",
-                "@react-spectrum/actiongroup": "^3.10.3",
-                "@react-spectrum/avatar": "^3.0.10",
-                "@react-spectrum/badge": "^3.1.11",
-                "@react-spectrum/breadcrumbs": "^3.9.5",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/buttongroup": "^3.6.11",
-                "@react-spectrum/calendar": "^3.4.7",
-                "@react-spectrum/checkbox": "^3.9.4",
-                "@react-spectrum/combobox": "^3.12.3",
-                "@react-spectrum/contextualhelp": "^3.6.9",
-                "@react-spectrum/datepicker": "^3.9.4",
-                "@react-spectrum/dialog": "^3.8.9",
-                "@react-spectrum/divider": "^3.5.11",
-                "@react-spectrum/dnd": "^3.3.8",
-                "@react-spectrum/form": "^3.7.4",
-                "@react-spectrum/icon": "^3.7.11",
-                "@react-spectrum/illustratedmessage": "^3.4.11",
-                "@react-spectrum/image": "^3.4.11",
-                "@react-spectrum/inlinealert": "^3.2.3",
-                "@react-spectrum/labeledvalue": "^3.1.12",
-                "@react-spectrum/layout": "^3.6.3",
-                "@react-spectrum/link": "^3.6.5",
-                "@react-spectrum/list": "^3.7.8",
-                "@react-spectrum/listbox": "^3.12.7",
-                "@react-spectrum/menu": "^3.18.1",
-                "@react-spectrum/meter": "^3.4.11",
-                "@react-spectrum/numberfield": "^3.9.1",
-                "@react-spectrum/overlays": "^5.5.5",
-                "@react-spectrum/picker": "^3.14.3",
-                "@react-spectrum/progress": "^3.7.5",
-                "@react-spectrum/provider": "^3.9.5",
-                "@react-spectrum/radio": "^3.7.4",
-                "@react-spectrum/searchfield": "^3.8.4",
-                "@react-spectrum/slider": "^3.6.7",
-                "@react-spectrum/statuslight": "^3.5.11",
-                "@react-spectrum/switch": "^3.5.3",
-                "@react-spectrum/table": "^3.12.8",
-                "@react-spectrum/tabs": "^3.8.8",
-                "@react-spectrum/tag": "^3.2.4",
-                "@react-spectrum/text": "^3.5.3",
-                "@react-spectrum/textfield": "^3.11.4",
-                "@react-spectrum/theme-dark": "^3.5.8",
-                "@react-spectrum/theme-default": "^3.5.8",
-                "@react-spectrum/theme-light": "^3.4.8",
-                "@react-spectrum/tooltip": "^3.6.5",
-                "@react-spectrum/view": "^3.6.8",
-                "@react-spectrum/well": "^3.4.11",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/data": "^3.11.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.35.0",
+            "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.35.0.tgz",
+            "integrity": "sha512-dhCKWKDS/xF02EpWluicH7Va6KAANbrUx4rb1UeeFT3czhwz44oCQ74ZYviVedNv6Bq7O22/kbMa20rDdyt/4A==",
+            "dependencies": {
+                "@internationalized/string": "^3.2.2",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/ssr": "^3.9.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/visually-hidden": "^3.8.11",
+                "@react-spectrum/actionbar": "^3.4.4",
+                "@react-spectrum/actiongroup": "^3.10.4",
+                "@react-spectrum/avatar": "^3.0.11",
+                "@react-spectrum/badge": "^3.1.12",
+                "@react-spectrum/breadcrumbs": "^3.9.6",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/buttongroup": "^3.6.12",
+                "@react-spectrum/calendar": "^3.4.8",
+                "@react-spectrum/checkbox": "^3.9.5",
+                "@react-spectrum/combobox": "^3.12.4",
+                "@react-spectrum/contextualhelp": "^3.6.10",
+                "@react-spectrum/datepicker": "^3.9.5",
+                "@react-spectrum/dialog": "^3.8.10",
+                "@react-spectrum/divider": "^3.5.12",
+                "@react-spectrum/dnd": "^3.3.9",
+                "@react-spectrum/dropzone": "^3.0.0",
+                "@react-spectrum/filetrigger": "^3.0.0",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/icon": "^3.7.12",
+                "@react-spectrum/illustratedmessage": "^3.5.0",
+                "@react-spectrum/image": "^3.5.0",
+                "@react-spectrum/inlinealert": "^3.2.4",
+                "@react-spectrum/labeledvalue": "^3.1.13",
+                "@react-spectrum/layout": "^3.6.4",
+                "@react-spectrum/link": "^3.6.6",
+                "@react-spectrum/list": "^3.7.9",
+                "@react-spectrum/listbox": "^3.12.8",
+                "@react-spectrum/menu": "^3.19.0",
+                "@react-spectrum/meter": "^3.5.0",
+                "@react-spectrum/numberfield": "^3.9.2",
+                "@react-spectrum/overlays": "^5.6.0",
+                "@react-spectrum/picker": "^3.14.4",
+                "@react-spectrum/progress": "^3.7.6",
+                "@react-spectrum/provider": "^3.9.6",
+                "@react-spectrum/radio": "^3.7.5",
+                "@react-spectrum/searchfield": "^3.8.5",
+                "@react-spectrum/slider": "^3.6.8",
+                "@react-spectrum/statuslight": "^3.5.12",
+                "@react-spectrum/switch": "^3.5.4",
+                "@react-spectrum/table": "^3.12.9",
+                "@react-spectrum/tabs": "^3.8.9",
+                "@react-spectrum/tag": "^3.2.5",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/textfield": "^3.12.0",
+                "@react-spectrum/theme-dark": "^3.5.9",
+                "@react-spectrum/theme-default": "^3.5.9",
+                "@react-spectrum/theme-light": "^3.4.9",
+                "@react-spectrum/tooltip": "^3.6.6",
+                "@react-spectrum/view": "^3.6.9",
+                "@react-spectrum/well": "^3.4.12",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/data": "^3.11.3",
+                "@react-types/shared": "^3.23.0",
                 "client-only": "^0.0.1"
             },
             "peerDependencies": {
@@ -598,21 +592,21 @@
             }
         },
         "node_modules/@babel/core": {
-            "version": "7.24.4",
-            "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz",
-            "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz",
+            "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==",
             "peer": true,
             "dependencies": {
                 "@ampproject/remapping": "^2.2.0",
                 "@babel/code-frame": "^7.24.2",
-                "@babel/generator": "^7.24.4",
+                "@babel/generator": "^7.24.5",
                 "@babel/helper-compilation-targets": "^7.23.6",
-                "@babel/helper-module-transforms": "^7.23.3",
-                "@babel/helpers": "^7.24.4",
-                "@babel/parser": "^7.24.4",
+                "@babel/helper-module-transforms": "^7.24.5",
+                "@babel/helpers": "^7.24.5",
+                "@babel/parser": "^7.24.5",
                 "@babel/template": "^7.24.0",
-                "@babel/traverse": "^7.24.1",
-                "@babel/types": "^7.24.0",
+                "@babel/traverse": "^7.24.5",
+                "@babel/types": "^7.24.5",
                 "convert-source-map": "^2.0.0",
                 "debug": "^4.1.0",
                 "gensync": "^1.0.0-beta.2",
@@ -628,11 +622,11 @@
             }
         },
         "node_modules/@babel/generator": {
-            "version": "7.24.4",
-            "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz",
-            "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz",
+            "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==",
             "dependencies": {
-                "@babel/types": "^7.24.0",
+                "@babel/types": "^7.24.5",
                 "@jridgewell/gen-mapping": "^0.3.5",
                 "@jridgewell/trace-mapping": "^0.3.25",
                 "jsesc": "^2.5.1"
@@ -679,18 +673,18 @@
             }
         },
         "node_modules/@babel/helper-create-class-features-plugin": {
-            "version": "7.24.4",
-            "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz",
-            "integrity": "sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz",
+            "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==",
             "dependencies": {
                 "@babel/helper-annotate-as-pure": "^7.22.5",
                 "@babel/helper-environment-visitor": "^7.22.20",
                 "@babel/helper-function-name": "^7.23.0",
-                "@babel/helper-member-expression-to-functions": "^7.23.0",
+                "@babel/helper-member-expression-to-functions": "^7.24.5",
                 "@babel/helper-optimise-call-expression": "^7.22.5",
                 "@babel/helper-replace-supers": "^7.24.1",
                 "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
-                "@babel/helper-split-export-declaration": "^7.22.6",
+                "@babel/helper-split-export-declaration": "^7.24.5",
                 "semver": "^6.3.1"
             },
             "engines": {
@@ -717,9 +711,9 @@
             }
         },
         "node_modules/@babel/helper-define-polyfill-provider": {
-            "version": "0.6.1",
-            "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz",
-            "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==",
+            "version": "0.6.2",
+            "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz",
+            "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==",
             "dependencies": {
                 "@babel/helper-compilation-targets": "^7.22.6",
                 "@babel/helper-plugin-utils": "^7.22.5",
@@ -763,11 +757,11 @@
             }
         },
         "node_modules/@babel/helper-member-expression-to-functions": {
-            "version": "7.23.0",
-            "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz",
-            "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz",
+            "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==",
             "dependencies": {
-                "@babel/types": "^7.23.0"
+                "@babel/types": "^7.24.5"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -785,15 +779,15 @@
             }
         },
         "node_modules/@babel/helper-module-transforms": {
-            "version": "7.23.3",
-            "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
-            "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz",
+            "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==",
             "dependencies": {
                 "@babel/helper-environment-visitor": "^7.22.20",
-                "@babel/helper-module-imports": "^7.22.15",
-                "@babel/helper-simple-access": "^7.22.5",
-                "@babel/helper-split-export-declaration": "^7.22.6",
-                "@babel/helper-validator-identifier": "^7.22.20"
+                "@babel/helper-module-imports": "^7.24.3",
+                "@babel/helper-simple-access": "^7.24.5",
+                "@babel/helper-split-export-declaration": "^7.24.5",
+                "@babel/helper-validator-identifier": "^7.24.5"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -814,9 +808,9 @@
             }
         },
         "node_modules/@babel/helper-plugin-utils": {
-            "version": "7.24.0",
-            "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
-            "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz",
+            "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==",
             "engines": {
                 "node": ">=6.9.0"
             }
@@ -854,11 +848,11 @@
             }
         },
         "node_modules/@babel/helper-simple-access": {
-            "version": "7.22.5",
-            "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
-            "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz",
+            "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==",
             "dependencies": {
-                "@babel/types": "^7.22.5"
+                "@babel/types": "^7.24.5"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -876,11 +870,11 @@
             }
         },
         "node_modules/@babel/helper-split-export-declaration": {
-            "version": "7.22.6",
-            "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
-            "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
+            "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
             "dependencies": {
-                "@babel/types": "^7.22.5"
+                "@babel/types": "^7.24.5"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -895,9 +889,9 @@
             }
         },
         "node_modules/@babel/helper-validator-identifier": {
-            "version": "7.22.20",
-            "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
-            "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
+            "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
             "engines": {
                 "node": ">=6.9.0"
             }
@@ -911,38 +905,38 @@
             }
         },
         "node_modules/@babel/helper-wrap-function": {
-            "version": "7.22.20",
-            "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz",
-            "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz",
+            "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==",
             "dependencies": {
-                "@babel/helper-function-name": "^7.22.5",
-                "@babel/template": "^7.22.15",
-                "@babel/types": "^7.22.19"
+                "@babel/helper-function-name": "^7.23.0",
+                "@babel/template": "^7.24.0",
+                "@babel/types": "^7.24.5"
             },
             "engines": {
                 "node": ">=6.9.0"
             }
         },
         "node_modules/@babel/helpers": {
-            "version": "7.24.4",
-            "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz",
-            "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz",
+            "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==",
             "peer": true,
             "dependencies": {
                 "@babel/template": "^7.24.0",
-                "@babel/traverse": "^7.24.1",
-                "@babel/types": "^7.24.0"
+                "@babel/traverse": "^7.24.5",
+                "@babel/types": "^7.24.5"
             },
             "engines": {
                 "node": ">=6.9.0"
             }
         },
         "node_modules/@babel/highlight": {
-            "version": "7.24.2",
-            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz",
-            "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz",
+            "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==",
             "dependencies": {
-                "@babel/helper-validator-identifier": "^7.22.20",
+                "@babel/helper-validator-identifier": "^7.24.5",
                 "chalk": "^2.4.2",
                 "js-tokens": "^4.0.0",
                 "picocolors": "^1.0.0"
@@ -952,9 +946,9 @@
             }
         },
         "node_modules/@babel/parser": {
-            "version": "7.24.4",
-            "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz",
-            "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
+            "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
             "bin": {
                 "parser": "bin/babel-parser.js"
             },
@@ -963,12 +957,12 @@
             }
         },
         "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
-            "version": "7.24.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz",
-            "integrity": "sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz",
+            "integrity": "sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==",
             "dependencies": {
                 "@babel/helper-environment-visitor": "^7.22.20",
-                "@babel/helper-plugin-utils": "^7.24.0"
+                "@babel/helper-plugin-utils": "^7.24.5"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1326,11 +1320,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-block-scoping": {
-            "version": "7.24.4",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz",
-            "integrity": "sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz",
+            "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.24.0"
+                "@babel/helper-plugin-utils": "^7.24.5"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1371,17 +1365,17 @@
             }
         },
         "node_modules/@babel/plugin-transform-classes": {
-            "version": "7.24.1",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz",
-            "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz",
+            "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==",
             "dependencies": {
                 "@babel/helper-annotate-as-pure": "^7.22.5",
                 "@babel/helper-compilation-targets": "^7.23.6",
                 "@babel/helper-environment-visitor": "^7.22.20",
                 "@babel/helper-function-name": "^7.23.0",
-                "@babel/helper-plugin-utils": "^7.24.0",
+                "@babel/helper-plugin-utils": "^7.24.5",
                 "@babel/helper-replace-supers": "^7.24.1",
-                "@babel/helper-split-export-declaration": "^7.22.6",
+                "@babel/helper-split-export-declaration": "^7.24.5",
                 "globals": "^11.1.0"
             },
             "engines": {
@@ -1415,11 +1409,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-destructuring": {
-            "version": "7.24.1",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz",
-            "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz",
+            "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.24.0"
+                "@babel/helper-plugin-utils": "^7.24.5"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1714,14 +1708,14 @@
             }
         },
         "node_modules/@babel/plugin-transform-object-rest-spread": {
-            "version": "7.24.1",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz",
-            "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz",
+            "integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==",
             "dependencies": {
                 "@babel/helper-compilation-targets": "^7.23.6",
-                "@babel/helper-plugin-utils": "^7.24.0",
+                "@babel/helper-plugin-utils": "^7.24.5",
                 "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
-                "@babel/plugin-transform-parameters": "^7.24.1"
+                "@babel/plugin-transform-parameters": "^7.24.5"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1761,11 +1755,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-optional-chaining": {
-            "version": "7.24.1",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz",
-            "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz",
+            "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.24.0",
+                "@babel/helper-plugin-utils": "^7.24.5",
                 "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
                 "@babel/plugin-syntax-optional-chaining": "^7.8.3"
             },
@@ -1777,11 +1771,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-parameters": {
-            "version": "7.24.1",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz",
-            "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz",
+            "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.24.0"
+                "@babel/helper-plugin-utils": "^7.24.5"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -1806,13 +1800,13 @@
             }
         },
         "node_modules/@babel/plugin-transform-private-property-in-object": {
-            "version": "7.24.1",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz",
-            "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz",
+            "integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==",
             "dependencies": {
                 "@babel/helper-annotate-as-pure": "^7.22.5",
-                "@babel/helper-create-class-features-plugin": "^7.24.1",
-                "@babel/helper-plugin-utils": "^7.24.0",
+                "@babel/helper-create-class-features-plugin": "^7.24.5",
+                "@babel/helper-plugin-utils": "^7.24.5",
                 "@babel/plugin-syntax-private-property-in-object": "^7.14.5"
             },
             "engines": {
@@ -1984,11 +1978,11 @@
             }
         },
         "node_modules/@babel/plugin-transform-typeof-symbol": {
-            "version": "7.24.1",
-            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz",
-            "integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz",
+            "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==",
             "dependencies": {
-                "@babel/helper-plugin-utils": "^7.24.0"
+                "@babel/helper-plugin-utils": "^7.24.5"
             },
             "engines": {
                 "node": ">=6.9.0"
@@ -2057,15 +2051,15 @@
             }
         },
         "node_modules/@babel/preset-env": {
-            "version": "7.24.4",
-            "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.4.tgz",
-            "integrity": "sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.5.tgz",
+            "integrity": "sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==",
             "dependencies": {
                 "@babel/compat-data": "^7.24.4",
                 "@babel/helper-compilation-targets": "^7.23.6",
-                "@babel/helper-plugin-utils": "^7.24.0",
+                "@babel/helper-plugin-utils": "^7.24.5",
                 "@babel/helper-validator-option": "^7.23.5",
-                "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.4",
+                "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.5",
                 "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1",
                 "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1",
                 "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1",
@@ -2092,12 +2086,12 @@
                 "@babel/plugin-transform-async-generator-functions": "^7.24.3",
                 "@babel/plugin-transform-async-to-generator": "^7.24.1",
                 "@babel/plugin-transform-block-scoped-functions": "^7.24.1",
-                "@babel/plugin-transform-block-scoping": "^7.24.4",
+                "@babel/plugin-transform-block-scoping": "^7.24.5",
                 "@babel/plugin-transform-class-properties": "^7.24.1",
                 "@babel/plugin-transform-class-static-block": "^7.24.4",
-                "@babel/plugin-transform-classes": "^7.24.1",
+                "@babel/plugin-transform-classes": "^7.24.5",
                 "@babel/plugin-transform-computed-properties": "^7.24.1",
-                "@babel/plugin-transform-destructuring": "^7.24.1",
+                "@babel/plugin-transform-destructuring": "^7.24.5",
                 "@babel/plugin-transform-dotall-regex": "^7.24.1",
                 "@babel/plugin-transform-duplicate-keys": "^7.24.1",
                 "@babel/plugin-transform-dynamic-import": "^7.24.1",
@@ -2117,13 +2111,13 @@
                 "@babel/plugin-transform-new-target": "^7.24.1",
                 "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1",
                 "@babel/plugin-transform-numeric-separator": "^7.24.1",
-                "@babel/plugin-transform-object-rest-spread": "^7.24.1",
+                "@babel/plugin-transform-object-rest-spread": "^7.24.5",
                 "@babel/plugin-transform-object-super": "^7.24.1",
                 "@babel/plugin-transform-optional-catch-binding": "^7.24.1",
-                "@babel/plugin-transform-optional-chaining": "^7.24.1",
-                "@babel/plugin-transform-parameters": "^7.24.1",
+                "@babel/plugin-transform-optional-chaining": "^7.24.5",
+                "@babel/plugin-transform-parameters": "^7.24.5",
                 "@babel/plugin-transform-private-methods": "^7.24.1",
-                "@babel/plugin-transform-private-property-in-object": "^7.24.1",
+                "@babel/plugin-transform-private-property-in-object": "^7.24.5",
                 "@babel/plugin-transform-property-literals": "^7.24.1",
                 "@babel/plugin-transform-regenerator": "^7.24.1",
                 "@babel/plugin-transform-reserved-words": "^7.24.1",
@@ -2131,7 +2125,7 @@
                 "@babel/plugin-transform-spread": "^7.24.1",
                 "@babel/plugin-transform-sticky-regex": "^7.24.1",
                 "@babel/plugin-transform-template-literals": "^7.24.1",
-                "@babel/plugin-transform-typeof-symbol": "^7.24.1",
+                "@babel/plugin-transform-typeof-symbol": "^7.24.5",
                 "@babel/plugin-transform-unicode-escapes": "^7.24.1",
                 "@babel/plugin-transform-unicode-property-regex": "^7.24.1",
                 "@babel/plugin-transform-unicode-regex": "^7.24.1",
@@ -2188,9 +2182,9 @@
             "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
         },
         "node_modules/@babel/runtime": {
-            "version": "7.24.4",
-            "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
-            "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
+            "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==",
             "dependencies": {
                 "regenerator-runtime": "^0.14.0"
             },
@@ -2199,9 +2193,9 @@
             }
         },
         "node_modules/@babel/runtime-corejs3": {
-            "version": "7.24.4",
-            "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.4.tgz",
-            "integrity": "sha512-VOQOexSilscN24VEY810G/PqtpFvx/z6UqDIjIWbDe2368HhDLkYN5TYwaEz/+eRCUkhJ2WaNLLmQAlxzfWj4w==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.5.tgz",
+            "integrity": "sha512-GWO0mgzNMLWaSYM4z4NVIuY0Cd1fl8cPnuetuddu5w/qGuvt5Y7oUi/kvvQGK9xgOkFJDQX2heIvTRn/OQ1XTg==",
             "dependencies": {
                 "core-js-pure": "^3.30.2",
                 "regenerator-runtime": "^0.14.0"
@@ -2224,18 +2218,18 @@
             }
         },
         "node_modules/@babel/traverse": {
-            "version": "7.24.1",
-            "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz",
-            "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz",
+            "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==",
             "dependencies": {
-                "@babel/code-frame": "^7.24.1",
-                "@babel/generator": "^7.24.1",
+                "@babel/code-frame": "^7.24.2",
+                "@babel/generator": "^7.24.5",
                 "@babel/helper-environment-visitor": "^7.22.20",
                 "@babel/helper-function-name": "^7.23.0",
                 "@babel/helper-hoist-variables": "^7.22.5",
-                "@babel/helper-split-export-declaration": "^7.22.6",
-                "@babel/parser": "^7.24.1",
-                "@babel/types": "^7.24.0",
+                "@babel/helper-split-export-declaration": "^7.24.5",
+                "@babel/parser": "^7.24.5",
+                "@babel/types": "^7.24.5",
                 "debug": "^4.3.1",
                 "globals": "^11.1.0"
             },
@@ -2252,12 +2246,12 @@
             }
         },
         "node_modules/@babel/types": {
-            "version": "7.24.0",
-            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
-            "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
+            "version": "7.24.5",
+            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
+            "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
             "dependencies": {
-                "@babel/helper-string-parser": "^7.23.4",
-                "@babel/helper-validator-identifier": "^7.22.20",
+                "@babel/helper-string-parser": "^7.24.1",
+                "@babel/helper-validator-identifier": "^7.24.5",
                 "to-fast-properties": "^2.0.0"
             },
             "engines": {
@@ -2414,6 +2408,12 @@
                 }
             }
         },
+        "node_modules/@emotion/stylis": {
+            "version": "0.8.5",
+            "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
+            "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==",
+            "peer": true
+        },
         "node_modules/@emotion/unitless": {
             "version": "0.8.1",
             "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
@@ -2532,9 +2532,9 @@
             }
         },
         "node_modules/@eslint/js": {
-            "version": "9.1.1",
-            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.1.1.tgz",
-            "integrity": "sha512-5WoDz3Y19Bg2BnErkZTp0en+c/i9PvgFS7MBe1+m60HjFr0hrphlAGp4yzI7pxpt4xShln4ZyYp4neJm8hmOkQ==",
+            "version": "9.2.0",
+            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.2.0.tgz",
+            "integrity": "sha512-ESiIudvhoYni+MdsI8oD7skpprZ89qKocwRM2KEvhhBJ9nl5MRh7BXU5GTod7Mdygq+AUl+QzId6iWJKR/wABA==",
             "dev": true,
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2568,26 +2568,26 @@
             }
         },
         "node_modules/@floating-ui/core": {
-            "version": "1.6.0",
-            "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
-            "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==",
+            "version": "1.6.1",
+            "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.1.tgz",
+            "integrity": "sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==",
             "dependencies": {
-                "@floating-ui/utils": "^0.2.1"
+                "@floating-ui/utils": "^0.2.0"
             }
         },
         "node_modules/@floating-ui/dom": {
-            "version": "1.6.3",
-            "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz",
-            "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==",
+            "version": "1.6.4",
+            "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.4.tgz",
+            "integrity": "sha512-0G8R+zOvQsAG1pg2Q99P21jiqxqGBW1iRe/iXHsBRBxnpXKFI8QwbB4x5KmYLggNO5m34IQgOIu9SCRfR/WWiQ==",
             "dependencies": {
                 "@floating-ui/core": "^1.0.0",
                 "@floating-ui/utils": "^0.2.0"
             }
         },
         "node_modules/@floating-ui/react": {
-            "version": "0.26.12",
-            "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.12.tgz",
-            "integrity": "sha512-D09o62HrWdIkstF2kGekIKAC0/N/Dl6wo3CQsnLcOmO3LkW6Ik8uIb3kw8JYkwxNCcg+uJ2bpWUiIijTBep05w==",
+            "version": "0.26.13",
+            "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.13.tgz",
+            "integrity": "sha512-kBa9wntpugzrZ8t/4yWelvSmEKZdeTXTJzrxqyrLmcU/n1SM4nvse8yQh2e1b37rJGvtu0EplV9+IkBrCJ1vkw==",
             "dependencies": {
                 "@floating-ui/react-dom": "^2.0.0",
                 "@floating-ui/utils": "^0.2.0",
@@ -2599,11 +2599,11 @@
             }
         },
         "node_modules/@floating-ui/react-dom": {
-            "version": "2.0.8",
-            "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz",
-            "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==",
+            "version": "2.0.9",
+            "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.9.tgz",
+            "integrity": "sha512-q0umO0+LQK4+p6aGyvzASqKbKOJcAHJ7ycE9CuUvfx3s9zTHWmGJTPOIlM/hmSBfUfg/XfY5YhLBLR/LHwShQQ==",
             "dependencies": {
-                "@floating-ui/dom": "^1.6.1"
+                "@floating-ui/dom": "^1.0.0"
             },
             "peerDependencies": {
                 "react": ">=16.8.0",
@@ -2611,9 +2611,9 @@
             }
         },
         "node_modules/@floating-ui/utils": {
-            "version": "0.2.1",
-            "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
-            "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
+            "version": "0.2.2",
+            "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz",
+            "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw=="
         },
         "node_modules/@formatjs/ecma402-abstract": {
             "version": "1.18.2",
@@ -2831,34 +2831,34 @@
             }
         },
         "node_modules/@internationalized/date": {
-            "version": "3.5.2",
-            "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.2.tgz",
-            "integrity": "sha512-vo1yOMUt2hzp63IutEaTUxROdvQg1qlMRsbCvbay2AK2Gai7wIgCyK5weEX3nHkiLgo4qCXHijFNC/ILhlRpOQ==",
+            "version": "3.5.3",
+            "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.3.tgz",
+            "integrity": "sha512-X9bi8NAEHAjD8yzmPYT2pdJsbe+tYSEBAfowtlxJVJdZR3aK8Vg7ZUT1Fm5M47KLzp/M1p1VwAaeSma3RT7biw==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0"
             }
         },
         "node_modules/@internationalized/message": {
-            "version": "3.1.2",
-            "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.2.tgz",
-            "integrity": "sha512-MHAWsZWz8jf6jFPZqpTudcCM361YMtPIRu9CXkYmKjJ/0R3pQRScV5C0zS+Qi50O5UAm8ecKhkXx6mWDDcF6/g==",
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.3.tgz",
+            "integrity": "sha512-jba3kGxnh4hN4zoeJZuMft99Ly1zbmon4fyDz3VAmO39Kb5Aw+usGub7oU/sGoBIcVQ7REEwsvjIWtIO1nitbw==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0",
                 "intl-messageformat": "^10.1.0"
             }
         },
         "node_modules/@internationalized/number": {
-            "version": "3.5.1",
-            "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.5.1.tgz",
-            "integrity": "sha512-N0fPU/nz15SwR9IbfJ5xaS9Ss/O5h1sVXMZf43vc9mxEG48ovglvvzBjF53aHlq20uoR6c+88CrIXipU/LSzwg==",
+            "version": "3.5.2",
+            "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.5.2.tgz",
+            "integrity": "sha512-4FGHTi0rOEX1giSkt5MH4/te0eHBq3cvAYsfLlpguV6pzJAReXymiYpE5wPCqKqjkUO3PIsyvk+tBiIV1pZtbA==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0"
             }
         },
         "node_modules/@internationalized/string": {
-            "version": "3.2.1",
-            "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.1.tgz",
-            "integrity": "sha512-vWQOvRIauvFMzOO+h7QrdsJmtN1AXAFVcaLWP9AseRN2o7iHceZ6bIXhBD4teZl8i91A3gxKnWBlGgjCwU6MFQ==",
+            "version": "3.2.2",
+            "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.2.tgz",
+            "integrity": "sha512-5xy2JfSQyGqL9FDIdJXVjoKSBBDJR4lvwoCbqKhc5hQZ/qSLU/OlONCmrJPcSH0zxh88lXJMzbOAk8gJ48JBFw==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0"
             }
@@ -3440,6 +3440,57 @@
                 "@jridgewell/sourcemap-codec": "^1.4.14"
             }
         },
+        "node_modules/@jsonjoy.com/base64": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.1.tgz",
+            "integrity": "sha512-LnFjVChaGY8cZVMwAIMjvA1XwQjZ/zIXHyh28IyJkyNkzof4Dkm1+KN9UIm3lHhREH4vs7XwZ0NpkZKnwOtEfg==",
+            "engines": {
+                "node": ">=10.0"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/streamich"
+            },
+            "peerDependencies": {
+                "tslib": "2"
+            }
+        },
+        "node_modules/@jsonjoy.com/json-pack": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.0.3.tgz",
+            "integrity": "sha512-Q0SPAdmK6s5Fe3e1kcNvwNyk6e2+CxM8XZdGbf4abZG7nUO05KSie3/iX29loTBuY+75uVP6RixDSPVpotfzmQ==",
+            "dependencies": {
+                "@jsonjoy.com/base64": "^1.1.1",
+                "@jsonjoy.com/util": "^1.1.2",
+                "hyperdyperid": "^1.2.0",
+                "thingies": "^1.20.0"
+            },
+            "engines": {
+                "node": ">=10.0"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/streamich"
+            },
+            "peerDependencies": {
+                "tslib": "2"
+            }
+        },
+        "node_modules/@jsonjoy.com/util": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.1.2.tgz",
+            "integrity": "sha512-HOGa9wtE6LEz2I5mMQ2pMSjth85PmD71kPbsecs02nEUq3/Kw0wRK3gmZn5BCEB8mFLXByqPxjHgApoMwIPMKQ==",
+            "engines": {
+                "node": ">=10.0"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/streamich"
+            },
+            "peerDependencies": {
+                "tslib": "2"
+            }
+        },
         "node_modules/@kurkle/color": {
             "version": "0.3.2",
             "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
@@ -3639,9 +3690,9 @@
             }
         },
         "node_modules/@mongodb-js/saslprep": {
-            "version": "1.1.5",
-            "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz",
-            "integrity": "sha512-XLNOMH66KhJzUJNwT/qlMnS4WsNDWD5ASdyaSH3EtK+F4r/CFGa3jT4GNi4mfOitGvWXtdLgQJkQjxSVrio+jA==",
+            "version": "1.1.6",
+            "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.6.tgz",
+            "integrity": "sha512-jqTTXQ46H8cAxmXBu8wm1HTSIMBMrIcoVrsjdQkKdMBj3il/fSCgWyya4P2I1xjPBl69mw+nRphrPlcIqBd20Q==",
             "dependencies": {
                 "sparse-bitfield": "^3.0.3"
             }
@@ -3678,18 +3729,18 @@
             }
         },
         "node_modules/@mui/core-downloads-tracker": {
-            "version": "5.15.15",
-            "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz",
-            "integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==",
+            "version": "5.15.16",
+            "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.16.tgz",
+            "integrity": "sha512-PTIbMJs5C/vYMfyJNW8ArOezh4eyHkg2pTeA7bBxh2kLP1Uzs0Nm+krXWbWGJPwTWjM8EhnDrr4aCF26+2oleg==",
             "funding": {
                 "type": "opencollective",
                 "url": "https://opencollective.com/mui-org"
             }
         },
         "node_modules/@mui/icons-material": {
-            "version": "5.15.15",
-            "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.15.tgz",
-            "integrity": "sha512-kkeU/pe+hABcYDH6Uqy8RmIsr2S/y5bP2rp+Gat4CcRjCcVne6KudS1NrZQhUCRysrTDCAhcbcf9gt+/+pGO2g==",
+            "version": "5.15.16",
+            "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.16.tgz",
+            "integrity": "sha512-s8vYbyACzTNZRKv+20fCfVXJwJqNcVotns2EKnu1wmAga6wv2LAo5kB1d5yqQqZlMFtp34EJvRXf7cy8X0tJVA==",
             "dependencies": {
                 "@babel/runtime": "^7.23.9"
             },
@@ -3712,13 +3763,13 @@
             }
         },
         "node_modules/@mui/material": {
-            "version": "5.15.15",
-            "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.15.tgz",
-            "integrity": "sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA==",
+            "version": "5.15.16",
+            "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.16.tgz",
+            "integrity": "sha512-ery2hFReewko9gpDBqOr2VmXwQG9ifXofPhGzIx09/b9JqCQC/06kZXZDGGrOTpIddK9HlIf4yrS+G70jPAzUQ==",
             "dependencies": {
                 "@babel/runtime": "^7.23.9",
                 "@mui/base": "5.0.0-beta.40",
-                "@mui/core-downloads-tracker": "^5.15.15",
+                "@mui/core-downloads-tracker": "^5.15.16",
                 "@mui/system": "^5.15.15",
                 "@mui/types": "^7.2.14",
                 "@mui/utils": "^5.15.14",
@@ -3812,32 +3863,6 @@
                 }
             }
         },
-        "node_modules/@mui/styled-engine-sc": {
-            "version": "5.14.12",
-            "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.14.12.tgz",
-            "integrity": "sha512-FQ5KDd17OkRurE0ljR4Pddekv1uPSoJxcBqXa9tdoOETGULVCefM5Gd9CRGzT+alNPDyHBoUeEYKulIkDN9ytA==",
-            "dependencies": {
-                "@babel/runtime": "^7.23.1",
-                "csstype": "^3.1.2",
-                "prop-types": "^15.8.1"
-            },
-            "engines": {
-                "node": ">=12.0.0"
-            },
-            "funding": {
-                "type": "opencollective",
-                "url": "https://opencollective.com/mui"
-            },
-            "peerDependencies": {
-                "@types/styled-components": "^5.1.14",
-                "styled-components": "^5.3.1"
-            },
-            "peerDependenciesMeta": {
-                "@types/styled-components": {
-                    "optional": true
-                }
-            }
-        },
         "node_modules/@mui/system": {
             "version": "5.15.15",
             "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz",
@@ -3950,9 +3975,9 @@
             }
         },
         "node_modules/@octokit/auth-token": {
-            "version": "5.1.0",
-            "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.0.tgz",
-            "integrity": "sha512-JH+5PhVMjpbBuKlykiseCHa2uZdEd8Qm/N9Kpqncx4o/wkGF38gqVjIP2gZqfaP3nxFZPpg0FwGClKzBi6nS2g==",
+            "version": "5.1.1",
+            "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz",
+            "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==",
             "engines": {
                 "node": ">= 18"
             }
@@ -3975,9 +4000,9 @@
             }
         },
         "node_modules/@octokit/endpoint": {
-            "version": "10.1.0",
-            "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.0.tgz",
-            "integrity": "sha512-ogZ5uLMeGBZUzS32fNt9j+dNw3kkEn5CSw4CVkN1EvCNdFYWrQ5diQR6Hh52VrPR0oayIoYTqQFL/l8RqkV0qw==",
+            "version": "10.1.1",
+            "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
+            "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
             "dependencies": {
                 "@octokit/types": "^13.0.0",
                 "universal-user-agent": "^7.0.2"
@@ -3987,9 +4012,9 @@
             }
         },
         "node_modules/@octokit/graphql": {
-            "version": "8.1.0",
-            "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.0.tgz",
-            "integrity": "sha512-XDvj6GcUnQYgbCLXElt3vZDzNIPGvGiwxQO2XzsvfVUjebGh0E5eCD/1My9zUGSNKaGVZitVuO8LMziGmoFryg==",
+            "version": "8.1.1",
+            "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz",
+            "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==",
             "dependencies": {
                 "@octokit/request": "^9.0.0",
                 "@octokit/types": "^13.0.0",
@@ -4000,14 +4025,14 @@
             }
         },
         "node_modules/@octokit/openapi-types": {
-            "version": "22.0.1",
-            "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.0.1.tgz",
-            "integrity": "sha512-1yN5m1IMNXthoBDUXFF97N1gHop04B3H8ws7wtOr8GgRyDO1gKALjwMHARNBoMBiB/2vEe/vxstrApcJZzQbnQ=="
+            "version": "22.2.0",
+            "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
+            "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
         },
         "node_modules/@octokit/request": {
-            "version": "9.1.0",
-            "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.0.tgz",
-            "integrity": "sha512-1mDzqKSiryRKZM++MhO6WQBukWbikes6AN6UTxB5vpRnNUbPDkVfUhpSvZ3aXYEFnbcV8DZkikOnCr3pdgMD3Q==",
+            "version": "9.1.1",
+            "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.1.tgz",
+            "integrity": "sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==",
             "dependencies": {
                 "@octokit/endpoint": "^10.0.0",
                 "@octokit/request-error": "^6.0.1",
@@ -4019,9 +4044,9 @@
             }
         },
         "node_modules/@octokit/request-error": {
-            "version": "6.1.0",
-            "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.0.tgz",
-            "integrity": "sha512-xcLJv4IgfWIOEEVZwfhUN3yHNWJL0AMw1J1Ba8BofM9RdDTbapg6MO4zNxlPS4XXX9aAIsbDRa47K57EhgeVAw==",
+            "version": "6.1.1",
+            "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.1.tgz",
+            "integrity": "sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==",
             "dependencies": {
                 "@octokit/types": "^13.0.0"
             },
@@ -4030,11 +4055,11 @@
             }
         },
         "node_modules/@octokit/types": {
-            "version": "13.4.0",
-            "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.4.0.tgz",
-            "integrity": "sha512-WlMegy3lPXYWASe3k9Jslc5a0anrYAYMWtsFrxBTdQjS70hvLH6C+PGvHbOsgy3RA3LouGJoU/vAt4KarecQLQ==",
+            "version": "13.5.0",
+            "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz",
+            "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==",
             "dependencies": {
-                "@octokit/openapi-types": "^22.0.1"
+                "@octokit/openapi-types": "^22.2.0"
             }
         },
         "node_modules/@opentelemetry/api": {
@@ -4076,17 +4101,17 @@
             }
         },
         "node_modules/@react-aria/actiongroup": {
-            "version": "3.7.3",
-            "resolved": "https://registry.npmjs.org/@react-aria/actiongroup/-/actiongroup-3.7.3.tgz",
-            "integrity": "sha512-o1qw7w7GdL8vsOuzBc2mil+MM1CWWDDZ1+VhWnVwoDVt5Pxj36981leTh/WTS58IQ34N7p/jVdQMraQ25EJJyA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/list": "^3.10.3",
-                "@react-types/actiongroup": "^3.4.7",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.7.4",
+            "resolved": "https://registry.npmjs.org/@react-aria/actiongroup/-/actiongroup-3.7.4.tgz",
+            "integrity": "sha512-HQ4D6jCW/EfotRPUI57qXPvZyKlqRr1pRYL/yTxFEE9nvHb/JHxvvQgo9tyIW1YHPO0k/cwkXhDOXLKT1q6d1A==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/list": "^3.10.4",
+                "@react-types/actiongroup": "^3.4.8",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4095,15 +4120,15 @@
             }
         },
         "node_modules/@react-aria/breadcrumbs": {
-            "version": "3.5.11",
-            "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.11.tgz",
-            "integrity": "sha512-bQz4g2tKvcWxeqPGj9O0RQf++Ka8f2o/pJMJB+QQ27DVQWhxpQpND//oFku2aFYkxHB/fyD9qVoiqpQR25bidw==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/link": "^3.6.5",
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/breadcrumbs": "^3.7.3",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.5.12",
+            "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.12.tgz",
+            "integrity": "sha512-UHTVe6kA73xbE1J6LLVjUooEQvTJ4vWPRyOxu4t3dZ/4dMttvHxpKylvj4z606wioSGVhCDEKC4Vn+RtQLypeA==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/link": "^3.7.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/breadcrumbs": "^3.7.4",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4111,16 +4136,16 @@
             }
         },
         "node_modules/@react-aria/button": {
-            "version": "3.9.3",
-            "resolved": "https://registry.npmjs.org/@react-aria/button/-/button-3.9.3.tgz",
-            "integrity": "sha512-ZXo2VGTxfbaTEnfeIlm5ym4vYpGAy8sGrad8Scv+EyDAJWLMKokqctfaN6YSWbqUApC3FN63IvMqASflbmnYig==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/toggle": "^3.7.2",
-                "@react-types/button": "^3.9.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.9.4",
+            "resolved": "https://registry.npmjs.org/@react-aria/button/-/button-3.9.4.tgz",
+            "integrity": "sha512-YOt4XWtC+0m7LwLQnU1Gl0ENETLEhtM8SyDbwsFR/fIQYX0T0H9D6jMlnKxXDjKgRvHzom9NZ8caTfsEPbgW/g==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/toggle": "^3.7.3",
+                "@react-types/button": "^3.9.3",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4128,19 +4153,19 @@
             }
         },
         "node_modules/@react-aria/calendar": {
-            "version": "3.5.6",
-            "resolved": "https://registry.npmjs.org/@react-aria/calendar/-/calendar-3.5.6.tgz",
-            "integrity": "sha512-PA0Ur5WcODMn7t2gCUvq61YktkB+WlSZjzDr5kcY3sdl53ZjiyqCa2hYgrb6R0J859LVJXAp+5Qaproz8g1oLA==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/live-announcer": "^3.3.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/calendar": "^3.4.4",
-                "@react-types/button": "^3.9.2",
-                "@react-types/calendar": "^3.4.4",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.5.7",
+            "resolved": "https://registry.npmjs.org/@react-aria/calendar/-/calendar-3.5.7.tgz",
+            "integrity": "sha512-HbOxoslIpak1/RKHQ/p4A5roI+RpM6geK68s72D+9n3NMPDw/X95yesc6JD1Sti2KsGl9GHI6Myf9xcNjuAMnw==",
+            "dependencies": {
+                "@internationalized/date": "^3.5.3",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/live-announcer": "^3.3.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/calendar": "^3.5.0",
+                "@react-types/button": "^3.9.3",
+                "@react-types/calendar": "^3.4.5",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4149,45 +4174,69 @@
             }
         },
         "node_modules/@react-aria/checkbox": {
-            "version": "3.14.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/checkbox/-/checkbox-3.14.1.tgz",
-            "integrity": "sha512-b4rtrg5SpRSa9jBOqzJMmprJ+jDi3KyVvUh+DsvISe5Ti7gVAhMBgnca1D0xBp22w2jhk/o4gyu1bYxGLum0GA==",
-            "dependencies": {
-                "@react-aria/form": "^3.0.3",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/label": "^3.7.6",
-                "@react-aria/toggle": "^3.10.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/checkbox": "^3.6.3",
-                "@react-stately/form": "^3.0.1",
-                "@react-stately/toggle": "^3.7.2",
-                "@react-types/checkbox": "^3.7.1",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.14.2",
+            "resolved": "https://registry.npmjs.org/@react-aria/checkbox/-/checkbox-3.14.2.tgz",
+            "integrity": "sha512-PeXTEfURrZZBN80YJUyVPAvkT7gwpPtwBgtKxg1ars+D1iDV4Yp48yh5pKaNSf0/rlLNOgKJSCpcFzY7V3ipFw==",
+            "dependencies": {
+                "@react-aria/form": "^3.0.4",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/label": "^3.7.7",
+                "@react-aria/toggle": "^3.10.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/checkbox": "^3.6.4",
+                "@react-stately/form": "^3.0.2",
+                "@react-stately/toggle": "^3.7.3",
+                "@react-types/checkbox": "^3.8.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
+        "node_modules/@react-aria/color": {
+            "version": "3.0.0-beta.32",
+            "resolved": "https://registry.npmjs.org/@react-aria/color/-/color-3.0.0-beta.32.tgz",
+            "integrity": "sha512-FKy5jb+31SIaLrjQR7oMq23CCBgoPdlYN06mwb16EdN024D1ZkQ2ULUvI84vBJxCul8cMOmoIK1DBOnhDslbrQ==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/numberfield": "^3.11.2",
+                "@react-aria/slider": "^3.7.7",
+                "@react-aria/spinbutton": "^3.6.4",
+                "@react-aria/textfield": "^3.14.4",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/visually-hidden": "^3.8.11",
+                "@react-stately/color": "^3.6.0",
+                "@react-stately/form": "^3.0.2",
+                "@react-types/color": "3.0.0-beta.24",
+                "@react-types/shared": "^3.23.0",
+                "@swc/helpers": "^0.5.0"
+            },
+            "peerDependencies": {
+                "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
+                "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+            }
+        },
         "node_modules/@react-aria/combobox": {
-            "version": "3.8.4",
-            "resolved": "https://registry.npmjs.org/@react-aria/combobox/-/combobox-3.8.4.tgz",
-            "integrity": "sha512-HyTWIo2B/0xq0Of+sDEZCfJyf4BvCvDYIWG4UhjqL1kHIHIGQyyr+SldbVUjXVYnk8pP1eGB3ttiREujjjALPQ==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/listbox": "^3.11.5",
-                "@react-aria/live-announcer": "^3.3.2",
-                "@react-aria/menu": "^3.13.1",
-                "@react-aria/overlays": "^3.21.1",
-                "@react-aria/selection": "^3.17.5",
-                "@react-aria/textfield": "^3.14.3",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/combobox": "^3.8.2",
-                "@react-stately/form": "^3.0.1",
-                "@react-types/button": "^3.9.2",
-                "@react-types/combobox": "^3.10.1",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.9.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/combobox/-/combobox-3.9.0.tgz",
+            "integrity": "sha512-JRiCoARx95Lu1hENmf4ndHzpJrMeP/2bV96jZbMn4StFUzhACKnUw0rNFpFdONfeoD/MkWO7tsvhxaPGLhpgtQ==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/listbox": "^3.12.0",
+                "@react-aria/live-announcer": "^3.3.3",
+                "@react-aria/menu": "^3.14.0",
+                "@react-aria/overlays": "^3.22.0",
+                "@react-aria/selection": "^3.18.0",
+                "@react-aria/textfield": "^3.14.4",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/combobox": "^3.8.3",
+                "@react-stately/form": "^3.0.2",
+                "@react-types/button": "^3.9.3",
+                "@react-types/combobox": "^3.11.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4196,27 +4245,27 @@
             }
         },
         "node_modules/@react-aria/datepicker": {
-            "version": "3.9.3",
-            "resolved": "https://registry.npmjs.org/@react-aria/datepicker/-/datepicker-3.9.3.tgz",
-            "integrity": "sha512-1AjCAizd88ACKjVNhFazX4HZZFwWi2rsSlGCTm66Nx6wm5N/Cpbm466dpYEFyQUsKSOG4CC65G1zfYoMPe48MQ==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.2",
-                "@internationalized/number": "^3.5.1",
-                "@internationalized/string": "^3.2.1",
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/form": "^3.0.3",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/label": "^3.7.6",
-                "@react-aria/spinbutton": "^3.6.3",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/datepicker": "^3.9.2",
-                "@react-stately/form": "^3.0.1",
-                "@react-types/button": "^3.9.2",
-                "@react-types/calendar": "^3.4.4",
-                "@react-types/datepicker": "^3.7.2",
-                "@react-types/dialog": "^3.5.8",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.10.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/datepicker/-/datepicker-3.10.0.tgz",
+            "integrity": "sha512-YiIxY+mRxc2rPN8j9ypdiGspRHSIrsK6TShBgKEk5UoG5EBKEJfNe/FfoXDR2d5xcpWLAHVuRjERi9WkiJNDBw==",
+            "dependencies": {
+                "@internationalized/date": "^3.5.3",
+                "@internationalized/number": "^3.5.2",
+                "@internationalized/string": "^3.2.2",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/form": "^3.0.4",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/label": "^3.7.7",
+                "@react-aria/spinbutton": "^3.6.4",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/datepicker": "^3.9.3",
+                "@react-stately/form": "^3.0.2",
+                "@react-types/button": "^3.9.3",
+                "@react-types/calendar": "^3.4.5",
+                "@react-types/datepicker": "^3.7.3",
+                "@react-types/dialog": "^3.5.9",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4225,15 +4274,15 @@
             }
         },
         "node_modules/@react-aria/dialog": {
-            "version": "3.5.12",
-            "resolved": "https://registry.npmjs.org/@react-aria/dialog/-/dialog-3.5.12.tgz",
-            "integrity": "sha512-7UJR/h/Y364u6Ltpw0bT51B48FybTuIBacGpEJN5IxZlpxvQt0KQcBDiOWfAa/GQogw4B5hH6agaOO0nJcP49Q==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/overlays": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/dialog": "^3.5.8",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.5.13",
+            "resolved": "https://registry.npmjs.org/@react-aria/dialog/-/dialog-3.5.13.tgz",
+            "integrity": "sha512-GUwY7sQtPMtO6LFHyoIGFMEv8tEBrNCrSNwEKilFLxvNUCo/1sY3N+7L2TcoeyDkcRWBJ9Uz9iR0iJ6EsCBWng==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/overlays": "^3.22.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/dialog": "^3.5.9",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4242,19 +4291,19 @@
             }
         },
         "node_modules/@react-aria/dnd": {
-            "version": "3.5.3",
-            "resolved": "https://registry.npmjs.org/@react-aria/dnd/-/dnd-3.5.3.tgz",
-            "integrity": "sha512-0gi6sRnr97fSQnGy+CMt+99/+vVqr+qv2T9Ts8X9TAzxHNokz5QfSL88QSlTU36EnAVLxPY18iZQWCExSjKpEQ==",
-            "dependencies": {
-                "@internationalized/string": "^3.2.1",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/live-announcer": "^3.3.2",
-                "@react-aria/overlays": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/dnd": "^3.2.8",
-                "@react-types/button": "^3.9.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.6.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/dnd/-/dnd-3.6.0.tgz",
+            "integrity": "sha512-uIM54sUr4NpSdvxWozNKGqSNSTe9ir/sO+QFGtGAF5dbjMX7FN/7sVVrtmB8UHKC+fwfs+Ml3kjJgHbm10/Qmg==",
+            "dependencies": {
+                "@internationalized/string": "^3.2.2",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/live-announcer": "^3.3.3",
+                "@react-aria/overlays": "^3.22.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/dnd": "^3.3.0",
+                "@react-types/button": "^3.9.3",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4263,13 +4312,13 @@
             }
         },
         "node_modules/@react-aria/focus": {
-            "version": "3.16.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.16.2.tgz",
-            "integrity": "sha512-Rqo9ummmgotESfypzFjI3uh58yMpL+E+lJBbQuXkBM0u0cU2YYzu0uOrFrq3zcHk997udZvq1pGK/R+2xk9B7g==",
+            "version": "3.17.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.17.0.tgz",
+            "integrity": "sha512-aRzBw1WTUkcIV3xFrqPA6aB8ZVt3XyGpTaSHAypU0Pgoy2wRq9YeJYpbunsKj9CJmskuffvTqXwAjTcaQish1Q==",
             "dependencies": {
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/shared": "^3.22.1",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0",
                 "clsx": "^2.0.0"
             },
@@ -4278,14 +4327,14 @@
             }
         },
         "node_modules/@react-aria/form": {
-            "version": "3.0.3",
-            "resolved": "https://registry.npmjs.org/@react-aria/form/-/form-3.0.3.tgz",
-            "integrity": "sha512-5Q2BHE4TTPDzGY2npCzpRRYshwWUb3SMUA/Cbz7QfEtBk+NYuVaq3KjvqLqgUUdyKtqLZ9Far0kIAexloOC4jw==",
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/@react-aria/form/-/form-3.0.4.tgz",
+            "integrity": "sha512-wWfW9Hv+OWIUbJ0QYzJ4EO5Yt7xZD1i+XNZG9pKGBiREi7dYBo7Y7lbqlWc3pJASSE+6aP9HzhK18dMPtGluVA==",
             "dependencies": {
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/form": "^3.0.1",
-                "@react-types/shared": "^3.22.1",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/form": "^3.0.2",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4293,23 +4342,23 @@
             }
         },
         "node_modules/@react-aria/grid": {
-            "version": "3.8.8",
-            "resolved": "https://registry.npmjs.org/@react-aria/grid/-/grid-3.8.8.tgz",
-            "integrity": "sha512-7Bzbya4tO0oIgqexwRb8D6ZdC0GASYq9f/pnkrqocgvG9e1SCld4zOioKbYQDvAK/NnbCgXmmdqFAcLM/iazaA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/live-announcer": "^3.3.2",
-                "@react-aria/selection": "^3.17.5",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/grid": "^3.8.5",
-                "@react-stately/selection": "^3.14.3",
-                "@react-stately/virtualizer": "^3.6.8",
-                "@react-types/checkbox": "^3.7.1",
-                "@react-types/grid": "^3.2.4",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.9.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/grid/-/grid-3.9.0.tgz",
+            "integrity": "sha512-jNg7haMptmeTKR7/ZomIjWZMLB6jWalBkl5in2JdU9Hc4pY5EKqD/7PSprr9SjOzCr5O+4MSiRDvw+Tu7xHevQ==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/live-announcer": "^3.3.3",
+                "@react-aria/selection": "^3.18.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/grid": "^3.8.6",
+                "@react-stately/selection": "^3.15.0",
+                "@react-stately/virtualizer": "^3.7.0",
+                "@react-types/checkbox": "^3.8.0",
+                "@react-types/grid": "^3.2.5",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4318,18 +4367,20 @@
             }
         },
         "node_modules/@react-aria/gridlist": {
-            "version": "3.7.5",
-            "resolved": "https://registry.npmjs.org/@react-aria/gridlist/-/gridlist-3.7.5.tgz",
-            "integrity": "sha512-RmHEJ++vngHYEWbUCtLLmGh7H3vNd2Y9S0q/9SgHFPbqPZycT5mxDZ2arqpOXeHRVRvPBaW9ZlMxI2bPOePrYw==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/grid": "^3.8.8",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/selection": "^3.17.5",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/list": "^3.10.3",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.8.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/gridlist/-/gridlist-3.8.0.tgz",
+            "integrity": "sha512-2iPBtpYZdTVij6XcqFsRsjzItjgg7FhFRPUEgD62mCyYd6NJIDkCxLuL97hkZ5BbXNxsr2jgVEns5Z4UzW//IQ==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/grid": "^3.9.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/selection": "^3.18.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/list": "^3.10.4",
+                "@react-stately/tree": "^3.8.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4338,17 +4389,17 @@
             }
         },
         "node_modules/@react-aria/i18n": {
-            "version": "3.10.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.10.2.tgz",
-            "integrity": "sha512-Z1ormoIvMOI4mEdcFLYsoJy9w/EzBdBmgfLP+S/Ah+1xwQOXpgwZxiKOhYHpWa0lf6hkKJL34N9MHJvCJ5Crvw==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.2",
-                "@internationalized/message": "^3.1.2",
-                "@internationalized/number": "^3.5.1",
-                "@internationalized/string": "^3.2.1",
-                "@react-aria/ssr": "^3.9.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.11.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.11.0.tgz",
+            "integrity": "sha512-dnopopsYKy2cd2dB2LdnmdJ58evKKcNCtiscWl624XFSbq2laDrYIQ4umrMhBxaKD7nDQkqydVBe6HoQKPzvJw==",
+            "dependencies": {
+                "@internationalized/date": "^3.5.3",
+                "@internationalized/message": "^3.1.3",
+                "@internationalized/number": "^3.5.2",
+                "@internationalized/string": "^3.2.2",
+                "@react-aria/ssr": "^3.9.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4356,13 +4407,13 @@
             }
         },
         "node_modules/@react-aria/interactions": {
-            "version": "3.21.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.21.1.tgz",
-            "integrity": "sha512-AlHf5SOzsShkHfV8GLLk3v9lEmYqYHURKcXWue0JdYbmquMRkUsf/+Tjl1+zHVAQ8lKqRnPYbTmc4AcZbqxltw==",
+            "version": "3.21.2",
+            "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.21.2.tgz",
+            "integrity": "sha512-Ju706DtoEmI/2vsfu9DCEIjDqsRBVLm/wmt2fr0xKbBca7PtmK8daajxFWz+eTq+EJakvYfLr7gWgLau9HyWXg==",
             "dependencies": {
-                "@react-aria/ssr": "^3.9.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/shared": "^3.22.1",
+                "@react-aria/ssr": "^3.9.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4370,12 +4421,12 @@
             }
         },
         "node_modules/@react-aria/label": {
-            "version": "3.7.6",
-            "resolved": "https://registry.npmjs.org/@react-aria/label/-/label-3.7.6.tgz",
-            "integrity": "sha512-ap9iFS+6RUOqeW/F2JoNpERqMn1PvVIo3tTMrJ1TY1tIwyJOxdCBRgx9yjnPBnr+Ywguep+fkPNNi/m74+tXVQ==",
+            "version": "3.7.7",
+            "resolved": "https://registry.npmjs.org/@react-aria/label/-/label-3.7.7.tgz",
+            "integrity": "sha512-0MDIu4SbagwsYzkprcCzi1Z0V/t2K/5Dd30eSTL2zanXMa+/85MVGSQjXI0vPrXMOXSNqp0R/aMxcqcgJ59yRA==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/shared": "^3.22.1",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4383,15 +4434,15 @@
             }
         },
         "node_modules/@react-aria/link": {
-            "version": "3.6.5",
-            "resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.6.5.tgz",
-            "integrity": "sha512-kg8CxKqkciQFzODvLAfxEs8gbqNXFZCW/ISOE2LHYKbh9pA144LVo71qO3SPeYVVzIjmZeW4vEMdZwqkNozecw==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/link": "^3.5.3",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.7.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.7.0.tgz",
+            "integrity": "sha512-gkF7KpDR+ApcMY5HS3xVKHrxRcwSP9TRPoySWEMBE4GPWvEK1Bk/On9EM1vRzeEibCZ5L6gKuLEEKLVSGbBMWg==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/link": "^3.5.4",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4399,18 +4450,18 @@
             }
         },
         "node_modules/@react-aria/listbox": {
-            "version": "3.11.5",
-            "resolved": "https://registry.npmjs.org/@react-aria/listbox/-/listbox-3.11.5.tgz",
-            "integrity": "sha512-y3a3zQYjT+JKgugCMMKS7K9sRoCoP1Z6Fiiyfd77OHXWzh9RlnvWGsseljynmbxLzSuPwFtCYkU1Jz4QwsPUIg==",
-            "dependencies": {
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/label": "^3.7.6",
-                "@react-aria/selection": "^3.17.5",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/list": "^3.10.3",
-                "@react-types/listbox": "^3.4.7",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.12.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/listbox/-/listbox-3.12.0.tgz",
+            "integrity": "sha512-Cy+UcfXU4MrOBMBnaB+kqG8bajeS3T1ZN8L7PXSTpmFS9jShFMhYkNz5gXpI+0SS4dgbHtkq/YDFJvu+bxFvdg==",
+            "dependencies": {
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/label": "^3.7.7",
+                "@react-aria/selection": "^3.18.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/list": "^3.10.4",
+                "@react-types/listbox": "^3.4.8",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4419,30 +4470,30 @@
             }
         },
         "node_modules/@react-aria/live-announcer": {
-            "version": "3.3.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/live-announcer/-/live-announcer-3.3.2.tgz",
-            "integrity": "sha512-aOyPcsfyY9tLCBhuUaYCruwcd1IrYLc47Ou+J7wMzjeN9v4lsaEfiN12WFl8pDqOwfy6/7It2wmlm5hOuZY8wQ==",
+            "version": "3.3.3",
+            "resolved": "https://registry.npmjs.org/@react-aria/live-announcer/-/live-announcer-3.3.3.tgz",
+            "integrity": "sha512-sMaBzzIgDPBDCeZ/UFbuXR/UnXikcE7t4OJ4cESzmUq6r6LvxzmZnG9ocwpH75n7udmUbINycKD082fneryHlg==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0"
             }
         },
         "node_modules/@react-aria/menu": {
-            "version": "3.13.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/menu/-/menu-3.13.1.tgz",
-            "integrity": "sha512-jF80YIcvD16Fgwm5pj7ViUE3Dj7z5iewQixLaFVdvpgfyE58SD/ZVU9/JkK5g/03DYM0sjpUKZGkdFxxw8eKnw==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/overlays": "^3.21.1",
-                "@react-aria/selection": "^3.17.5",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/menu": "^3.6.1",
-                "@react-stately/tree": "^3.7.6",
-                "@react-types/button": "^3.9.2",
-                "@react-types/menu": "^3.9.7",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.14.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/menu/-/menu-3.14.0.tgz",
+            "integrity": "sha512-veZIpwKPKDIX1xpUzvGnxSVTmMfpRjPQUi1v+hMgqgdjBKedKI2LkprLABo9grggjqV9c2xT4XUXDk6xH3r8eA==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/overlays": "^3.22.0",
+                "@react-aria/selection": "^3.18.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/menu": "^3.7.0",
+                "@react-stately/tree": "^3.8.0",
+                "@react-types/button": "^3.9.3",
+                "@react-types/menu": "^3.9.8",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4451,13 +4502,13 @@
             }
         },
         "node_modules/@react-aria/meter": {
-            "version": "3.4.11",
-            "resolved": "https://registry.npmjs.org/@react-aria/meter/-/meter-3.4.11.tgz",
-            "integrity": "sha512-P1G3Jdh0f/uieUDqvc3Ee4wzqBJa7H077BVSC3KPRqEp6YY7JimZGWjOwbFlO2PXhryXm/dI8EzUmh+4ZXjq/g==",
+            "version": "3.4.12",
+            "resolved": "https://registry.npmjs.org/@react-aria/meter/-/meter-3.4.12.tgz",
+            "integrity": "sha512-Ofgy3SHBjNLrc0mzuEKfn5ozOyrLudzcpw1cP5BFgtYs8BDdUx2/e33+2sm1+Pm3M/AhBrV/9LGyOE2JCtb8pg==",
             "dependencies": {
-                "@react-aria/progress": "^3.4.11",
-                "@react-types/meter": "^3.3.7",
-                "@react-types/shared": "^3.22.1",
+                "@react-aria/progress": "^3.4.12",
+                "@react-types/meter": "^3.4.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4465,20 +4516,20 @@
             }
         },
         "node_modules/@react-aria/numberfield": {
-            "version": "3.11.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/numberfield/-/numberfield-3.11.1.tgz",
-            "integrity": "sha512-JQ1Z+Ho5H+jeav7jt9A4vBsIQR/Dd2CFbObrULjGkqSrnWjARFZBv3gZwmfGCtplEPeAv9buYKHAqebPtJNUww==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/spinbutton": "^3.6.3",
-                "@react-aria/textfield": "^3.14.3",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/form": "^3.0.1",
-                "@react-stately/numberfield": "^3.9.1",
-                "@react-types/button": "^3.9.2",
-                "@react-types/numberfield": "^3.8.1",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.11.2",
+            "resolved": "https://registry.npmjs.org/@react-aria/numberfield/-/numberfield-3.11.2.tgz",
+            "integrity": "sha512-PPCRLhAnCz3/mbv/EXoG3mY8lUvaOnZdGZf0ufb1VS4K/wKtb8z3sCTDiu1hi7nFo1YYqynb8mKue4Es5jUwSw==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/spinbutton": "^3.6.4",
+                "@react-aria/textfield": "^3.14.4",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/form": "^3.0.2",
+                "@react-stately/numberfield": "^3.9.2",
+                "@react-types/button": "^3.9.3",
+                "@react-types/numberfield": "^3.8.2",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4487,20 +4538,20 @@
             }
         },
         "node_modules/@react-aria/overlays": {
-            "version": "3.21.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.21.1.tgz",
-            "integrity": "sha512-djEBDF+TbIIOHWWNpdm19+z8xtY8U+T+wKVQg/UZ6oWnclSqSWeGl70vu73Cg4HVBJ4hKf1SRx4Z/RN6VvH4Yw==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/ssr": "^3.9.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-aria/visually-hidden": "^3.8.10",
-                "@react-stately/overlays": "^3.6.5",
-                "@react-types/button": "^3.9.2",
-                "@react-types/overlays": "^3.8.5",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.22.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.22.0.tgz",
+            "integrity": "sha512-M3Iayc2Hf9vJ4JJ8K/zh+Ct6aymDLmBbo686ChV3AtGOc254RyyzqnVSNuMs3j5QVBsDUKihHZQfl4E9RCwd+w==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/ssr": "^3.9.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/visually-hidden": "^3.8.11",
+                "@react-stately/overlays": "^3.6.6",
+                "@react-types/button": "^3.9.3",
+                "@react-types/overlays": "^3.8.6",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4509,15 +4560,15 @@
             }
         },
         "node_modules/@react-aria/progress": {
-            "version": "3.4.11",
-            "resolved": "https://registry.npmjs.org/@react-aria/progress/-/progress-3.4.11.tgz",
-            "integrity": "sha512-RePHbS15/KYFiApYLdwazwvWKsB9q0Kn5DGCSb0hqCC+k2Eui8iVVOsegswiP+xqkk/TiUCIkBEw22W3Az4kTg==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/label": "^3.7.6",
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/progress": "^3.5.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.4.12",
+            "resolved": "https://registry.npmjs.org/@react-aria/progress/-/progress-3.4.12.tgz",
+            "integrity": "sha512-Wlz7VNFEzcLSawhZwWTKgJPM/IUKFiKJJG7KGcsT2biIlu6Yp60xj08hDZkCrLq3XsLLCRmweHlVfLFjG3AK9w==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/label": "^3.7.7",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/progress": "^3.5.3",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4525,19 +4576,19 @@
             }
         },
         "node_modules/@react-aria/radio": {
-            "version": "3.10.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/radio/-/radio-3.10.2.tgz",
-            "integrity": "sha512-CTUTR+qt3BLjmyQvKHZuVm+1kyvT72ZptOty++sowKXgJApTLdjq8so1IpaLAr8JIfzqD5I4tovsYwIQOX8log==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/form": "^3.0.3",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/label": "^3.7.6",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/radio": "^3.10.2",
-                "@react-types/radio": "^3.7.1",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.10.3",
+            "resolved": "https://registry.npmjs.org/@react-aria/radio/-/radio-3.10.3.tgz",
+            "integrity": "sha512-9noof5jyHE8iiFEUE7xCAHvCjG7EkZ/bZHh2+ZtrLlTFZmjpEbRbpZMw6QMKC8uzREPsmERBXjbd/6NyXH6mEQ==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/form": "^3.0.4",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/label": "^3.7.7",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/radio": "^3.10.3",
+                "@react-types/radio": "^3.8.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4545,17 +4596,17 @@
             }
         },
         "node_modules/@react-aria/searchfield": {
-            "version": "3.7.3",
-            "resolved": "https://registry.npmjs.org/@react-aria/searchfield/-/searchfield-3.7.3.tgz",
-            "integrity": "sha512-mnYI969R7tU3yMRIGmY1+peq7tmEW0W3MB/J2ImK36Obz/91tTtspHHEeFtPlQDLIyvVPB0Ucam4LIxCKPJm/Q==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/textfield": "^3.14.3",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/searchfield": "^3.5.1",
-                "@react-types/button": "^3.9.2",
-                "@react-types/searchfield": "^3.5.3",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.7.4",
+            "resolved": "https://registry.npmjs.org/@react-aria/searchfield/-/searchfield-3.7.4.tgz",
+            "integrity": "sha512-92TR4M6/lBKkIp6l3Fl/Sqjjo++bDceIZEOKC62/cuYjLm9RRWl4tLlKIR1GN3IH1vJJStKj+TB/SjlWbPuwiA==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/textfield": "^3.14.4",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/searchfield": "^3.5.2",
+                "@react-types/button": "^3.9.3",
+                "@react-types/searchfield": "^3.5.4",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4563,23 +4614,23 @@
             }
         },
         "node_modules/@react-aria/select": {
-            "version": "3.14.3",
-            "resolved": "https://registry.npmjs.org/@react-aria/select/-/select-3.14.3.tgz",
-            "integrity": "sha512-9KCxI41FI+jTxEfUzRsMdJsZvjkCuuhL4UHig8MZXtXs0nsi7Ir3ezUDQ9m5MSG+ooBYM/CA9DyLDvo5Ioef+g==",
-            "dependencies": {
-                "@react-aria/form": "^3.0.3",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/label": "^3.7.6",
-                "@react-aria/listbox": "^3.11.5",
-                "@react-aria/menu": "^3.13.1",
-                "@react-aria/selection": "^3.17.5",
-                "@react-aria/utils": "^3.23.2",
-                "@react-aria/visually-hidden": "^3.8.10",
-                "@react-stately/select": "^3.6.2",
-                "@react-types/button": "^3.9.2",
-                "@react-types/select": "^3.9.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.14.4",
+            "resolved": "https://registry.npmjs.org/@react-aria/select/-/select-3.14.4.tgz",
+            "integrity": "sha512-MeFN8pw9liXwejkJS+hg0fDqGa3oW/mIwZYx8CrZZLbPnEcjZ9NI4ZXSxJaMOHEIQj/RXQ3Fpu0Sunu9FVpYXQ==",
+            "dependencies": {
+                "@react-aria/form": "^3.0.4",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/label": "^3.7.7",
+                "@react-aria/listbox": "^3.12.0",
+                "@react-aria/menu": "^3.14.0",
+                "@react-aria/selection": "^3.18.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/visually-hidden": "^3.8.11",
+                "@react-stately/select": "^3.6.3",
+                "@react-types/button": "^3.9.3",
+                "@react-types/select": "^3.9.3",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4588,16 +4639,16 @@
             }
         },
         "node_modules/@react-aria/selection": {
-            "version": "3.17.5",
-            "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.17.5.tgz",
-            "integrity": "sha512-gO5jBUkc7WdkiFMlWt3x9pTSuj3Yeegsxfo44qU5NPlKrnGtPRZDWrlACNgkDHu645RNNPhlyoX0C+G8mUg1xA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/selection": "^3.14.3",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.18.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.18.0.tgz",
+            "integrity": "sha512-6ZvRuS9OHe56UVTb/qnsZ1TOxpZH9gRlX6eGG3Pt4LZK12wcvs13Uz2OvB2aYQHu0KPAua9ACnPh94xvXzQIlQ==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/selection": "^3.15.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4606,12 +4657,12 @@
             }
         },
         "node_modules/@react-aria/separator": {
-            "version": "3.3.11",
-            "resolved": "https://registry.npmjs.org/@react-aria/separator/-/separator-3.3.11.tgz",
-            "integrity": "sha512-UTla+3P2pELpP73WSfbwZgP1y1wODFBQbEOHnUxxO8ocyaUyQLJdvc07bBLLpPoyutlggRG0v9ACo0Rui7AjOg==",
+            "version": "3.3.12",
+            "resolved": "https://registry.npmjs.org/@react-aria/separator/-/separator-3.3.12.tgz",
+            "integrity": "sha512-KXeHynxek/DlAmjmry+M2KVRq+j75LqWFysX4x7t+OSbGR4t3bu5HRDd9bvDe9lsW8OKxlX3+hWTY7vsOL/HDA==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/shared": "^3.22.1",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4619,18 +4670,18 @@
             }
         },
         "node_modules/@react-aria/slider": {
-            "version": "3.7.6",
-            "resolved": "https://registry.npmjs.org/@react-aria/slider/-/slider-3.7.6.tgz",
-            "integrity": "sha512-ZeZhyHzhk9gxGuThPKgX2K3RKsxPxsFig1iYoJvqP8485NtHYQIPht2YcpEKA9siLxGF0DR9VCfouVhSoW0AEA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/label": "^3.7.6",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/slider": "^3.5.2",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/slider": "^3.7.1",
+            "version": "3.7.7",
+            "resolved": "https://registry.npmjs.org/@react-aria/slider/-/slider-3.7.7.tgz",
+            "integrity": "sha512-7tOJyR4ZZoSMKcVomC6DZxyYuXQqQopi9mPW2J1fViD1R5iO8YVmoX/ALXnokzi8GPuMA0c38i2Cmnecm30ZXA==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/label": "^3.7.7",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/slider": "^3.5.3",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/slider": "^3.7.2",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4638,15 +4689,15 @@
             }
         },
         "node_modules/@react-aria/spinbutton": {
-            "version": "3.6.3",
-            "resolved": "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.3.tgz",
-            "integrity": "sha512-IlfhRu/pc9zOt2C5zSEB7NmmzddvWisGx2iGzw8BwIKMD+cN3uy+Qwp+sG6Z/JzFEBN0F6Mxm3l5lhbiqjpICQ==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/live-announcer": "^3.3.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/button": "^3.9.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.6.4",
+            "resolved": "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.4.tgz",
+            "integrity": "sha512-KMnwm3bEM83g8MILGt6irbvAG7DNphkq6O0ePt7L1m6QZhWK3hbI2RNlxYMF1OKIDTAOhnEjR6IdMCWt9TuXvQ==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/live-announcer": "^3.3.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/button": "^3.9.3",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4655,9 +4706,9 @@
             }
         },
         "node_modules/@react-aria/ssr": {
-            "version": "3.9.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.2.tgz",
-            "integrity": "sha512-0gKkgDYdnq1w+ey8KzG9l+H5Z821qh9vVjztk55rUg71vTk/Eaebeir+WtzcLLwTjw3m/asIjx8Y59y1lJZhBw==",
+            "version": "3.9.3",
+            "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.3.tgz",
+            "integrity": "sha512-5bUZ93dmvHFcmfUcEN7qzYe8yQQ8JY+nHN6m9/iSDCQ/QmCiE0kWXYwhurjw5ch6I8WokQzx66xKIMHBAa4NNA==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0"
             },
@@ -4669,13 +4720,13 @@
             }
         },
         "node_modules/@react-aria/switch": {
-            "version": "3.6.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/switch/-/switch-3.6.2.tgz",
-            "integrity": "sha512-X5m/omyhXK+V/vhJFsHuRs2zmt9Asa/RuzlldbXnWohLdeuHMPgQnV8C9hg3f+sRi3sh9UUZ64H61pCtRoZNwg==",
+            "version": "3.6.3",
+            "resolved": "https://registry.npmjs.org/@react-aria/switch/-/switch-3.6.3.tgz",
+            "integrity": "sha512-UBWbTEnnlTDT/dFOEpGKfX5ngPTIOVDLX1ltUhDHHk6SrgSnvYxTPTZAo+ujHIUSBFHOuxmvVYG7y54rk168mg==",
             "dependencies": {
-                "@react-aria/toggle": "^3.10.2",
-                "@react-stately/toggle": "^3.7.2",
-                "@react-types/switch": "^3.5.1",
+                "@react-aria/toggle": "^3.10.3",
+                "@react-stately/toggle": "^3.7.3",
+                "@react-types/switch": "^3.5.2",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4683,25 +4734,25 @@
             }
         },
         "node_modules/@react-aria/table": {
-            "version": "3.13.5",
-            "resolved": "https://registry.npmjs.org/@react-aria/table/-/table-3.13.5.tgz",
-            "integrity": "sha512-P2nHEDk2CCoEbMFKNCyBC9qvmv7F/IXARDt/7z/J4mKFgU2iNSK+/zw6yrb38q33Zlk8hDaqSYNxHlMrh+/1MQ==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/grid": "^3.8.8",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/live-announcer": "^3.3.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-aria/visually-hidden": "^3.8.10",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/flags": "^3.0.1",
-                "@react-stately/table": "^3.11.6",
-                "@react-stately/virtualizer": "^3.6.8",
-                "@react-types/checkbox": "^3.7.1",
-                "@react-types/grid": "^3.2.4",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/table": "^3.9.3",
+            "version": "3.14.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/table/-/table-3.14.0.tgz",
+            "integrity": "sha512-IwBmzeIxeZjWlOlmMXVj/L64FbYm3qUh7v3VRgU98BVOdvgUyEKBDIwi6SuOV4FwbXKrCPZbXPU/k+KQU4tUoQ==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/grid": "^3.9.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/live-announcer": "^3.3.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/visually-hidden": "^3.8.11",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/flags": "^3.0.2",
+                "@react-stately/table": "^3.11.7",
+                "@react-stately/virtualizer": "^3.7.0",
+                "@react-types/checkbox": "^3.8.0",
+                "@react-types/grid": "^3.2.5",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/table": "^3.9.4",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4710,17 +4761,17 @@
             }
         },
         "node_modules/@react-aria/tabs": {
-            "version": "3.8.5",
-            "resolved": "https://registry.npmjs.org/@react-aria/tabs/-/tabs-3.8.5.tgz",
-            "integrity": "sha512-Jvt33/W+66n5oCxVwHAYarJ3Fit61vULiPcG7uTez0Mf11cq/C72wOrj+ZuNz6PTLTi2veBNQ7MauY72SnOjRg==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/selection": "^3.17.5",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/tabs": "^3.6.4",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/tabs": "^3.3.5",
+            "version": "3.9.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/tabs/-/tabs-3.9.0.tgz",
+            "integrity": "sha512-E4IHOO9ejEXNeSnpeThu79pDpNySHHYz3txr9ngtH6tp097k/I1auSqbGJPy/kwLj6MCPEt86dNJDXE2X0AcFw==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/selection": "^3.18.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/tabs": "^3.6.5",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/tabs": "^3.3.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4729,19 +4780,19 @@
             }
         },
         "node_modules/@react-aria/tag": {
-            "version": "3.3.3",
-            "resolved": "https://registry.npmjs.org/@react-aria/tag/-/tag-3.3.3.tgz",
-            "integrity": "sha512-tlJD9qj1XcsPIZD7DVJ6tWv8t7Z87/8qkbRDx7ugNqeHso9z0WqH9ZkSt17OFUWE2IQIk3V8D3iBSOtmhXcZGQ==",
-            "dependencies": {
-                "@react-aria/gridlist": "^3.7.5",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/label": "^3.7.6",
-                "@react-aria/selection": "^3.17.5",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/list": "^3.10.3",
-                "@react-types/button": "^3.9.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.4.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/tag/-/tag-3.4.0.tgz",
+            "integrity": "sha512-kTrj0zEIyABgdASZMM7qxe0LAEePAxlg4OmfjZfkiAYYV32liY4EPER7ocE0OhOXo6TeOYYIvpEcr0z/4PjCpw==",
+            "dependencies": {
+                "@react-aria/gridlist": "^3.8.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/label": "^3.7.7",
+                "@react-aria/selection": "^3.18.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/list": "^3.10.4",
+                "@react-types/button": "^3.9.3",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4750,18 +4801,18 @@
             }
         },
         "node_modules/@react-aria/textfield": {
-            "version": "3.14.3",
-            "resolved": "https://registry.npmjs.org/@react-aria/textfield/-/textfield-3.14.3.tgz",
-            "integrity": "sha512-wPSjj/mTABspYQdahg+l5YMtEQ3m5iPCTtb5g6nR1U1rzJkvS4i5Pug6PUXeLeMz2H3ToflPWGlNOqBioAFaOQ==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/form": "^3.0.3",
-                "@react-aria/label": "^3.7.6",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/form": "^3.0.1",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/textfield": "^3.9.1",
+            "version": "3.14.4",
+            "resolved": "https://registry.npmjs.org/@react-aria/textfield/-/textfield-3.14.4.tgz",
+            "integrity": "sha512-fdZChDyTRA4BPqbyDeD9gSw6rVeIAl7eG38osRwr0mzcKTiS/AyV3jiRwnHsBO9brU8RdViJFri4emVDuxSjag==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/form": "^3.0.4",
+                "@react-aria/label": "^3.7.7",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/form": "^3.0.2",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/textfield": "^3.9.2",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4769,15 +4820,30 @@
             }
         },
         "node_modules/@react-aria/toggle": {
-            "version": "3.10.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.10.2.tgz",
-            "integrity": "sha512-DgitscHWgI6IFgnvp2HcMpLGX/cAn+XX9kF5RJQbRQ9NqUgruU5cEEGSOLMrEJ6zXDa2xmOiQ+kINcyNhA+JLg==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/toggle": "^3.7.2",
-                "@react-types/checkbox": "^3.7.1",
+            "version": "3.10.3",
+            "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.10.3.tgz",
+            "integrity": "sha512-QtufHlWczMcTGmRxF7RCEgfMKpUPivyXJWZsQ1HSlknjRJPzf4uc3mSR62hq2sZ0VN9zXEpUsoixbEDB87TnGg==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/toggle": "^3.7.3",
+                "@react-types/checkbox": "^3.8.0",
+                "@swc/helpers": "^0.5.0"
+            },
+            "peerDependencies": {
+                "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+            }
+        },
+        "node_modules/@react-aria/toolbar": {
+            "version": "3.0.0-beta.4",
+            "resolved": "https://registry.npmjs.org/@react-aria/toolbar/-/toolbar-3.0.0-beta.4.tgz",
+            "integrity": "sha512-nerKAoHwHFTvf25DDtj8d/zlDaZHaTPWvL4gweTm3hu9ztisE58CoVUqzqH+VNiYjcrWr2PYgLbNLsuOzk0q1Q==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4785,30 +4851,49 @@
             }
         },
         "node_modules/@react-aria/tooltip": {
-            "version": "3.7.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/tooltip/-/tooltip-3.7.2.tgz",
-            "integrity": "sha512-6jXOSGPao3gPgUQWLbH2r/jxGMqIaIKrJgfwu9TQrh+UkwwiTYW20EpEDCYY2nRFlcoi7EYAiPDSEbHCwXS7Lg==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/tooltip": "^3.4.7",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/tooltip": "^3.4.7",
+            "version": "3.7.3",
+            "resolved": "https://registry.npmjs.org/@react-aria/tooltip/-/tooltip-3.7.3.tgz",
+            "integrity": "sha512-uF2J/GRKTHSeEYMwvXTu7oK710nov/NRbY7db2Hh7yXluGmjJORXb5wxsy+lqHaWqPKBbkhmxBJYeJJpAqlZ5g==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/tooltip": "^3.4.8",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/tooltip": "^3.4.8",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
+        "node_modules/@react-aria/tree": {
+            "version": "3.0.0-alpha.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/tree/-/tree-3.0.0-alpha.0.tgz",
+            "integrity": "sha512-/5dqWjSHEMlrvoJTsQuGHrtKRoxYco5OKrdIf5UQq5wHB/quUD6ZexEfn9/NzflyoHVz2zPIm7RU8J9WuZugNw==",
+            "dependencies": {
+                "@react-aria/gridlist": "^3.8.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/selection": "^3.18.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/tree": "^3.8.0",
+                "@react-types/button": "^3.9.3",
+                "@react-types/shared": "^3.23.0",
+                "@swc/helpers": "^0.5.0"
+            },
+            "peerDependencies": {
+                "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
+                "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+            }
+        },
         "node_modules/@react-aria/utils": {
-            "version": "3.23.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.23.2.tgz",
-            "integrity": "sha512-yznR9jJ0GG+YJvTMZxijQwVp+ahP66DY0apZf7X+dllyN+ByEDW+yaL1ewYPIpugxVzH5P8jhnBXsIyHKN411g==",
+            "version": "3.24.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.24.0.tgz",
+            "integrity": "sha512-JAxkPhK5fCvFVNY2YG3TW3m1nTzwRcbz7iyTSkUzLFat4N4LZ7Kzh7NMHsgeE/oMOxd8zLY+XsUxMu/E/2GujA==",
             "dependencies": {
-                "@react-aria/ssr": "^3.9.2",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/shared": "^3.22.1",
+                "@react-aria/ssr": "^3.9.3",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0",
                 "clsx": "^2.0.0"
             },
@@ -4817,15 +4902,15 @@
             }
         },
         "node_modules/@react-aria/virtualizer": {
-            "version": "3.9.10",
-            "resolved": "https://registry.npmjs.org/@react-aria/virtualizer/-/virtualizer-3.9.10.tgz",
-            "integrity": "sha512-oDvGgexK6phB9XECWvAaKTq/nRKxHjmJSiZ2gv9j72JFoky4iVEHKAV6Qnar0VBcEpk16JcJVI/wf1xr9F+sjQ==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-stately/virtualizer": "^3.6.8",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.10.0",
+            "resolved": "https://registry.npmjs.org/@react-aria/virtualizer/-/virtualizer-3.10.0.tgz",
+            "integrity": "sha512-bxpWicfmsipPnzXp6pSv6Xmo2yLZI5Np4sPDRVTNrsi41ecm295AjPTRLgf/A7Rni0xgyWhUS0FkS7JXTQEv+A==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/virtualizer": "^3.7.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4834,13 +4919,13 @@
             }
         },
         "node_modules/@react-aria/visually-hidden": {
-            "version": "3.8.10",
-            "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.10.tgz",
-            "integrity": "sha512-np8c4wxdbE7ZrMv/bnjwEfpX0/nkWy9sELEb0sK8n4+HJ+WycoXXrVxBUb9tXgL/GCx5ReeDQChjQWwajm/z3A==",
+            "version": "3.8.11",
+            "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.11.tgz",
+            "integrity": "sha512-1JFruyAatoKnC18qrix8Q1gyUNlizWZvYdPADgB5btakMy0PEGTWPmFRK5gFsO+N0CZLCFTCip0dkUv6rrp31w==",
             "dependencies": {
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/shared": "^3.22.1",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4875,24 +4960,24 @@
             "integrity": "sha512-x9ibmsP0ZVqzyCo1Pitbw+4b6iEXRw/r1TCy3vOUR3eKrzWLnHYZMR325BkZW2r8fnuWE/V3Fp4QZOP9qYORCw=="
         },
         "node_modules/@react-spectrum/actionbar": {
-            "version": "3.4.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/actionbar/-/actionbar-3.4.3.tgz",
-            "integrity": "sha512-LIg3a5z0bnFIkA1teaGUo/RdT4ZKPpUUF0RxPSm0nHWEzolSgD7BN4ijK5UxsDLA2cfNxv78s+vwHHKkKnsjSA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/live-announcer": "^3.3.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/actiongroup": "^3.10.3",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/overlays": "^5.5.5",
-                "@react-spectrum/text": "^3.5.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/collections": "^3.10.5",
-                "@react-types/actionbar": "^3.1.5",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.4.4",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/actionbar/-/actionbar-3.4.4.tgz",
+            "integrity": "sha512-Kao7LhiYcRjMRIdhRiYUDTdwALAMdHCEIVTbKYJ+OBbEy3Tzw382p+cogiBtwlcEI6/hVK+JdVyxgfsfZsalPg==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/live-announcer": "^3.3.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/actiongroup": "^3.10.4",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/overlays": "^5.6.0",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/collections": "^3.10.6",
+                "@react-types/actionbar": "^3.1.6",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4902,25 +4987,25 @@
             }
         },
         "node_modules/@react-spectrum/actiongroup": {
-            "version": "3.10.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/actiongroup/-/actiongroup-3.10.3.tgz",
-            "integrity": "sha512-VJASE+Y75Q885z9mx7CVELrX8PuZPbRnGFQnkoRnwZOTpMVZM6wQxA4IcAfsFVvaVIpHgBwtQTI1Ib+gAdtqgw==",
-            "dependencies": {
-                "@react-aria/actiongroup": "^3.7.3",
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/menu": "^3.18.1",
-                "@react-spectrum/text": "^3.5.3",
-                "@react-spectrum/tooltip": "^3.6.5",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/list": "^3.10.3",
-                "@react-types/actiongroup": "^3.4.7",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
-                "@spectrum-icons/workflow": "^4.2.10",
+            "version": "3.10.4",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/actiongroup/-/actiongroup-3.10.4.tgz",
+            "integrity": "sha512-TY9DA+YFBqq+QnzJV1jnJ9vW3xDje5ZdU/WTlU8VBvxZrg9V0Qco1rWA2YSqAsTBOZQJ0iP5V7IvDjAoPrt5Kw==",
+            "dependencies": {
+                "@react-aria/actiongroup": "^3.7.4",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/menu": "^3.19.0",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/tooltip": "^3.6.6",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/list": "^3.10.4",
+                "@react-types/actiongroup": "^3.4.8",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
+                "@spectrum-icons/workflow": "^4.2.11",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4930,14 +5015,14 @@
             }
         },
         "node_modules/@react-spectrum/avatar": {
-            "version": "3.0.10",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/avatar/-/avatar-3.0.10.tgz",
-            "integrity": "sha512-PvsCTTcjNN7QSsa3iCc2UF5LeQCwFNaiR0/xvpk+/ViP1wVq+xFR1tfSrbAq7TAF1B3NQNfI+wepy2ol5l8X2g==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/avatar": "^3.0.5",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.0.11",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/avatar/-/avatar-3.0.11.tgz",
+            "integrity": "sha512-DwLXGiJHDB6XA0KpFD70gU4y3PyaYctCo8GqKbpgerMQJTeE3xZbzlZvt9XvBYx+BrOPA6T0/xpBmxGDaYDDBA==",
+            "dependencies": {
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/avatar": "^3.0.6",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4946,15 +5031,15 @@
             }
         },
         "node_modules/@react-spectrum/badge": {
-            "version": "3.1.11",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/badge/-/badge-3.1.11.tgz",
-            "integrity": "sha512-YOuUBdWmqDQQrNI0uUpazWv/qYmFeDCsJVfQnl/K4J0A6/x7wuvz3iLPJKYuvmqG45/I+WXYFFaNuWOm4jCMzA==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/text": "^3.5.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/badge": "^3.1.7",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.1.12",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/badge/-/badge-3.1.12.tgz",
+            "integrity": "sha512-iWj5mqj3MQ+UOksTuy9JMA+MTvOD33uzcsCKKhJPZZEHqfHMNE1shqaZCUuFCQ7Gmw/KbSDC2B2de1CL6fxjmw==",
+            "dependencies": {
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/badge": "^3.1.8",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4963,22 +5048,22 @@
             }
         },
         "node_modules/@react-spectrum/breadcrumbs": {
-            "version": "3.9.5",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/breadcrumbs/-/breadcrumbs-3.9.5.tgz",
-            "integrity": "sha512-MV1b077f189biweuI8H1/D6F7Nwrao0phYzAKqjaTa8bhXf180ej3VhtEEhrSyEhhMrHG3pPzCCbNUn2VMXDlg==",
-            "dependencies": {
-                "@react-aria/breadcrumbs": "^3.5.11",
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/menu": "^3.18.1",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/collections": "^3.10.5",
-                "@react-types/breadcrumbs": "^3.7.3",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.9.6",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/breadcrumbs/-/breadcrumbs-3.9.6.tgz",
+            "integrity": "sha512-7bxRj3DlUC/TBTOtNY0rVktNTPlmWPD1AonPA0Ot+Hgm0S+eTdqPqvqJGTz6sFZx1vPgGTnp+RIg6sf5b/liGA==",
+            "dependencies": {
+                "@react-aria/breadcrumbs": "^3.5.12",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/menu": "^3.19.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/collections": "^3.10.6",
+                "@react-types/breadcrumbs": "^3.7.4",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4988,22 +5073,22 @@
             }
         },
         "node_modules/@react-spectrum/button": {
-            "version": "3.16.2",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/button/-/button-3.16.2.tgz",
-            "integrity": "sha512-T0B4EUeAAfsocTMZpfKYIsPMIbuwjB1+hfbZyBBvWt8gerLiEiPAwt6+ga1YzV7C6aAz2FPeTT+zd/RxouDTPw==",
-            "dependencies": {
-                "@react-aria/button": "^3.9.3",
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/progress": "^3.7.5",
-                "@react-spectrum/text": "^3.5.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/toggle": "^3.7.2",
-                "@react-types/button": "^3.9.2",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.16.3",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/button/-/button-3.16.3.tgz",
+            "integrity": "sha512-cywlxV2N9cyffu9oGU45wVaOm9U0WNPGE+8978W2tmQMIFUBkQHRM2m7o9qr1C6T7D9hQhzXL0X5LQpTUj9PEQ==",
+            "dependencies": {
+                "@react-aria/button": "^3.9.4",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/progress": "^3.7.6",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/toggle": "^3.7.3",
+                "@react-types/button": "^3.9.3",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5012,14 +5097,14 @@
             }
         },
         "node_modules/@react-spectrum/buttongroup": {
-            "version": "3.6.11",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/buttongroup/-/buttongroup-3.6.11.tgz",
-            "integrity": "sha512-yJv6yBZRLIPYdOAch4FVhdrI0xZz5PPuLiSA6HF/9LlIimALj0qBgCsbQPPThz/UV8+8Y1F76wQUsDcj+iub/A==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/buttongroup": "^3.3.7",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.6.12",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/buttongroup/-/buttongroup-3.6.12.tgz",
+            "integrity": "sha512-eCK+8DzzVO137IjNtdDX9UUMfKWDq4JYMMsGNHO7U146OYC8VnPHb1cdnfR0Pg7I9jefvwOeT0KKxwGenuuXfw==",
+            "dependencies": {
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/buttongroup": "^3.3.8",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5028,25 +5113,25 @@
             }
         },
         "node_modules/@react-spectrum/calendar": {
-            "version": "3.4.7",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/calendar/-/calendar-3.4.7.tgz",
-            "integrity": "sha512-eBH8SAHHbhprbzk445wnNrW80bnm7Kv5rwmcDBd7/FGb5EhWnQgcQtBo3z79dpCXCl8UtpLsih3eDV9QQSjIDA==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.2",
-                "@react-aria/calendar": "^3.5.6",
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-aria/visually-hidden": "^3.8.10",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/label": "^3.16.4",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/calendar": "^3.4.4",
-                "@react-types/button": "^3.9.2",
-                "@react-types/calendar": "^3.4.4",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.4.8",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/calendar/-/calendar-3.4.8.tgz",
+            "integrity": "sha512-Ge8FryNtBvPhQch2678s5j6BuTipK4VdrSstZvG+Zq8QcHP3ksRwF2q1DifNS8iukZSEAS3lgl3Q2RhWXZKanw==",
+            "dependencies": {
+                "@internationalized/date": "^3.5.3",
+                "@react-aria/calendar": "^3.5.7",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/visually-hidden": "^3.8.11",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/label": "^3.16.5",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/calendar": "^3.5.0",
+                "@react-types/button": "^3.9.3",
+                "@react-types/calendar": "^3.4.5",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5056,22 +5141,23 @@
             }
         },
         "node_modules/@react-spectrum/checkbox": {
-            "version": "3.9.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/checkbox/-/checkbox-3.9.4.tgz",
-            "integrity": "sha512-PzUaSza13wh7YipWJLd/0q/nkAnEnxso7hdvMzDLYcw0iKhXNTM2fUoouUuY1SsbithlF2GS5REYyMpQ4FNO3g==",
-            "dependencies": {
-                "@react-aria/checkbox": "^3.14.1",
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-spectrum/form": "^3.7.4",
-                "@react-spectrum/label": "^3.16.4",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/checkbox": "^3.6.3",
-                "@react-stately/toggle": "^3.7.2",
-                "@react-types/checkbox": "^3.7.1",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
-                "@swc/helpers": "^0.5.0"
+            "version": "3.9.5",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/checkbox/-/checkbox-3.9.5.tgz",
+            "integrity": "sha512-M8ZwVohk9986IVVfnhjC++WzXNfeUTj8we5Ud1DnetA3vqpmCoBA5/ym0Ey5LCTizDnTu/9i9NwdxPr2vIDpsw==",
+            "dependencies": {
+                "@react-aria/checkbox": "^3.14.2",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/label": "^3.16.5",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/checkbox": "^3.6.4",
+                "@react-stately/toggle": "^3.7.3",
+                "@react-types/checkbox": "^3.8.0",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
+                "@swc/helpers": "^0.5.0",
+                "react-aria-components": "^1.2.0"
             },
             "peerDependencies": {
                 "@react-spectrum/provider": "^3.0.0",
@@ -5079,34 +5165,34 @@
             }
         },
         "node_modules/@react-spectrum/combobox": {
-            "version": "3.12.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/combobox/-/combobox-3.12.3.tgz",
-            "integrity": "sha512-hskwbdqXSSNEBRYTn3XESSnLGjELU4Lfht3GkZCvcQt02TVvu9NMETSkVe4lwCYV2hwXtPFGyl3jXvmA3MQurQ==",
-            "dependencies": {
-                "@react-aria/button": "^3.9.3",
-                "@react-aria/combobox": "^3.8.4",
-                "@react-aria/dialog": "^3.5.12",
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/form": "^3.0.3",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/label": "^3.7.6",
-                "@react-aria/overlays": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/form": "^3.7.4",
-                "@react-spectrum/label": "^3.16.4",
-                "@react-spectrum/listbox": "^3.12.7",
-                "@react-spectrum/overlays": "^5.5.5",
-                "@react-spectrum/progress": "^3.7.5",
-                "@react-spectrum/textfield": "^3.11.4",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/combobox": "^3.8.2",
-                "@react-types/button": "^3.9.2",
-                "@react-types/combobox": "^3.10.1",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.12.4",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/combobox/-/combobox-3.12.4.tgz",
+            "integrity": "sha512-Xw/QiT3DPaJiGvUkLTYmYRBqNcjpAWS2A40GuMCeDm2idJM6GBWy1k8xwzb0L08HInDCkIV6amqqwV6Et65Pnw==",
+            "dependencies": {
+                "@react-aria/button": "^3.9.4",
+                "@react-aria/combobox": "^3.9.0",
+                "@react-aria/dialog": "^3.5.13",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/form": "^3.0.4",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/label": "^3.7.7",
+                "@react-aria/overlays": "^3.22.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/label": "^3.16.5",
+                "@react-spectrum/listbox": "^3.12.8",
+                "@react-spectrum/overlays": "^5.6.0",
+                "@react-spectrum/progress": "^3.7.6",
+                "@react-spectrum/textfield": "^3.12.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/combobox": "^3.8.3",
+                "@react-types/button": "^3.9.3",
+                "@react-types/combobox": "^3.11.0",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5116,18 +5202,18 @@
             }
         },
         "node_modules/@react-spectrum/contextualhelp": {
-            "version": "3.6.9",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/contextualhelp/-/contextualhelp-3.6.9.tgz",
-            "integrity": "sha512-M0GUmZSLWm6pBAqy0Wwv1srlUPxBSvhQxTxvxtBcRSQwCIE1BcnQ6VaJxlx6n+Ocr14GwYH6MTpD1ZLMUvWeiA==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/dialog": "^3.8.9",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/contextualhelp": "^3.2.8",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/workflow": "^4.2.10",
+            "version": "3.6.10",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/contextualhelp/-/contextualhelp-3.6.10.tgz",
+            "integrity": "sha512-wj3HSNBaFY77hs2MvFDDNw6W9J6XEttS2G/ISC7d4ZjCTuBapj+0PwVM1rGjf5k95FlorSj8jwRZQog8/hXCpw==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/dialog": "^3.8.10",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/contextualhelp": "^3.2.9",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/workflow": "^4.2.11",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5137,29 +5223,29 @@
             }
         },
         "node_modules/@react-spectrum/datepicker": {
-            "version": "3.9.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/datepicker/-/datepicker-3.9.4.tgz",
-            "integrity": "sha512-NyFIH68WqbGajBMe/UMxZDwNwaPL/AKJeLC9CY+GcLp4uncWjI42LdcoBPSQFoEJHLtJlLhx+r7qWO/PMOhg3Q==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.2",
-                "@react-aria/datepicker": "^3.9.3",
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/calendar": "^3.4.7",
-                "@react-spectrum/dialog": "^3.8.9",
-                "@react-spectrum/form": "^3.7.4",
-                "@react-spectrum/label": "^3.16.4",
-                "@react-spectrum/layout": "^3.6.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-spectrum/view": "^3.6.8",
-                "@react-stately/datepicker": "^3.9.2",
-                "@react-types/datepicker": "^3.7.2",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
-                "@spectrum-icons/workflow": "^4.2.10",
+            "version": "3.9.5",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/datepicker/-/datepicker-3.9.5.tgz",
+            "integrity": "sha512-MTOhAuhjX9PKioqGPWksPZgeGiVZesRi26WNUefu1hCrVbmfGXJcL17MNnJbq4bGtMfvaYPOze05OOpka30V8g==",
+            "dependencies": {
+                "@internationalized/date": "^3.5.3",
+                "@react-aria/datepicker": "^3.10.0",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/calendar": "^3.4.8",
+                "@react-spectrum/dialog": "^3.8.10",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/label": "^3.16.5",
+                "@react-spectrum/layout": "^3.6.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-spectrum/view": "^3.6.9",
+                "@react-stately/datepicker": "^3.9.3",
+                "@react-types/datepicker": "^3.7.3",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
+                "@spectrum-icons/workflow": "^4.2.11",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5169,28 +5255,28 @@
             }
         },
         "node_modules/@react-spectrum/dialog": {
-            "version": "3.8.9",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/dialog/-/dialog-3.8.9.tgz",
-            "integrity": "sha512-CxyARi0qYOJi24YVqXZp7tHVreYQoYc7+VBZzcn8dTbIL1CSxcSGbRGXOpHiaAejZ9/3lPm/PTbrWrKUBLbckA==",
-            "dependencies": {
-                "@react-aria/dialog": "^3.5.12",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/overlays": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/buttongroup": "^3.6.11",
-                "@react-spectrum/divider": "^3.5.11",
-                "@react-spectrum/layout": "^3.6.3",
-                "@react-spectrum/overlays": "^5.5.5",
-                "@react-spectrum/text": "^3.5.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-spectrum/view": "^3.6.8",
-                "@react-stately/overlays": "^3.6.5",
-                "@react-types/button": "^3.9.2",
-                "@react-types/dialog": "^3.5.8",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.8.10",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/dialog/-/dialog-3.8.10.tgz",
+            "integrity": "sha512-IcgLn7/ULwwBT41m4QOxqVXu58UG0N9Gm6jo7iAeOnYZHLdp31MEAxmvCUvd3xa4B5wTCG1AxMIQUZnuAac15g==",
+            "dependencies": {
+                "@react-aria/dialog": "^3.5.13",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/overlays": "^3.22.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/buttongroup": "^3.6.12",
+                "@react-spectrum/divider": "^3.5.12",
+                "@react-spectrum/layout": "^3.6.4",
+                "@react-spectrum/overlays": "^5.6.0",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-spectrum/view": "^3.6.9",
+                "@react-stately/overlays": "^3.6.6",
+                "@react-types/button": "^3.9.3",
+                "@react-types/dialog": "^3.5.9",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5200,14 +5286,14 @@
             }
         },
         "node_modules/@react-spectrum/divider": {
-            "version": "3.5.11",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/divider/-/divider-3.5.11.tgz",
-            "integrity": "sha512-u6pyqBEc4JRpntF+KGuzmkzn+rJh04kfvjJ3Tx4F9IucaY8xCfglhunf6/yvRqQB0VaMdlxKy5BGhHgBJzUBiA==",
-            "dependencies": {
-                "@react-aria/separator": "^3.3.11",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/divider": "^3.3.7",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.5.12",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/divider/-/divider-3.5.12.tgz",
+            "integrity": "sha512-EVd/0chomjZni+5OZMrlG6Rqd0VLO3aTyNvvckS28A62hIk/zCGJzJZdPCsK0RZu5P6M61SdW3epUScX7nqO1A==",
+            "dependencies": {
+                "@react-aria/separator": "^3.3.12",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/divider": "^3.3.8",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5215,13 +5301,13 @@
             }
         },
         "node_modules/@react-spectrum/dnd": {
-            "version": "3.3.8",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/dnd/-/dnd-3.3.8.tgz",
-            "integrity": "sha512-GfLqduIBpH2LArBsFpNa4i9cYeFg7lJpqwrLVJRl96DbhdXEcwt9a4tqvsegkqKn6KPHDPG7uTX0OqPoQJluXQ==",
+            "version": "3.3.9",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/dnd/-/dnd-3.3.9.tgz",
+            "integrity": "sha512-o2SE26LgZ2+FRAY8mXbKyyf5BCawx7sSq/9oeZhi9wb9T+SSgGq7Ab0bYcq5MEn5Q1QIB1MaGczIM8nZfd+Wtw==",
             "dependencies": {
-                "@react-aria/dnd": "^3.5.3",
-                "@react-stately/dnd": "^3.2.8",
-                "@react-types/shared": "^3.22.1",
+                "@react-aria/dnd": "^3.6.0",
+                "@react-stately/dnd": "^3.3.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5230,16 +5316,46 @@
                 "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
+        "node_modules/@react-spectrum/dropzone": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/dropzone/-/dropzone-3.0.0.tgz",
+            "integrity": "sha512-ptMmfMwMx0LLTg6QzXCyqv4ffuYh+Y13srqnPZKDiHK5LP+lYRUNT250bFVEqxO6F4LYQrmet6dUaRrMOILn2w==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/shared": "^3.23.0",
+                "@swc/helpers": "^0.5.0",
+                "react-aria-components": "^1.2.0"
+            },
+            "peerDependencies": {
+                "@react-spectrum/provider": "^3.0.0",
+                "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+            }
+        },
+        "node_modules/@react-spectrum/filetrigger": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/filetrigger/-/filetrigger-3.0.0.tgz",
+            "integrity": "sha512-PH5gX+KYTRKO2R3XZB0KUvQssoQLifdvJV3hAeNCl8XgAioPkyVJsxO7SLNmIqShI/lyv9jgqPne3gI5m7pDYg==",
+            "dependencies": {
+                "@swc/helpers": "^0.5.0",
+                "react-aria-components": "^1.2.0"
+            },
+            "peerDependencies": {
+                "@react-spectrum/provider": "^3.0.0",
+                "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+            }
+        },
         "node_modules/@react-spectrum/form": {
-            "version": "3.7.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/form/-/form-3.7.4.tgz",
-            "integrity": "sha512-xAS6vFM6dzKJc7gkC8Qy76VXLxZ9VqPZq4xxQeySvsO8pOujTFCL97QkS0mDjMOdpu72VDIF5JegK1Edlewr7g==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/form": "^3.0.1",
-                "@react-types/form": "^3.7.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.7.5",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/form/-/form-3.7.5.tgz",
+            "integrity": "sha512-lVqtZCgGNyw75MvDO6Okh0lK1bXWvm0+V03rMWiBcXDABHWxSVKfgY31qXGXhptukc1DDRIcNLuXJR9joLMKpg==",
+            "dependencies": {
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/form": "^3.0.2",
+                "@react-types/form": "^3.7.3",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5248,13 +5364,13 @@
             }
         },
         "node_modules/@react-spectrum/icon": {
-            "version": "3.7.11",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/icon/-/icon-3.7.11.tgz",
-            "integrity": "sha512-CTvbukq0g2oEjMqwMDx2qjfmFAv6u7/KwHRn9pGf6BUiU53Zlnl6Fjtm+c9cw8d9GcwlXyIWYBMIKyMm4+7g5A==",
+            "version": "3.7.12",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/icon/-/icon-3.7.12.tgz",
+            "integrity": "sha512-fdDiAFdiES02mDl2NgJuWjbYfwSxQQQo0zNlfzvqwa6gs0OKDf+0Oi+h764Z0prDEUGHLy/cGe1L1olhuQprVw==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/shared": "^3.22.1",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5263,15 +5379,15 @@
             }
         },
         "node_modules/@react-spectrum/illustratedmessage": {
-            "version": "3.4.11",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/illustratedmessage/-/illustratedmessage-3.4.11.tgz",
-            "integrity": "sha512-KJdcF+YY2rPZBTP5P38MEQBoNLglP1QUaCmbPH8a5dSZ+wj5EsOgseFa0fxAEZ23q9Wo9Fv9P9niRq0/479KTQ==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/layout": "^3.6.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/illustratedmessage": "^3.3.7",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.5.0",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/illustratedmessage/-/illustratedmessage-3.5.0.tgz",
+            "integrity": "sha512-L994E940uDdXu0gcRlwZ8IMIVSjP3sfdRLH98RD1pPT+MYbzT7NN/Xj+YistaEG9D9J43liGY5R3fY39Njep0g==",
+            "dependencies": {
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/layout": "^3.6.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/illustratedmessage": "^3.3.8",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5280,14 +5396,14 @@
             }
         },
         "node_modules/@react-spectrum/image": {
-            "version": "3.4.11",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/image/-/image-3.4.11.tgz",
-            "integrity": "sha512-pzYubgkkC5Ic3eKrNlPUFPbVUTUvZ7DObmGpQvmgmG0xipmUAscYKFEBr4LhCsy/29Sm7Tz4zsGUdA2vM5eA8g==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/image": "^3.3.7",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.5.0",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/image/-/image-3.5.0.tgz",
+            "integrity": "sha512-r+vPqssEqRtrwi9gC1KhCN0wQCODGwrx9luVlxhXyBJzhQLVyb4vcDbtcVZ8HwZ8iOObjblX96ITmk6QGGDVaw==",
+            "dependencies": {
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/image": "^3.4.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5296,17 +5412,17 @@
             }
         },
         "node_modules/@react-spectrum/inlinealert": {
-            "version": "3.2.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/inlinealert/-/inlinealert-3.2.3.tgz",
-            "integrity": "sha512-NX1bZvmI3EOBTRWaT4dzVyFxAuePPbXASZeJ4/wAnhvWDN65rKrctn+VZW3x6ZQoZ/WkQl4qW1txjVcLHkRBWg==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/layout": "^3.6.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.2.4",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/inlinealert/-/inlinealert-3.2.4.tgz",
+            "integrity": "sha512-dTfCQMtv+mfdtixHDRBFwhtk7K0fLIoArGaYJOcifxRBmRWVB8np6rizpe/e89AECW4eyGeAJbvKgl8CcrFxwg==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/layout": "^3.6.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5315,18 +5431,18 @@
             }
         },
         "node_modules/@react-spectrum/label": {
-            "version": "3.16.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/label/-/label-3.16.4.tgz",
-            "integrity": "sha512-38hk6wqUwDCyYeScF1NIaxlPaltkTRTv6/XtIkm528eilLyOMxtMGXcP0Bl7nv0oDLxDZSm9TkgXjN24SyY0KQ==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/form": "^3.7.4",
-                "@react-spectrum/layout": "^3.6.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/label": "^3.9.1",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.16.5",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/label/-/label-3.16.5.tgz",
+            "integrity": "sha512-357dr3Yjkxs8oKVWwiVyoVyvsLeDre64/N5Td8KKV0vcI8DCfEXMCh8bFvEkD/NUoiNdgBjyGBwbNmG7eneWow==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/layout": "^3.6.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/label": "^3.9.2",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5335,16 +5451,16 @@
             }
         },
         "node_modules/@react-spectrum/labeledvalue": {
-            "version": "3.1.12",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/labeledvalue/-/labeledvalue-3.1.12.tgz",
-            "integrity": "sha512-Pa/eYf9FKr8QF7xKLoCv6a+Ezudfi7N1jFJLZ71zaVcDOcNByYKiy1lAquwLB+36PpsWt/EwKLE2/U7OuiWyfQ==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/label": "^3.16.4",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.1.13",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/labeledvalue/-/labeledvalue-3.1.13.tgz",
+            "integrity": "sha512-gokBp4CkV1Btn86w/yrKiqwfwiGND2CnIzx1dEXZLBdBCFCt9bE09YfGvQXeYO2GDj+q/WEBuCfWMKhcOdIQDw==",
+            "dependencies": {
+                "@internationalized/date": "^3.5.3",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/label": "^3.16.5",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5353,14 +5469,14 @@
             }
         },
         "node_modules/@react-spectrum/layout": {
-            "version": "3.6.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/layout/-/layout-3.6.3.tgz",
-            "integrity": "sha512-JsG4KclsgzKfRbe+PyMo8QryG5SmwYlym8JRuSLmZ+nfIKhESVbqSJE3IXblmDDWUb0rabQhSvW4p5h5nOil4w==",
+            "version": "3.6.4",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/layout/-/layout-3.6.4.tgz",
+            "integrity": "sha512-GY2O4IDbeEJHjxs6kp6WKnrFDTV/74HkCWjqwlYdYauSOIwldg1Kz2NBeJ3NpcM6zrXTdtoXXJcmv0+wu4sF4Q==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/layout": "^3.3.13",
-                "@react-types/shared": "^3.22.1",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/layout": "^3.3.14",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5369,16 +5485,16 @@
             }
         },
         "node_modules/@react-spectrum/link": {
-            "version": "3.6.5",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/link/-/link-3.6.5.tgz",
-            "integrity": "sha512-pnPIzkM0/Nxm460dHuR195bD0BAaIvasqW8TqUFT0Igxfw0NFYyWZr5d4OkHKyypyUoQKaobpOgeAJHXxPtzTg==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/link": "^3.6.5",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/link": "^3.5.3",
+            "version": "3.6.6",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/link/-/link-3.6.6.tgz",
+            "integrity": "sha512-zTMd2EgMbFom8mi/kwGxD7c39MP2bYuYX3agAuT3YiC2Mr6YVh2w6HWMn+jMc4HX9gzQvF69CDX4DnRi284xgQ==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/link": "^3.7.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/link": "^3.5.4",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5387,30 +5503,30 @@
             }
         },
         "node_modules/@react-spectrum/list": {
-            "version": "3.7.8",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/list/-/list-3.7.8.tgz",
-            "integrity": "sha512-w+fxiX+wz8X0bASAtKsf/myMrFI1Z0OXamttPRAJS5Ene6x09kf+K2djpZO1t9nT70IPnCcthhkMaCHk6eyUIw==",
-            "dependencies": {
-                "@react-aria/button": "^3.9.3",
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/gridlist": "^3.7.5",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-aria/virtualizer": "^3.9.10",
-                "@react-aria/visually-hidden": "^3.8.10",
-                "@react-spectrum/checkbox": "^3.9.4",
-                "@react-spectrum/dnd": "^3.3.8",
-                "@react-spectrum/layout": "^3.6.3",
-                "@react-spectrum/progress": "^3.7.5",
-                "@react-spectrum/text": "^3.5.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/layout": "^3.13.7",
-                "@react-stately/list": "^3.10.3",
-                "@react-types/grid": "^3.2.4",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.7.9",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/list/-/list-3.7.9.tgz",
+            "integrity": "sha512-dlmaeH4/64URHOrNBh8LrUAjyUMvHpPAwGnmLQ1oPGIUHJ8rwpc68K+RdpyZ/riuIHqWe/HqcLmnIlHSoSJEAw==",
+            "dependencies": {
+                "@react-aria/button": "^3.9.4",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/gridlist": "^3.8.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/virtualizer": "^3.10.0",
+                "@react-aria/visually-hidden": "^3.8.11",
+                "@react-spectrum/checkbox": "^3.9.5",
+                "@react-spectrum/dnd": "^3.3.9",
+                "@react-spectrum/layout": "^3.6.4",
+                "@react-spectrum/progress": "^3.7.6",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/layout": "^3.13.8",
+                "@react-stately/list": "^3.10.4",
+                "@react-types/grid": "^3.2.5",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0",
                 "react-transition-group": "^4.4.5"
             },
@@ -5421,27 +5537,27 @@
             }
         },
         "node_modules/@react-spectrum/listbox": {
-            "version": "3.12.7",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/listbox/-/listbox-3.12.7.tgz",
-            "integrity": "sha512-cFUIKPRtz5ViQYS0vO+oQmiFmbJjA04GCpyZKnBJAEonNDiY1RIHfFg/53Mr8IwstwNvGbxHVjMilPy6DtWDjA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/listbox": "^3.11.5",
-                "@react-aria/utils": "^3.23.2",
-                "@react-aria/virtualizer": "^3.9.10",
-                "@react-spectrum/layout": "^3.6.3",
-                "@react-spectrum/progress": "^3.7.5",
-                "@react-spectrum/text": "^3.5.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/layout": "^3.13.7",
-                "@react-stately/list": "^3.10.3",
-                "@react-stately/virtualizer": "^3.6.8",
-                "@react-types/listbox": "^3.4.7",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.12.8",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/listbox/-/listbox-3.12.8.tgz",
+            "integrity": "sha512-ZfUZRxzbXkmRRsC90dQsVYCqYxVjBb/8QHqFMKzSSARHmJCv57abCdqAS2eRPrfvoZVzIa1DsIVe1jq5mP5s0g==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/listbox": "^3.12.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/virtualizer": "^3.10.0",
+                "@react-spectrum/layout": "^3.6.4",
+                "@react-spectrum/progress": "^3.7.6",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/layout": "^3.13.8",
+                "@react-stately/list": "^3.10.4",
+                "@react-stately/virtualizer": "^3.7.0",
+                "@react-types/listbox": "^3.4.8",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5451,31 +5567,31 @@
             }
         },
         "node_modules/@react-spectrum/menu": {
-            "version": "3.18.1",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/menu/-/menu-3.18.1.tgz",
-            "integrity": "sha512-3KutRgn9/uYcRH2IzUT6qiPlS9SK9+4R2W4Z1Ox27lQ30ZgC68kE37uOwZX10bDgTOWV/COYvm6qBBScZ+VFUg==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/menu": "^3.13.1",
-                "@react-aria/overlays": "^3.21.1",
-                "@react-aria/separator": "^3.3.11",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/layout": "^3.6.3",
-                "@react-spectrum/overlays": "^5.5.5",
-                "@react-spectrum/text": "^3.5.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/menu": "^3.6.1",
-                "@react-stately/overlays": "^3.6.5",
-                "@react-stately/tree": "^3.7.6",
-                "@react-types/menu": "^3.9.7",
-                "@react-types/overlays": "^3.8.5",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
-                "@spectrum-icons/workflow": "^4.2.10",
+            "version": "3.19.0",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/menu/-/menu-3.19.0.tgz",
+            "integrity": "sha512-4UCxuX2t/qJFoCV430lSIZC1PUh1hgcULfTMv0u2S5Lz8DJfFH5tYfI2ChN/XeYqb8AwmjCWov6muH922ADwhw==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/menu": "^3.14.0",
+                "@react-aria/overlays": "^3.22.0",
+                "@react-aria/separator": "^3.3.12",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/layout": "^3.6.4",
+                "@react-spectrum/overlays": "^5.6.0",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/menu": "^3.7.0",
+                "@react-stately/overlays": "^3.6.6",
+                "@react-stately/tree": "^3.8.0",
+                "@react-types/menu": "^3.9.8",
+                "@react-types/overlays": "^3.8.6",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
+                "@spectrum-icons/workflow": "^4.2.11",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5485,15 +5601,15 @@
             }
         },
         "node_modules/@react-spectrum/meter": {
-            "version": "3.4.11",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/meter/-/meter-3.4.11.tgz",
-            "integrity": "sha512-OBYIHnumb2cfeT48py6Qtsfs7m9DmddaHSCVB8MXNnn3fzrpPr5t4Wz2dYUWXnkPPC11D8WarjJ+6EgrNwNvdQ==",
-            "dependencies": {
-                "@react-aria/meter": "^3.4.11",
-                "@react-spectrum/progress": "^3.7.5",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/meter": "^3.3.7",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.5.0",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/meter/-/meter-3.5.0.tgz",
+            "integrity": "sha512-AsBXu6+ZwVj8/pwXRiI8qkOl8KDUE6dLvhjfkuX5POh7T2yjHrZbrHtKplzcU6Bb2OrgW+EZEbCWqs6uHDIqHg==",
+            "dependencies": {
+                "@react-aria/meter": "^3.4.12",
+                "@react-spectrum/progress": "^3.7.6",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/meter": "^3.4.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5502,26 +5618,26 @@
             }
         },
         "node_modules/@react-spectrum/numberfield": {
-            "version": "3.9.1",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/numberfield/-/numberfield-3.9.1.tgz",
-            "integrity": "sha512-s6QNRncVrVjOYihKeyQ8fkdD1YJTqYttog/Rj96rD66vfUlI0bTr3q6mK6hZpU1YM29SL24BUCn6NdqOxqVeiQ==",
-            "dependencies": {
-                "@react-aria/button": "^3.9.3",
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/numberfield": "^3.11.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/form": "^3.7.4",
-                "@react-spectrum/label": "^3.16.4",
-                "@react-spectrum/textfield": "^3.11.4",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/numberfield": "^3.9.1",
-                "@react-types/button": "^3.9.2",
-                "@react-types/numberfield": "^3.8.1",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
-                "@spectrum-icons/workflow": "^4.2.10",
+            "version": "3.9.2",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/numberfield/-/numberfield-3.9.2.tgz",
+            "integrity": "sha512-eLHxVYYc2JqcImIr4vj/HzWRFTe1R2O+v7rJpStCUGWu4pSgElSCY6tHmn1woCF4AxSxsLviemwZ+oaz+42iWQ==",
+            "dependencies": {
+                "@react-aria/button": "^3.9.4",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/numberfield": "^3.11.2",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/label": "^3.16.5",
+                "@react-spectrum/textfield": "^3.12.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/numberfield": "^3.9.2",
+                "@react-types/button": "^3.9.3",
+                "@react-types/numberfield": "^3.8.2",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
+                "@spectrum-icons/workflow": "^4.2.11",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5531,17 +5647,17 @@
             }
         },
         "node_modules/@react-spectrum/overlays": {
-            "version": "5.5.5",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/overlays/-/overlays-5.5.5.tgz",
-            "integrity": "sha512-rFwbWuagFo/Tuo45UE8KHUMz1Li1j5CNzsZdIfKZydHd0u4t69yR8bCn3pDUNIYEwclrLGUD2XFJaN9jYq1H4A==",
-            "dependencies": {
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/overlays": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/overlays": "^3.6.5",
-                "@react-types/overlays": "^3.8.5",
-                "@react-types/shared": "^3.22.1",
+            "version": "5.6.0",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/overlays/-/overlays-5.6.0.tgz",
+            "integrity": "sha512-cP2SWSZ8US3+NtiFtkKn6yVNrra4wX59hliSrBijfFRHs1OFGSx3yLJfZtjibfEcdCj/0OPHyJvXRlxgG+vu4A==",
+            "dependencies": {
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/overlays": "^3.22.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/overlays": "^3.6.6",
+                "@react-types/overlays": "^3.8.6",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0",
                 "react-transition-group": "^4.4.5"
             },
@@ -5552,27 +5668,27 @@
             }
         },
         "node_modules/@react-spectrum/picker": {
-            "version": "3.14.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/picker/-/picker-3.14.3.tgz",
-            "integrity": "sha512-xaewWrOtGt/dQbiSwZe9UYH58berRlxyqIeAZQ7+FCRGoG/gl1p+l+7rfNKFS5OjVy6Adh6BZrrY6A99j4PbQA==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/select": "^3.14.3",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/form": "^3.7.4",
-                "@react-spectrum/label": "^3.16.4",
-                "@react-spectrum/listbox": "^3.12.7",
-                "@react-spectrum/overlays": "^5.5.5",
-                "@react-spectrum/progress": "^3.7.5",
-                "@react-spectrum/text": "^3.5.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/select": "^3.6.2",
-                "@react-types/select": "^3.9.2",
-                "@react-types/shared": "^3.22.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.14.4",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/picker/-/picker-3.14.4.tgz",
+            "integrity": "sha512-JM0cYnq0mGsOUwnbhRNWQ9jzi73Ub3S6+v5bzjyKps+96BYdW8Wr+KY/tQg49UCAcwcY0g30tbyEzqXzTpPkdA==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/select": "^3.14.4",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/label": "^3.16.5",
+                "@react-spectrum/listbox": "^3.12.8",
+                "@react-spectrum/overlays": "^5.6.0",
+                "@react-spectrum/progress": "^3.7.6",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/select": "^3.6.3",
+                "@react-types/select": "^3.9.3",
+                "@react-types/shared": "^3.23.0",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5582,15 +5698,15 @@
             }
         },
         "node_modules/@react-spectrum/progress": {
-            "version": "3.7.5",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/progress/-/progress-3.7.5.tgz",
-            "integrity": "sha512-ku+RTtgXWaxUwjopzWpvgNxJsn/gohMWO4len2K2yDQp6b4SY67ojRFBwyIDZ9Oi9RPTAvZOc3wJu5QPftfVcg==",
-            "dependencies": {
-                "@react-aria/progress": "^3.4.11",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/progress": "^3.5.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.7.6",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/progress/-/progress-3.7.6.tgz",
+            "integrity": "sha512-LlU8F43jWCse+3FgWPJNMb5wGQmk643ppbl2frxPHMiTb2YpaZYd+9vr2EndOrZpenjO2oTvDidgeprbm53LLA==",
+            "dependencies": {
+                "@react-aria/progress": "^3.4.12",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/progress": "^3.5.3",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5599,16 +5715,16 @@
             }
         },
         "node_modules/@react-spectrum/provider": {
-            "version": "3.9.5",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/provider/-/provider-3.9.5.tgz",
-            "integrity": "sha512-FE5RQC1EwMMXUAtslekheyA0IrXSev4flaY4CT3exC/ohB1PuiXUDg4qqaHos2vVhsdUPNq5SeathtHEr8ryJA==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/overlays": "^3.21.1",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/provider": "^3.7.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.9.6",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/provider/-/provider-3.9.6.tgz",
+            "integrity": "sha512-BSjO2CeUognPEzyLhFu25aVsBbpeoRlinCdQ/oMzvQwGxlcf/OKR6fkZMKJrpMuhh155ilDZXe0UFzl1RcziYA==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/overlays": "^3.22.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/provider": "^3.8.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0",
                 "clsx": "^2.0.0"
             },
@@ -5618,19 +5734,19 @@
             }
         },
         "node_modules/@react-spectrum/radio": {
-            "version": "3.7.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/radio/-/radio-3.7.4.tgz",
-            "integrity": "sha512-FmWcJlOeo5KsbWYpnEtoB9oC+haUeowfRda8dq0kBptR5WlagWD4Arez55/hjXCHLqQZ4r6vY6rYgMya2xiddQ==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/radio": "^3.10.2",
-                "@react-spectrum/form": "^3.7.4",
-                "@react-spectrum/label": "^3.16.4",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/radio": "^3.10.2",
-                "@react-types/radio": "^3.7.1",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.7.5",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/radio/-/radio-3.7.5.tgz",
+            "integrity": "sha512-g2Neh30G4UwOVBGVxvYxE+Bx+bVNFNOtK/MHCw+0tYZ0ysVYKVqqoNvQBmKNcqrDJaulnLwB9Igcm6SDjesGFA==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/radio": "^3.10.3",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/label": "^3.16.5",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/radio": "^3.10.3",
+                "@react-types/radio": "^3.8.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5639,19 +5755,19 @@
             }
         },
         "node_modules/@react-spectrum/searchfield": {
-            "version": "3.8.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/searchfield/-/searchfield-3.8.4.tgz",
-            "integrity": "sha512-O8pM9dGlRZoFhNtZLVFpIOHM2+sy5/t23Lv0z0jXvVYmzHOaib8wPPJELnPRsDUIylqpnVxfiYSz051Gx5Zq8g==",
-            "dependencies": {
-                "@react-aria/searchfield": "^3.7.3",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/form": "^3.7.4",
-                "@react-spectrum/textfield": "^3.11.4",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/searchfield": "^3.5.1",
-                "@react-types/searchfield": "^3.5.3",
-                "@react-types/textfield": "^3.9.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.8.5",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/searchfield/-/searchfield-3.8.5.tgz",
+            "integrity": "sha512-yhV5DBcI0gztcXamW1EYgr0SjVNjytUoEeR620IBb0Ta0g3XJOufVdmgJsW6114WMqQIXpiUq/Em8+Dy+iGqCg==",
+            "dependencies": {
+                "@react-aria/searchfield": "^3.7.4",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/textfield": "^3.12.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/searchfield": "^3.5.2",
+                "@react-types/searchfield": "^3.5.4",
+                "@react-types/textfield": "^3.9.2",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5660,20 +5776,20 @@
             }
         },
         "node_modules/@react-spectrum/slider": {
-            "version": "3.6.7",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/slider/-/slider-3.6.7.tgz",
-            "integrity": "sha512-mhTG+sGZwQfNW9z+uzvmTQzzV2Qzcack3qYSw8HXEfd4BOjkgwTinS9aN8MLv147TF5RMvI0y3dmNirAoDi+yQ==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/slider": "^3.7.6",
-                "@react-aria/utils": "^3.23.2",
-                "@react-aria/visually-hidden": "^3.8.10",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/slider": "^3.5.2",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/slider": "^3.7.1",
+            "version": "3.6.8",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/slider/-/slider-3.6.8.tgz",
+            "integrity": "sha512-P+0wtr4WePMU1CSW2AEGgm7l07EoC0lJGOrvQ4bif2Q56yE+QDQClXk6RAFY9eckK9dgMCXmYd5+pT8p4pZ24w==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/slider": "^3.7.7",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/visually-hidden": "^3.8.11",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/slider": "^3.5.3",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/slider": "^3.7.2",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5682,14 +5798,14 @@
             }
         },
         "node_modules/@react-spectrum/statuslight": {
-            "version": "3.5.11",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/statuslight/-/statuslight-3.5.11.tgz",
-            "integrity": "sha512-zbIrI8rPLmMu3Jnl2fpA/kb0V3azdRBsv1KfhGNQkHM17Gy6ufrzzzh6cmnvQh8FREllMcIjC4eaBkYHFArtyg==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/statuslight": "^3.3.7",
+            "version": "3.5.12",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/statuslight/-/statuslight-3.5.12.tgz",
+            "integrity": "sha512-1I5RaE1dCtmxCzUisE2xtQiD++8C2fzgyb0ayP554HSzsP+ZCE0mUxpW7DtoujASyR8h7E7M7wxP1SFEKgI3bA==",
+            "dependencies": {
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/statuslight": "^3.3.8",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5698,17 +5814,17 @@
             }
         },
         "node_modules/@react-spectrum/switch": {
-            "version": "3.5.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/switch/-/switch-3.5.3.tgz",
-            "integrity": "sha512-NJMk/b/Lrmgl0RB9QPF0wSuD5CgTPODaLuZD1uxXAcESKFVdN58eW8CU99e+ZC0tHgFofroTpEF073Yiz2LnkQ==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/switch": "^3.6.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/toggle": "^3.7.2",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/switch": "^3.5.1",
+            "version": "3.5.4",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/switch/-/switch-3.5.4.tgz",
+            "integrity": "sha512-8efBXAL2AR2+Vicazyc3z/Us4/dkm2Gi/CpCBhAEDZne2fnNM6X7zFXHEDhMgPn4oDZeUj76VwmBM4MEr+3m3g==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/switch": "^3.6.3",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/toggle": "^3.7.3",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/switch": "^3.5.2",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5717,33 +5833,34 @@
             }
         },
         "node_modules/@react-spectrum/table": {
-            "version": "3.12.8",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/table/-/table-3.12.8.tgz",
-            "integrity": "sha512-4JyDlvKljhvqqUHYk6X5JjmxXy03IZergKTu//MbELW4v9bwiwf94Ap8zTcUZPh/pcw7IQhpUDLPAi6RFBAUpA==",
-            "dependencies": {
-                "@react-aria/button": "^3.9.3",
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/table": "^3.13.5",
-                "@react-aria/utils": "^3.23.2",
-                "@react-aria/virtualizer": "^3.9.10",
-                "@react-aria/visually-hidden": "^3.8.10",
-                "@react-spectrum/checkbox": "^3.9.4",
-                "@react-spectrum/dnd": "^3.3.8",
-                "@react-spectrum/layout": "^3.6.3",
-                "@react-spectrum/menu": "^3.18.1",
-                "@react-spectrum/progress": "^3.7.5",
-                "@react-spectrum/tooltip": "^3.6.5",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/flags": "^3.0.1",
-                "@react-stately/layout": "^3.13.7",
-                "@react-stately/table": "^3.11.6",
-                "@react-stately/virtualizer": "^3.6.8",
-                "@react-types/grid": "^3.2.4",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/table": "^3.9.3",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.12.9",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/table/-/table-3.12.9.tgz",
+            "integrity": "sha512-lFEkHLuMd5owB9vQjeGDOiBah/xOn4zUBtieMKHP6xLh7vUG27zr6vcQzpizNsHT3273Rktr34IjRf/U26Mu4w==",
+            "dependencies": {
+                "@react-aria/button": "^3.9.4",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/overlays": "^3.22.0",
+                "@react-aria/table": "^3.14.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/virtualizer": "^3.10.0",
+                "@react-aria/visually-hidden": "^3.8.11",
+                "@react-spectrum/checkbox": "^3.9.5",
+                "@react-spectrum/dnd": "^3.3.9",
+                "@react-spectrum/layout": "^3.6.4",
+                "@react-spectrum/menu": "^3.19.0",
+                "@react-spectrum/progress": "^3.7.6",
+                "@react-spectrum/tooltip": "^3.6.6",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/flags": "^3.0.2",
+                "@react-stately/layout": "^3.13.8",
+                "@react-stately/table": "^3.11.7",
+                "@react-stately/virtualizer": "^3.7.0",
+                "@react-types/grid": "^3.2.5",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/table": "^3.9.4",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5753,24 +5870,24 @@
             }
         },
         "node_modules/@react-spectrum/tabs": {
-            "version": "3.8.8",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/tabs/-/tabs-3.8.8.tgz",
-            "integrity": "sha512-oBXKLD7cyCLQV3SUXIjEn9ttdyoGwpiTUfM3DNY3ki097pRdoBgMxPk1Sp7jKQs6tRUg4osK53iOG4LvEs6eTg==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/tabs": "^3.8.5",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/picker": "^3.14.3",
-                "@react-spectrum/text": "^3.5.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/list": "^3.10.3",
-                "@react-stately/tabs": "^3.6.4",
-                "@react-types/select": "^3.9.2",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/tabs": "^3.3.5",
+            "version": "3.8.9",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/tabs/-/tabs-3.8.9.tgz",
+            "integrity": "sha512-7lgM47iEVNrz6c4XLU6IOivWyh22lmuq0AGCKIel8tJgWM137qEnHkqKmHg9VWAueaKbxW6iWEYuQX6InEeFwA==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/tabs": "^3.9.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/picker": "^3.14.4",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/list": "^3.10.4",
+                "@react-stately/tabs": "^3.6.5",
+                "@react-types/select": "^3.9.3",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/tabs": "^3.3.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5780,24 +5897,24 @@
             }
         },
         "node_modules/@react-spectrum/tag": {
-            "version": "3.2.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/tag/-/tag-3.2.4.tgz",
-            "integrity": "sha512-d6cqX+n10pokLsBGEC/ksLLHM3vBoR7zc0AAlBAUrBN6VMBWUQRyazDHeXPLLZs/S4+ICL80UXRf8NwWfA7Efg==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/selection": "^3.17.5",
-                "@react-aria/tag": "^3.3.3",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/button": "^3.16.2",
-                "@react-spectrum/form": "^3.7.4",
-                "@react-spectrum/label": "^3.16.4",
-                "@react-spectrum/text": "^3.5.3",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/list": "^3.10.3",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.2.5",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/tag/-/tag-3.2.5.tgz",
+            "integrity": "sha512-/DbGrTX0OovTrxYjUM3L+2RXIGJMO0srI5jGd0dA6uzs8t7fA1GUySarpSpttiZqQDyrasNA3+mnG5xVfcRoPw==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/selection": "^3.18.0",
+                "@react-aria/tag": "^3.4.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/label": "^3.16.5",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/list": "^3.10.4",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5807,15 +5924,16 @@
             }
         },
         "node_modules/@react-spectrum/text": {
-            "version": "3.5.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/text/-/text-3.5.3.tgz",
-            "integrity": "sha512-28WiSnIPm8WC4JOsHPLudCGjPc3tk7zKm3L+8xqzr374j1+HdMGivrhVOf1t4jbUMcT5dOxR7huuDe7E97qe5A==",
+            "version": "3.5.4",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/text/-/text-3.5.4.tgz",
+            "integrity": "sha512-VsCsuHxpycvKWUkwZKBIg0Seu3ylaGtgkDDKgw5uyIKENdhz/W9h6FXnKm4O3VgI8snIySnnHGSa00Z4b6hMig==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/text": "^3.3.7",
-                "@swc/helpers": "^0.5.0"
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/text": "^3.3.8",
+                "@swc/helpers": "^0.5.0",
+                "react-aria-components": "^1.2.0"
             },
             "peerDependencies": {
                 "@react-spectrum/provider": "^3.0.0",
@@ -5823,21 +5941,21 @@
             }
         },
         "node_modules/@react-spectrum/textfield": {
-            "version": "3.11.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/textfield/-/textfield-3.11.4.tgz",
-            "integrity": "sha512-J70Q1ddpX0v+R7x8w5DaJLixnp5mBWm39lUnekiahEUuA420U1eZCW80GiHXFS/GO6wpwjIZpWHcpBQuF/EDCQ==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/interactions": "^3.21.1",
-                "@react-aria/textfield": "^3.14.3",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/form": "^3.7.4",
-                "@react-spectrum/label": "^3.16.4",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/textfield": "^3.9.1",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.12.0",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/textfield/-/textfield-3.12.0.tgz",
+            "integrity": "sha512-ZupbZ/cyIFMyxb2rCcy+4FQ8lsh/I3AXrsZ9VNE1KjEklR5s8In7cIjFtlfJlk8CX8B5KL69crhyLoe2hUxX5g==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/textfield": "^3.14.4",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/label": "^3.16.5",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/textfield": "^3.9.2",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5846,11 +5964,11 @@
             }
         },
         "node_modules/@react-spectrum/theme-dark": {
-            "version": "3.5.8",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/theme-dark/-/theme-dark-3.5.8.tgz",
-            "integrity": "sha512-ep78rIrgrXu5MHe6LQAiK8+Us/4PoeHcixm+xFJk8I7AP+6wrh+9+QSWdIi3/NJY9fIjXsMm+VyehFVtEq8PAg==",
+            "version": "3.5.9",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/theme-dark/-/theme-dark-3.5.9.tgz",
+            "integrity": "sha512-9aHHosP1X+qGIgQLXCg4tb/8g+UA5YDhAXSoa7T2fem+V/SZtTv0TqUPZZpyUlMXM0DoDlGmN7BFpG5S7vm6+A==",
             "dependencies": {
-                "@react-types/provider": "^3.7.2",
+                "@react-types/provider": "^3.8.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5858,11 +5976,11 @@
             }
         },
         "node_modules/@react-spectrum/theme-default": {
-            "version": "3.5.8",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/theme-default/-/theme-default-3.5.8.tgz",
-            "integrity": "sha512-vZ9GH4l6H2Ks/PD15k2mx6XgA1EYKxQbB+ai9UZ3ALXf0KYlVIFSoHk4Vp3hP16D12KsuB7V0Gk8SvHlsRNo+Q==",
+            "version": "3.5.9",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/theme-default/-/theme-default-3.5.9.tgz",
+            "integrity": "sha512-+Ebngv8waP3sGsGUFtr1py61T0Iv79YJz6YB2geJbPeFbrtiTxaTxFoJLsaHCRFcgcJFxAu4hrNT+brR/UMt4w==",
             "dependencies": {
-                "@react-types/provider": "^3.7.2",
+                "@react-types/provider": "^3.8.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5870,11 +5988,11 @@
             }
         },
         "node_modules/@react-spectrum/theme-light": {
-            "version": "3.4.8",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/theme-light/-/theme-light-3.4.8.tgz",
-            "integrity": "sha512-6kjtQ6IGrh2E0WJKQ9eAR5yhcWFI2mNvd+wi8Uy3rtRdBB1L3iSfr2RmgxgcTCl50DnKqmcFsHbHaKMMKqhkaQ==",
+            "version": "3.4.9",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/theme-light/-/theme-light-3.4.9.tgz",
+            "integrity": "sha512-ZlImejzK+yAEWjxV2hqo8cdqHQc/y+ZOexF30lR6fez6O59nrNC0ETJqBTYEMWqrCHiqJk+2MD+uZf5RNHeyxg==",
             "dependencies": {
-                "@react-types/provider": "^3.7.2",
+                "@react-types/provider": "^3.8.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5882,21 +6000,21 @@
             }
         },
         "node_modules/@react-spectrum/tooltip": {
-            "version": "3.6.5",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/tooltip/-/tooltip-3.6.5.tgz",
-            "integrity": "sha512-TnVUX8gNx65wOfHE5UIzuzpC4kUd6RuFeDykcu+3OM+fsK6qvDYg2MQq/3UiZHFI0ZHhX89yPf4uLaP0hcT8sg==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.2",
-                "@react-aria/overlays": "^3.21.1",
-                "@react-aria/tooltip": "^3.7.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/overlays": "^5.5.5",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-stately/tooltip": "^3.4.7",
-                "@react-types/overlays": "^3.8.5",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/tooltip": "^3.4.7",
-                "@spectrum-icons/ui": "^3.6.5",
+            "version": "3.6.6",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/tooltip/-/tooltip-3.6.6.tgz",
+            "integrity": "sha512-xqqu8dNoSHOXrjczH3IzplDy+mB8hZb0/vk0IB61xJr+O8hjfN4jlgBrKqbuzxeZyFLKV5GY7b9D8jkLE/9v3w==",
+            "dependencies": {
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/overlays": "^3.22.0",
+                "@react-aria/tooltip": "^3.7.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/overlays": "^5.6.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-stately/tooltip": "^3.4.8",
+                "@react-types/overlays": "^3.8.6",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/tooltip": "^3.4.8",
+                "@spectrum-icons/ui": "^3.6.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5906,14 +6024,14 @@
             }
         },
         "node_modules/@react-spectrum/utils": {
-            "version": "3.11.5",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/utils/-/utils-3.11.5.tgz",
-            "integrity": "sha512-V4heIuCBXhYOP3om5B0KNs2+RK6RKwAhHVjjJZ3RBeNqsF9UDxv+D/+dk3sAM2dsM1F8l38BNXQMMWXWW/BZYA==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.2",
-                "@react-aria/ssr": "^3.9.2",
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.11.6",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/utils/-/utils-3.11.6.tgz",
+            "integrity": "sha512-CAjPqeP9haHmU60WE3lAWNMSx9B80LT/umNETC7YWkryT1cxMi4nn+jPXhYzozvDrrbGSTjdcuMo203Ywmj+tw==",
+            "dependencies": {
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/ssr": "^3.9.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0",
                 "clsx": "^2.0.0"
             },
@@ -5922,14 +6040,14 @@
             }
         },
         "node_modules/@react-spectrum/view": {
-            "version": "3.6.8",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/view/-/view-3.6.8.tgz",
-            "integrity": "sha512-jAKh22xUjFY5sxPTtLc53fw3C+dki+DEDxArFK06/tF+ntV6lDat91j+YedcOtDT2p7C2PSfaToX5moyEAQ+NA==",
+            "version": "3.6.9",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/view/-/view-3.6.9.tgz",
+            "integrity": "sha512-auIXiyfMK+BjqCvGTPb9uENPed26byr4mL8MW89NW2x39jaRejLyLUNKuElIE4PkGCJQA5CHNOmObAHZR/XA8Q==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/view": "^3.4.7",
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/view": "^3.4.8",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5938,14 +6056,14 @@
             }
         },
         "node_modules/@react-spectrum/well": {
-            "version": "3.4.11",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/well/-/well-3.4.11.tgz",
-            "integrity": "sha512-oCQqKKcKXNoF18dNz5GHBdSXrNPsVH5jsH17suqQevzDQkMN2X02Ov0oyOYMAUQM0xSE3ensLdjvLzUL0yl36g==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-spectrum/utils": "^3.11.5",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/well": "^3.3.7",
+            "version": "3.4.12",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/well/-/well-3.4.12.tgz",
+            "integrity": "sha512-/3msBtaiKge9K0nAua4K2YFYXZiYHCcnY2dWzlLrov75uQPaRiTyzxXYqRT+OQ62vPHtu1FQ5BsJDecoM8jedw==",
+            "dependencies": {
+                "@react-aria/utils": "^3.24.0",
+                "@react-spectrum/utils": "^3.11.6",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/well": "^3.3.8",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5953,14 +6071,14 @@
             }
         },
         "node_modules/@react-stately/calendar": {
-            "version": "3.4.4",
-            "resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.4.4.tgz",
-            "integrity": "sha512-f9ZOd096gGGD+3LmU1gkmfqytGyQtrgi+Qjn+70GbM2Jy65pwOR4I9YrobbmeAFov5Tff13mQEa0yqWvbcDLZQ==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.2",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/calendar": "^3.4.4",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.5.0",
+            "resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.5.0.tgz",
+            "integrity": "sha512-tINxgGAeZ9KsYNomuR50PljG2mN9C9FWQ8zyvATfFq44EFcjjdXCMNWV+qgIRKGKLwrSJhu3boPaiHVIpUxrXA==",
+            "dependencies": {
+                "@internationalized/date": "^3.5.3",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/calendar": "^3.4.5",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5968,14 +6086,14 @@
             }
         },
         "node_modules/@react-stately/checkbox": {
-            "version": "3.6.3",
-            "resolved": "https://registry.npmjs.org/@react-stately/checkbox/-/checkbox-3.6.3.tgz",
-            "integrity": "sha512-hWp0GXVbMI4sS2NbBjWgOnHNrRqSV4jeftP8zc5JsIYRmrWBUZitxluB34QuVPzrBO29bGsF0GTArSiQZt6BWw==",
+            "version": "3.6.4",
+            "resolved": "https://registry.npmjs.org/@react-stately/checkbox/-/checkbox-3.6.4.tgz",
+            "integrity": "sha512-gecaRtWeQNoJuSl3AtfV6z6LjaUV578Kzbag8d3pTPbGXl8komTtTj/26nIEPsmf/L8jZ3kCscDGxGTKr+7sqg==",
             "dependencies": {
-                "@react-stately/form": "^3.0.1",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/checkbox": "^3.7.1",
-                "@react-types/shared": "^3.22.1",
+                "@react-stately/form": "^3.0.2",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/checkbox": "^3.8.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5983,11 +6101,31 @@
             }
         },
         "node_modules/@react-stately/collections": {
-            "version": "3.10.5",
-            "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.10.5.tgz",
-            "integrity": "sha512-k8Q29Nnvb7iAia1QvTanZsrWP2aqVNBy/1SlE6kLL6vDqtKZC+Esd1SDLHRmIcYIp5aTdfwIGd0NuiRQA7a81Q==",
+            "version": "3.10.6",
+            "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.10.6.tgz",
+            "integrity": "sha512-hb/yzxQnZaSRu43iR6ftkCJIqD4Qu5WUjl4ASBn2EGb9TmipA7bFnYVqSH4xFPCCTZ68Qxh95dOcxYBHlHeWZQ==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1",
+                "@react-types/shared": "^3.23.0",
+                "@swc/helpers": "^0.5.0"
+            },
+            "peerDependencies": {
+                "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+            }
+        },
+        "node_modules/@react-stately/color": {
+            "version": "3.6.0",
+            "resolved": "https://registry.npmjs.org/@react-stately/color/-/color-3.6.0.tgz",
+            "integrity": "sha512-sjQDjE1zBAnaJaqssP4Ep63Pd32xvLOwcfUmXAOua6s+8CKVlKQqr5BoBiBnqvGGmy7oTlEJd1dkfZA+bvh/zw==",
+            "dependencies": {
+                "@internationalized/number": "^3.5.2",
+                "@internationalized/string": "^3.2.2",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-stately/form": "^3.0.2",
+                "@react-stately/numberfield": "^3.9.2",
+                "@react-stately/slider": "^3.5.3",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/color": "3.0.0-beta.24",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5995,18 +6133,18 @@
             }
         },
         "node_modules/@react-stately/combobox": {
-            "version": "3.8.2",
-            "resolved": "https://registry.npmjs.org/@react-stately/combobox/-/combobox-3.8.2.tgz",
-            "integrity": "sha512-f+IHuFW848VoMbvTfSakn2WIh2urDxO355LrKxnisXPCkpQHpq3lvT2mJtKJwkPxjAy7xPjpV8ejgga2R6p53Q==",
-            "dependencies": {
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/form": "^3.0.1",
-                "@react-stately/list": "^3.10.3",
-                "@react-stately/overlays": "^3.6.5",
-                "@react-stately/select": "^3.6.2",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/combobox": "^3.10.1",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.8.3",
+            "resolved": "https://registry.npmjs.org/@react-stately/combobox/-/combobox-3.8.3.tgz",
+            "integrity": "sha512-lmwt2M39jHQUA9CWKhTc9MVoUBKuJM1Y+9GYPElON8P/guQL6G3bM1u8I4Hxf0zzGzAIW3ymV57bF9mcaA/nzA==",
+            "dependencies": {
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/form": "^3.0.2",
+                "@react-stately/list": "^3.10.4",
+                "@react-stately/overlays": "^3.6.6",
+                "@react-stately/select": "^3.6.3",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/combobox": "^3.11.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6014,11 +6152,11 @@
             }
         },
         "node_modules/@react-stately/data": {
-            "version": "3.11.2",
-            "resolved": "https://registry.npmjs.org/@react-stately/data/-/data-3.11.2.tgz",
-            "integrity": "sha512-yhK2upk2WbJeiLBRWHrh/4G2CvmmozCzoivLaRAPYu53m1J3MyzVGCLJgnZMbMZvAbNcYWZK6IzO6VqZ2y1fOw==",
+            "version": "3.11.3",
+            "resolved": "https://registry.npmjs.org/@react-stately/data/-/data-3.11.3.tgz",
+            "integrity": "sha512-iyg27s3uUquxvX5AXckAgOPszG6uX7fCOIg0D/GJXxdFxLv1gFSupIeIJhM+kXufPgpaLiugaQxoYlO5y65shA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6026,17 +6164,17 @@
             }
         },
         "node_modules/@react-stately/datepicker": {
-            "version": "3.9.2",
-            "resolved": "https://registry.npmjs.org/@react-stately/datepicker/-/datepicker-3.9.2.tgz",
-            "integrity": "sha512-Z6FrK6Af7R5BizqHhJFCj3Hn32mg5iLSDdEgFQAuO043guOXUKFUAnbxfbQUjL6PGE6QwWMfQD7PPGebHn9Ifw==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.2",
-                "@internationalized/string": "^3.2.1",
-                "@react-stately/form": "^3.0.1",
-                "@react-stately/overlays": "^3.6.5",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/datepicker": "^3.7.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.9.3",
+            "resolved": "https://registry.npmjs.org/@react-stately/datepicker/-/datepicker-3.9.3.tgz",
+            "integrity": "sha512-NjZ8uqxmKf7mGLNWSZsvm22xX46k+yo0QkPspONuorHFTf8qqCnp4i+bBpEpaVCwX5KVSRdjxJOk7XhvJF8q4w==",
+            "dependencies": {
+                "@internationalized/date": "^3.5.3",
+                "@internationalized/string": "^3.2.2",
+                "@react-stately/form": "^3.0.2",
+                "@react-stately/overlays": "^3.6.6",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/datepicker": "^3.7.3",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6044,12 +6182,12 @@
             }
         },
         "node_modules/@react-stately/dnd": {
-            "version": "3.2.8",
-            "resolved": "https://registry.npmjs.org/@react-stately/dnd/-/dnd-3.2.8.tgz",
-            "integrity": "sha512-oSo+2Bzum3Q1/d+3FuaDmpVHqqBB004tycuQDDFtad3N1BKm+fNfmslRK1ioLkPLK4sm1130V+BZBY3JXLe80A==",
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/@react-stately/dnd/-/dnd-3.3.0.tgz",
+            "integrity": "sha512-bHH3B4wFqfATpyxpP5Wdv/5uQdci4WvStJgeExj7Yy2UwYSsZEnS6Ky0MhLLFdIyUpragjiSCzYcYwwli6oHUQ==",
             "dependencies": {
-                "@react-stately/selection": "^3.14.3",
-                "@react-types/shared": "^3.22.1",
+                "@react-stately/selection": "^3.15.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6057,9 +6195,9 @@
             }
         },
         "node_modules/@react-stately/flags": {
-            "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.0.1.tgz",
-            "integrity": "sha512-h5PcDMj54aipQNO18ig/IMI1kzPwcvSwVq5M6Ib6XE1WIkOH0dIuW2eADdAOhcGi3KXJtXVdD29zh0Eox1TKgQ==",
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.0.2.tgz",
+            "integrity": "sha512-/KyVJMND2WdkgoHpt+m+ash7h5q9pq91DLgyizQWcbf2xphicH9D1HKAB8co3Cfvq6T/QqjQEP8aBkheiPyfEg==",
             "dependencies": {
                 "@swc/helpers": "^0.4.14"
             }
@@ -6074,11 +6212,11 @@
             }
         },
         "node_modules/@react-stately/form": {
-            "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.0.1.tgz",
-            "integrity": "sha512-T1Ul2Ou0uE/S4ECLcGKa0OfXjffdjEHfUFZAk7OZl0Mqq/F7dl5WpoLWJ4d4IyvZzGO6anFNenP+vODWbrF3NA==",
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.0.2.tgz",
+            "integrity": "sha512-MA4P9lHv770I3DJpJTQlkh5POVuklmeQuixwlbyKzlWT+KqFSOXvqaliszqU7gyDdVGAFksMa6E3mXbGbk1wuA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6086,14 +6224,14 @@
             }
         },
         "node_modules/@react-stately/grid": {
-            "version": "3.8.5",
-            "resolved": "https://registry.npmjs.org/@react-stately/grid/-/grid-3.8.5.tgz",
-            "integrity": "sha512-KCzi0x0p1ZKK+OptonvJqMbn6Vlgo6GfOIlgcDd0dNYDP8TJ+3QFJAFre5mCr7Fubx7LcAOio4Rij0l/R8fkXQ==",
-            "dependencies": {
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/selection": "^3.14.3",
-                "@react-types/grid": "^3.2.4",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.8.6",
+            "resolved": "https://registry.npmjs.org/@react-stately/grid/-/grid-3.8.6.tgz",
+            "integrity": "sha512-XkxDfaIAWzbsb5pnL2IE4FqQbqegVzPnU+R2ZvDrJT7514I2usSMoJ2ZUUoy8DIYQomJHB5QKZeyQkGIelHMcg==",
+            "dependencies": {
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/selection": "^3.15.0",
+                "@react-types/grid": "^3.2.5",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6101,16 +6239,16 @@
             }
         },
         "node_modules/@react-stately/layout": {
-            "version": "3.13.7",
-            "resolved": "https://registry.npmjs.org/@react-stately/layout/-/layout-3.13.7.tgz",
-            "integrity": "sha512-9HH/aSxpEHwUW1T1vGN3+iznkAXQUzoMrsoEepNzesOsUGSm/MFZmEk4+9cdPA7y3ou2eHpGNUB1YIDDVptElg==",
-            "dependencies": {
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/table": "^3.11.6",
-                "@react-stately/virtualizer": "^3.6.8",
-                "@react-types/grid": "^3.2.4",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/table": "^3.9.3",
+            "version": "3.13.8",
+            "resolved": "https://registry.npmjs.org/@react-stately/layout/-/layout-3.13.8.tgz",
+            "integrity": "sha512-WDusSjT8w6vgzgMS7Hwl5zx7rhivMIjFo0a0UqQahgF4kXYPqeEYMOgoczbFqCGgS/vQKOmMy5Rk6fzBb73bdA==",
+            "dependencies": {
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/table": "^3.11.7",
+                "@react-stately/virtualizer": "^3.7.0",
+                "@react-types/grid": "^3.2.5",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/table": "^3.9.4",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6118,14 +6256,14 @@
             }
         },
         "node_modules/@react-stately/list": {
-            "version": "3.10.3",
-            "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.10.3.tgz",
-            "integrity": "sha512-Ul8el0tQy2Ucl3qMQ0fiqdJ874W1ZNjURVSgSxN+pGwVLNBVRjd6Fl7YwZFCXER2YOlzkwg+Zqozf/ZlS0EdXA==",
-            "dependencies": {
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/selection": "^3.14.3",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.10.4",
+            "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.10.4.tgz",
+            "integrity": "sha512-sj501OKcQr+1Zdo0m6NuvpZDHLE0tUdReSKcWqt35odzC6ic/qr7C7ozZ/5ay+nuHTryUUTC/mDQ0zlBmQX0dA==",
+            "dependencies": {
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/selection": "^3.15.0",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6133,13 +6271,13 @@
             }
         },
         "node_modules/@react-stately/menu": {
-            "version": "3.6.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/menu/-/menu-3.6.1.tgz",
-            "integrity": "sha512-3v0vkTm/kInuuG8jG7jbxXDBnMQcoDZKWvYsBQq7+POt0LmijbLdbdZPBoz9TkZ3eo/OoP194LLHOaFTQyHhlw==",
+            "version": "3.7.0",
+            "resolved": "https://registry.npmjs.org/@react-stately/menu/-/menu-3.7.0.tgz",
+            "integrity": "sha512-8UJhvKEF+zaHXrwv0YhFr73OSEprzIs6xRNoV6F/omd4twy1ngPZrL1X8HNzaXsf5BrHuib2tbh81e/Z95D3nA==",
             "dependencies": {
-                "@react-stately/overlays": "^3.6.5",
-                "@react-types/menu": "^3.9.7",
-                "@react-types/shared": "^3.22.1",
+                "@react-stately/overlays": "^3.6.6",
+                "@react-types/menu": "^3.9.8",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6147,14 +6285,14 @@
             }
         },
         "node_modules/@react-stately/numberfield": {
-            "version": "3.9.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/numberfield/-/numberfield-3.9.1.tgz",
-            "integrity": "sha512-btBIcBEfSVCUm6NwJrMrMygoIu/fQGazzD0RhF7PNsfvkFiWn+TSOyQqSXcsUJVOnBfoS/dVWj6r57KA7zl3FA==",
-            "dependencies": {
-                "@internationalized/number": "^3.5.1",
-                "@react-stately/form": "^3.0.1",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/numberfield": "^3.8.1",
+            "version": "3.9.2",
+            "resolved": "https://registry.npmjs.org/@react-stately/numberfield/-/numberfield-3.9.2.tgz",
+            "integrity": "sha512-Sp+0MnqaqZ/Tu8qalZXbMIXyvZ7aXIny2PxNIxmnqxVHfxIzQCLJW5Y4bJr1yJIHH3QDZic5OyqS72MBWBXnIA==",
+            "dependencies": {
+                "@internationalized/number": "^3.5.2",
+                "@react-stately/form": "^3.0.2",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/numberfield": "^3.8.2",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6162,12 +6300,12 @@
             }
         },
         "node_modules/@react-stately/overlays": {
-            "version": "3.6.5",
-            "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.5.tgz",
-            "integrity": "sha512-U4rCFj6TPJPXLUvYXAcvh+yP/CO2W+7f0IuqP7ZZGE+Osk9qFkT+zRK5/6ayhBDFpmueNfjIEAzT9gYPQwNHFw==",
+            "version": "3.6.6",
+            "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.6.tgz",
+            "integrity": "sha512-NvzQXh4zYGZuUmZH5d3NmEDNr8r1hfub2s5w7WOeIG35xqIzoKGdFZ7LLWrie+4nxPmM+ckdfqOQ9pBZFNJypQ==",
             "dependencies": {
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/overlays": "^3.8.5",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/overlays": "^3.8.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6175,14 +6313,14 @@
             }
         },
         "node_modules/@react-stately/radio": {
-            "version": "3.10.2",
-            "resolved": "https://registry.npmjs.org/@react-stately/radio/-/radio-3.10.2.tgz",
-            "integrity": "sha512-JW5ZWiNMKcZvMTsuPeWJQLHXD5rlqy7Qk6fwUx/ZgeibvMBW/NnW19mm2+IMinzmbtERXvR6nsiA837qI+4dew==",
-            "dependencies": {
-                "@react-stately/form": "^3.0.1",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/radio": "^3.7.1",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.10.3",
+            "resolved": "https://registry.npmjs.org/@react-stately/radio/-/radio-3.10.3.tgz",
+            "integrity": "sha512-EWLLRgLQ9orI7G9uPuJv1bdZPu3OoRWy1TGSn+6G8b8rleNx3haI4eZUR+JGB0YNgemotMz/gbNTNG/wEIsRgw==",
+            "dependencies": {
+                "@react-stately/form": "^3.0.2",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/radio": "^3.8.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6190,12 +6328,12 @@
             }
         },
         "node_modules/@react-stately/searchfield": {
-            "version": "3.5.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/searchfield/-/searchfield-3.5.1.tgz",
-            "integrity": "sha512-9A8Wghx1avRHhMpNH1Nj+jFfiF1bhsff2GEC5PZgWYzhCykw3G5bywn3JAuUS4kh7Vpqhbu4KpHAhmWPSv4B/Q==",
+            "version": "3.5.2",
+            "resolved": "https://registry.npmjs.org/@react-stately/searchfield/-/searchfield-3.5.2.tgz",
+            "integrity": "sha512-M73mhUCbY5aJAtVH8BF9TeBwOtEMC7G1N/W6j71V8f3MlN0ppN0n4pZWW3CMd2x0BKuYum8KPvcL1DgiXzoo5A==",
             "dependencies": {
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/searchfield": "^3.5.3",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/searchfield": "^3.5.4",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6203,15 +6341,15 @@
             }
         },
         "node_modules/@react-stately/select": {
-            "version": "3.6.2",
-            "resolved": "https://registry.npmjs.org/@react-stately/select/-/select-3.6.2.tgz",
-            "integrity": "sha512-duOxdHKol93h6Ew6fap6Amz+zngoERKZLSKVm/8I8uaBgkoBhEeTFv7mlpHTgINxymMw3mMrvy6GL/gfKFwkqg==",
-            "dependencies": {
-                "@react-stately/form": "^3.0.1",
-                "@react-stately/list": "^3.10.3",
-                "@react-stately/overlays": "^3.6.5",
-                "@react-types/select": "^3.9.2",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.6.3",
+            "resolved": "https://registry.npmjs.org/@react-stately/select/-/select-3.6.3.tgz",
+            "integrity": "sha512-d/ha6j0oiEaw/F5hgPgCZg1e8LObNmvsocEebxXPToVdwHd9H55r2Fogi5nLoiX8geHKiYm0KPfSxs/oXbW/5Q==",
+            "dependencies": {
+                "@react-stately/form": "^3.0.2",
+                "@react-stately/list": "^3.10.4",
+                "@react-stately/overlays": "^3.6.6",
+                "@react-types/select": "^3.9.3",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6219,13 +6357,13 @@
             }
         },
         "node_modules/@react-stately/selection": {
-            "version": "3.14.3",
-            "resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.14.3.tgz",
-            "integrity": "sha512-d/t0rIWieqQ7wjLoMoWnuHEUSMoVXxkPBFuSlJF3F16289FiQ+b8aeKFDzFTYN7fFD8rkZTnpuE4Tcxg3TmA+w==",
+            "version": "3.15.0",
+            "resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.15.0.tgz",
+            "integrity": "sha512-OtypXNtvRWLmpkaktluzCYEXKXAON16WIJv2mZ4cae3H0UVfWaFL9sD+ST9nj7UqYNTDXECug5ziIY+YKd7zvA==",
             "dependencies": {
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/shared": "^3.22.1",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6233,13 +6371,13 @@
             }
         },
         "node_modules/@react-stately/slider": {
-            "version": "3.5.2",
-            "resolved": "https://registry.npmjs.org/@react-stately/slider/-/slider-3.5.2.tgz",
-            "integrity": "sha512-ntH3NLRG+AwVC7q4Dx9DcmMkMh9vmHjHNXAgaoqNjhvwfSIae7sQ69CkVe6XeJjIBy6LlH81Kgapz+ABe5a1ZA==",
+            "version": "3.5.3",
+            "resolved": "https://registry.npmjs.org/@react-stately/slider/-/slider-3.5.3.tgz",
+            "integrity": "sha512-jA0XR7GjtwoucLw8kx/KB50pSGNUbR7xNfM9t5H8D7k3wd+j4yqfarWyNFyPX/X5MJez+/bd+BIDJUl3XGOWkA==",
             "dependencies": {
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/slider": "^3.7.1",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/slider": "^3.7.2",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6247,18 +6385,18 @@
             }
         },
         "node_modules/@react-stately/table": {
-            "version": "3.11.6",
-            "resolved": "https://registry.npmjs.org/@react-stately/table/-/table-3.11.6.tgz",
-            "integrity": "sha512-34YsfOILXusj3p6QNcKEaDWVORhM6WEhwPSLCZlkwAJvkxuRQFdih5rQKoIDc0uV5aZsB6bYBqiFhnjY0VERhw==",
-            "dependencies": {
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/flags": "^3.0.1",
-                "@react-stately/grid": "^3.8.5",
-                "@react-stately/selection": "^3.14.3",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/grid": "^3.2.4",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/table": "^3.9.3",
+            "version": "3.11.7",
+            "resolved": "https://registry.npmjs.org/@react-stately/table/-/table-3.11.7.tgz",
+            "integrity": "sha512-VvazamtoXLENeWJAYF1fJzfIAXO2qbiXCfosRLgkEMtoU2kGqV8DHYQhIXuqwMRn8nO8GVw9hgAiQQcKghgCXA==",
+            "dependencies": {
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/flags": "^3.0.2",
+                "@react-stately/grid": "^3.8.6",
+                "@react-stately/selection": "^3.15.0",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/grid": "^3.2.5",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/table": "^3.9.4",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6266,13 +6404,13 @@
             }
         },
         "node_modules/@react-stately/tabs": {
-            "version": "3.6.4",
-            "resolved": "https://registry.npmjs.org/@react-stately/tabs/-/tabs-3.6.4.tgz",
-            "integrity": "sha512-WZJgMBqzLgN88RN8AxhY4aH1+I+4w1qQA0Lh3LRSDegaytd+NHixCWaP3IPjePgCB5N1UsPe96Xglw75zjHmDg==",
+            "version": "3.6.5",
+            "resolved": "https://registry.npmjs.org/@react-stately/tabs/-/tabs-3.6.5.tgz",
+            "integrity": "sha512-z1saZgGRqb0AsoRi19iE4JOJaIRV73GjRnzUX9QSl3gpK75XsH31vbmtUYiXOXAd6Dt+1KFLgbyeCzMUlZEnMw==",
             "dependencies": {
-                "@react-stately/list": "^3.10.3",
-                "@react-types/shared": "^3.22.1",
-                "@react-types/tabs": "^3.3.5",
+                "@react-stately/list": "^3.10.4",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/tabs": "^3.3.6",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6280,12 +6418,12 @@
             }
         },
         "node_modules/@react-stately/toggle": {
-            "version": "3.7.2",
-            "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.7.2.tgz",
-            "integrity": "sha512-SHCF2btcoK57c4lyhucRbyPBAFpp0Pdp0vcPdn3hUgqbu6e5gE0CwG/mgFmZRAQoc7PRc7XifL0uNw8diJJI0Q==",
+            "version": "3.7.3",
+            "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.7.3.tgz",
+            "integrity": "sha512-4jW6wxTu7Gkq6/2mZWqtJoQ6ff27Cl6lnVMEXXM+M8HwK/3zHoMZhVz8EApwgOsRByxDQ76PNSGm3xKZAcqZNw==",
             "dependencies": {
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/checkbox": "^3.7.1",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/checkbox": "^3.8.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6293,12 +6431,12 @@
             }
         },
         "node_modules/@react-stately/tooltip": {
-            "version": "3.4.7",
-            "resolved": "https://registry.npmjs.org/@react-stately/tooltip/-/tooltip-3.4.7.tgz",
-            "integrity": "sha512-ACtRgBQ8rphBtsUaaxvEAM0HHN9PvMuyvL0vUHd7jvBDCVZJ6it1BKu9SBKjekBkoBOw9nemtkplh9R2CA6V8Q==",
+            "version": "3.4.8",
+            "resolved": "https://registry.npmjs.org/@react-stately/tooltip/-/tooltip-3.4.8.tgz",
+            "integrity": "sha512-0D3cCeQhX5DjDpeuzFJwfX8SxIOxdL2iWPPjpC3hIxkUKuItavSq2A7G2tO39vpiip3RBOaaQMUpnSmjRK5DAQ==",
             "dependencies": {
-                "@react-stately/overlays": "^3.6.5",
-                "@react-types/tooltip": "^3.4.7",
+                "@react-stately/overlays": "^3.6.6",
+                "@react-types/tooltip": "^3.4.8",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6306,14 +6444,14 @@
             }
         },
         "node_modules/@react-stately/tree": {
-            "version": "3.7.6",
-            "resolved": "https://registry.npmjs.org/@react-stately/tree/-/tree-3.7.6.tgz",
-            "integrity": "sha512-y8KvEoZX6+YvqjNCVGS3zA/BKw4D3XrUtUKIDme3gu5Mn6z97u+hUXKdXVCniZR7yvV3fHAIXwE5V2K8Oit4aw==",
-            "dependencies": {
-                "@react-stately/collections": "^3.10.5",
-                "@react-stately/selection": "^3.14.3",
-                "@react-stately/utils": "^3.9.1",
-                "@react-types/shared": "^3.22.1",
+            "version": "3.8.0",
+            "resolved": "https://registry.npmjs.org/@react-stately/tree/-/tree-3.8.0.tgz",
+            "integrity": "sha512-7bfbCLjG8BTiWuo9GBE1A375PPI4S9r/rMtKQGLQvYAObgJb7C8P3svA9WKfryvl7M5iqaYrOVA0uzNSmeCNQQ==",
+            "dependencies": {
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/selection": "^3.15.0",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6321,9 +6459,9 @@
             }
         },
         "node_modules/@react-stately/utils": {
-            "version": "3.9.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.9.1.tgz",
-            "integrity": "sha512-yzw75GE0iUWiyps02BOAPTrybcsMIxEJlzXqtvllAb01O9uX5n0i3X+u2eCpj2UoDF4zS08Ps0jPgWxg8xEYtA==",
+            "version": "3.10.0",
+            "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.0.tgz",
+            "integrity": "sha512-nji2i9fTYg65ZWx/3r11zR1F2tGya+mBubRCbMTwHyRnsSLFZaeq/W6lmrOyIy1uMJKBNKLJpqfmpT4x7rw6pg==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0"
             },
@@ -6332,12 +6470,12 @@
             }
         },
         "node_modules/@react-stately/virtualizer": {
-            "version": "3.6.8",
-            "resolved": "https://registry.npmjs.org/@react-stately/virtualizer/-/virtualizer-3.6.8.tgz",
-            "integrity": "sha512-Pf06ihTwExRJltGhi72tmLIo0pcjkL55nu7ifMafAAdxZK4ONxRLSuUjjpvYf/0Rs92xRZy2t/XmHREnfirdkQ==",
+            "version": "3.7.0",
+            "resolved": "https://registry.npmjs.org/@react-stately/virtualizer/-/virtualizer-3.7.0.tgz",
+            "integrity": "sha512-Wkh502y6mzUvjJJr30p5FLRwBaphnfmnoSnGwidamwo3HuyrDICBSlwFGPl0AmUHo1afSaLXl6j8smU48VcClA==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.2",
-                "@react-types/shared": "^3.22.1",
+                "@react-aria/utils": "^3.24.0",
+                "@react-types/shared": "^3.23.0",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6345,470 +6483,482 @@
             }
         },
         "node_modules/@react-types/actionbar": {
-            "version": "3.1.5",
-            "resolved": "https://registry.npmjs.org/@react-types/actionbar/-/actionbar-3.1.5.tgz",
-            "integrity": "sha512-Z3hfIoaOaW8wJxQm1NyWVvSftpNDYv9iWqpEWBEdhxuqsUkOVszZ7KcNaF4qsm4bJIcJWn3FNKhaTKGwISZcdQ==",
+            "version": "3.1.6",
+            "resolved": "https://registry.npmjs.org/@react-types/actionbar/-/actionbar-3.1.6.tgz",
+            "integrity": "sha512-P2yp2eM/x7Vjc/wFu+DGsKLTfIYIlUSKGN4Ao+xk6v0fleOHkcHOep1Y3fwOtc1WGHk7HSeoxScFiw5d3e73eg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/actiongroup": {
-            "version": "3.4.7",
-            "resolved": "https://registry.npmjs.org/@react-types/actiongroup/-/actiongroup-3.4.7.tgz",
-            "integrity": "sha512-VsyHn6mGqEHKEIGFiHTq7rSuzuQjGVZGtnhh/9jQXW6zoSJyoM4fAnHEt+RE92NdiRv5e3+OzzrwG0TZsi87cQ==",
+            "version": "3.4.8",
+            "resolved": "https://registry.npmjs.org/@react-types/actiongroup/-/actiongroup-3.4.8.tgz",
+            "integrity": "sha512-IT3eSs1Lu9jN+EeoJL70f4VOXLeK5JlhtWjioPKz6JCmr/coUSHtFTLEgfUSzvCo98XJa4w8oC49ds7CkZBUGg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/avatar": {
-            "version": "3.0.5",
-            "resolved": "https://registry.npmjs.org/@react-types/avatar/-/avatar-3.0.5.tgz",
-            "integrity": "sha512-/hM/BnRoUhusT16G4hf2hfe4FocxaM5H/a7aetUrmlr9e5ppX3n0VsU0btIwxdh+aJL8u2BBt2scoiJaujQPNg==",
+            "version": "3.0.6",
+            "resolved": "https://registry.npmjs.org/@react-types/avatar/-/avatar-3.0.6.tgz",
+            "integrity": "sha512-hDCbtP0zU4H50psNiyhmyboT5UkwEK9EY0lxqKuBkF62kfP57d5dyS6TeD3XkdPbeCEOR06ttGjFQDwjFPrdvg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/badge": {
-            "version": "3.1.7",
-            "resolved": "https://registry.npmjs.org/@react-types/badge/-/badge-3.1.7.tgz",
-            "integrity": "sha512-2Q5W9gleI83WpL1i6qPdcPC8l1V7364dpkNLcMi9DFmM2l69+QcPvSWVQMQLevmRo4cnaQ+TYsOgYv8I4rRJCg==",
+            "version": "3.1.8",
+            "resolved": "https://registry.npmjs.org/@react-types/badge/-/badge-3.1.8.tgz",
+            "integrity": "sha512-rL/Qa3GpxYn6cFiI+oLWVuNQ45qOufCRejimWNMws+ieY4+lvxNEvZfbOPTEOT+2sdQG4i9QpV2eHuFL1mVK9w==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/breadcrumbs": {
-            "version": "3.7.3",
-            "resolved": "https://registry.npmjs.org/@react-types/breadcrumbs/-/breadcrumbs-3.7.3.tgz",
-            "integrity": "sha512-eFto/+6J+JR58vThNcALZRA1OlqlG3GzQ/bq3q8IrrkOZcrfbEJJCWit/+53Ia98siJKuF4OJHnotxIVIz5I3w==",
+            "version": "3.7.4",
+            "resolved": "https://registry.npmjs.org/@react-types/breadcrumbs/-/breadcrumbs-3.7.4.tgz",
+            "integrity": "sha512-gQPLi71i+4zE6m5S74v7bpZ/yBERtlUt5qBcvB4C7gJu8aR4cFrv1YFZ//9f8uwlAHjau7XBpVlbBDlhfb2aOQ==",
             "dependencies": {
-                "@react-types/link": "^3.5.3",
-                "@react-types/shared": "^3.22.1"
+                "@react-types/link": "^3.5.4",
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/button": {
-            "version": "3.9.2",
-            "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.9.2.tgz",
-            "integrity": "sha512-EnPTkGHZRtiwAoJy5q9lDjoG30bEzA/qnvKG29VVXKYAGeqY2IlFs1ypmU+z1X/CpJgPcG3I5cakM7yTVm3pSg==",
+            "version": "3.9.3",
+            "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.9.3.tgz",
+            "integrity": "sha512-YHlSeH85FhasJXOmkY4x+6If74ZpUh88C2fMlw0HUA/Bq/KGckUoriV8cnMqSnB1OwPqi8dpBZGfFVj6f6lh9A==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/buttongroup": {
-            "version": "3.3.7",
-            "resolved": "https://registry.npmjs.org/@react-types/buttongroup/-/buttongroup-3.3.7.tgz",
-            "integrity": "sha512-EZ/1k66GtDtWfE/udNRz2UBbt4YJumeyW1SGcZEO3jketvpms58WapLsMkCezlLz7WYaa5ADV0kU8v88APjI6g==",
+            "version": "3.3.8",
+            "resolved": "https://registry.npmjs.org/@react-types/buttongroup/-/buttongroup-3.3.8.tgz",
+            "integrity": "sha512-BlpLX8Qd2hlN9zWFqDEb3STR6gJF3ukDiLg/JjqTgWLPg7BK1t2MIk2zVr+Y2Wq5+jIq281+BvLP5NhYIcWJhA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/calendar": {
-            "version": "3.4.4",
-            "resolved": "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.4.4.tgz",
-            "integrity": "sha512-hV1Thmb/AES5OmfPvvmyjSkmsEULjiDfA7Yyy70L/YKuSNKb7Su+Bf2VnZuDW3ec+GxO4JJNlpJ0AkbphWBvcg==",
+            "version": "3.4.5",
+            "resolved": "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.4.5.tgz",
+            "integrity": "sha512-FAAUbqe8iPiNf/OtdxnpOuAEJzyeRgfK2QCzfb4BIVnNNaTDkbxGCI5wrqHfBQ4FASECJeNlkjYXtbvijaooyw==",
             "dependencies": {
-                "@internationalized/date": "^3.5.2",
-                "@react-types/shared": "^3.22.1"
+                "@internationalized/date": "^3.5.3",
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/checkbox": {
-            "version": "3.7.1",
-            "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.7.1.tgz",
-            "integrity": "sha512-kuGqjQFex0As/3gfWyk+e9njCcad/ZdnYLLiNvhlk15730xfa0MmnOdpqo9jfuFSXBjOcpxoofvEhvrRMtEdUA==",
+            "version": "3.8.0",
+            "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.8.0.tgz",
+            "integrity": "sha512-IBJ2bAsb3xoXaL+f0pwfRLDvRkhxfcX/q4NRJ2oT9jeHLU+j6svgK1Dqk8IGmY+vw1ltKbbMlIVeVonKQ3fgHw==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
+            },
+            "peerDependencies": {
+                "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+            }
+        },
+        "node_modules/@react-types/color": {
+            "version": "3.0.0-beta.24",
+            "resolved": "https://registry.npmjs.org/@react-types/color/-/color-3.0.0-beta.24.tgz",
+            "integrity": "sha512-YSb/9ERmFp7JxC+vjhqpcYI8HUt92SSv6aDEB+e//4RzBnCxvy+YQAGei9tI6PYIgzGgMLwEMkgpgH/7UcFblA==",
+            "dependencies": {
+                "@react-types/shared": "^3.23.0",
+                "@react-types/slider": "^3.7.2"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/combobox": {
-            "version": "3.10.1",
-            "resolved": "https://registry.npmjs.org/@react-types/combobox/-/combobox-3.10.1.tgz",
-            "integrity": "sha512-XMno1rgVRNta49vf5nV7VJpVSVAV20tt79t618gG1qRKH5Kt2Cy8lz2fQ5vHG6UTv/6jUOvU8g5Pc93sLaTmoA==",
+            "version": "3.11.0",
+            "resolved": "https://registry.npmjs.org/@react-types/combobox/-/combobox-3.11.0.tgz",
+            "integrity": "sha512-L6EEcIUIk7lsVvhO1Z1bklgH5bM84fBht03TC+es9YvS2T1Z9hdtyjBFcH6b3lVW9RwAArdUTL82/RNtvgD0Eg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/contextualhelp": {
-            "version": "3.2.8",
-            "resolved": "https://registry.npmjs.org/@react-types/contextualhelp/-/contextualhelp-3.2.8.tgz",
-            "integrity": "sha512-Yb0zOqkWfjKCO+gpfVj69CfWPhVq6RTgwLBV47ntIlHitpbh7RDYwDVulPyaVoopPbNSFUoV4YIJZPlOgpbKjg==",
+            "version": "3.2.9",
+            "resolved": "https://registry.npmjs.org/@react-types/contextualhelp/-/contextualhelp-3.2.9.tgz",
+            "integrity": "sha512-/YN8e1ujYSY99dnF2x/+vDErx/LcuXJmoKhvQL0wWrQAOIiGCP4j/ifVLWSt6sKhLdkPPbCNHxAd6Jq2SbC8ng==",
             "dependencies": {
-                "@react-types/overlays": "^3.8.5",
-                "@react-types/shared": "^3.22.1"
+                "@react-types/overlays": "^3.8.6",
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/datepicker": {
-            "version": "3.7.2",
-            "resolved": "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.7.2.tgz",
-            "integrity": "sha512-zThqFAdhQL1dqyVDsDSSTdfCjoD6634eyg/B0ZJfQxcLUR/5pch3v/gxBhbyCVDGMNHRWUWIJvY9DVOepuoSug==",
+            "version": "3.7.3",
+            "resolved": "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.7.3.tgz",
+            "integrity": "sha512-SpA91itY03QaBvTAGP4X62SEAOoKJr91Av/U5DgH8gP7Ev4Ui+I3Aqh+w8Qw6nxKX4aAvDUx6wEHwLQLbvJUPA==",
             "dependencies": {
-                "@internationalized/date": "^3.5.2",
-                "@react-types/calendar": "^3.4.4",
-                "@react-types/overlays": "^3.8.5",
-                "@react-types/shared": "^3.22.1"
+                "@internationalized/date": "^3.5.3",
+                "@react-types/calendar": "^3.4.5",
+                "@react-types/overlays": "^3.8.6",
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/dialog": {
-            "version": "3.5.8",
-            "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.8.tgz",
-            "integrity": "sha512-RX8JsMvty8ADHRqVEkppoynXLtN4IzUh8d5z88UEBbcvWKlHfd6bOBQjQcBH3AUue5wjfpPIt6brw2VzgBY/3Q==",
+            "version": "3.5.9",
+            "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.9.tgz",
+            "integrity": "sha512-8r9P1b1gq/cUv2bTPPNL3IFVEj9R5sIPACoSXznXkpXxh5FLU6yUPHDeQjvmM50q7KlEOgrPYhGl5pW525kLww==",
             "dependencies": {
-                "@react-types/overlays": "^3.8.5",
-                "@react-types/shared": "^3.22.1"
+                "@react-types/overlays": "^3.8.6",
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/divider": {
-            "version": "3.3.7",
-            "resolved": "https://registry.npmjs.org/@react-types/divider/-/divider-3.3.7.tgz",
-            "integrity": "sha512-enAlzHVwzqBPpmE+/VAO5IGVPxPrscHkdbnEkTqW9JQeMUGXd4L0TdHOGd+vtcA+gILJMnhUfT3XE4wQ4HlpVw==",
+            "version": "3.3.8",
+            "resolved": "https://registry.npmjs.org/@react-types/divider/-/divider-3.3.8.tgz",
+            "integrity": "sha512-klWNt9vaSwNnIZo0D44Flk4oGWsHXdgpUwdCU94VPeJEm9DY20areIOuSPQXRcEazinEPGo+C+d6Ytxzy2pyfA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/form": {
-            "version": "3.7.2",
-            "resolved": "https://registry.npmjs.org/@react-types/form/-/form-3.7.2.tgz",
-            "integrity": "sha512-6/isEJY4PsYoHdMaGQtqQyquXGTwB1FqCBOPKQjI/vBGWG3fL7FGfWm4Z62eTbCH4Xyv3FZuNywlT8UjPMQyKA==",
+            "version": "3.7.3",
+            "resolved": "https://registry.npmjs.org/@react-types/form/-/form-3.7.3.tgz",
+            "integrity": "sha512-RwVxgbuKuByIXTF9HnvIhc/KijzT10R8rnI2Hr/51nTH+QDkunmLtmzW279m4YJRT3Dlj94DRy/7SS/WNCT2FA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/grid": {
-            "version": "3.2.4",
-            "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.2.4.tgz",
-            "integrity": "sha512-sDVoyQcH7MoGdx5nBi5ZOU/mVFBt9YTxhvr0PZ97dMdEHZtJC1w9SuezwWS34f50yb8YAXQRTICbZYcK4bAlDA==",
+            "version": "3.2.5",
+            "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.2.5.tgz",
+            "integrity": "sha512-kvE3Y+i0/RGLrf8qn/uVK1nVxXygNf5Jm6h9S6UdZkEVsclcqHKIX8UzqQgEUTd99jMHZk7fbKPm/La8uJ9yFQ==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/illustratedmessage": {
-            "version": "3.3.7",
-            "resolved": "https://registry.npmjs.org/@react-types/illustratedmessage/-/illustratedmessage-3.3.7.tgz",
-            "integrity": "sha512-r8WAv+w0WQSDRmDmG6jL0dXK8MjMr/lr2gBpVYVvC45Gu+fqf4VL+4zphgwASRysWclFbjQhgkgC7OEXk3GUKg==",
+            "version": "3.3.8",
+            "resolved": "https://registry.npmjs.org/@react-types/illustratedmessage/-/illustratedmessage-3.3.8.tgz",
+            "integrity": "sha512-AIQdkfMYPvhUp2rko5E/UXaKz+9cfsZAHpk0ff+7x3mYWxUTCDXk8LVN/f8avZVzfa4w1UyOtzzNo/BssdLpuA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/image": {
-            "version": "3.3.7",
-            "resolved": "https://registry.npmjs.org/@react-types/image/-/image-3.3.7.tgz",
-            "integrity": "sha512-ty6wuan6p3Z8OzSrbjtL2k8yXHW+n0qiYKyqKpSA8TcRLDLhtvRceI7X12RuDfbAiEN6/zfLCuF47O3y7OeKug==",
+            "version": "3.4.0",
+            "resolved": "https://registry.npmjs.org/@react-types/image/-/image-3.4.0.tgz",
+            "integrity": "sha512-0zwHdfnbW5DjBaQ7s+SAiLJr0vPm+rzGEJUb+iXbgarPYYIHQpaJ/bPgvUU8BY1T95SQczB+AYownDJ+XESdsg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/label": {
-            "version": "3.9.1",
-            "resolved": "https://registry.npmjs.org/@react-types/label/-/label-3.9.1.tgz",
-            "integrity": "sha512-0N/UOhwrKjE9VjVbUoJCsH5UwTzSu3d5B3xtvhH8YFqSbJCI+HMKILby8i8ys55RA9fQDMScIjCJafEFWW7UWA==",
+            "version": "3.9.2",
+            "resolved": "https://registry.npmjs.org/@react-types/label/-/label-3.9.2.tgz",
+            "integrity": "sha512-N7Sj4M9LHcMOTrq7DW4KcVl2DgcBIADTY6Ni50g5tN6o+TjVj3KDkX3ivsCNUHu/AJnJ8q7E+J74R4pDnQOAoQ==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/layout": {
-            "version": "3.3.13",
-            "resolved": "https://registry.npmjs.org/@react-types/layout/-/layout-3.3.13.tgz",
-            "integrity": "sha512-Y3pmSsfDB+bV299F5SJannR6m/XM5IyfdqjPS0/Qx8KBBf0A3UWfL6qUr4BMEAKsEJuIXA7zT3RSo/AbGSbSyg==",
+            "version": "3.3.14",
+            "resolved": "https://registry.npmjs.org/@react-types/layout/-/layout-3.3.14.tgz",
+            "integrity": "sha512-PUxGdXXvRt8dQ5MOnxh8UkZjO3c89eWceLv4gtBizZKv+LRYfHof/eYH+9FtQkxGziWUJ6G7bzFt93Q9ydqH5Q==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/link": {
-            "version": "3.5.3",
-            "resolved": "https://registry.npmjs.org/@react-types/link/-/link-3.5.3.tgz",
-            "integrity": "sha512-yVafjW3IejyVnK3oMBNjFABCGG6J27EUG8rvkaGaI1uB6srGUEhpJ97XLv11aj1QkXHBy3VGXqxEV3S7wn4HTw==",
+            "version": "3.5.4",
+            "resolved": "https://registry.npmjs.org/@react-types/link/-/link-3.5.4.tgz",
+            "integrity": "sha512-5hVAlKE4wiEVHmkqQG9/G4sdar257CISmLzWh9xf8heq14a93MBIHm7S9mhHULk2a84EC9bNoTi8Hh6P6nnMEw==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/listbox": {
-            "version": "3.4.7",
-            "resolved": "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.4.7.tgz",
-            "integrity": "sha512-68y5H9CVSPFiwO6MOFxTbry9JQMK/Lb1M9i3M8TDyq1AbJxBPpgAvJ9RaqIMCucsnqCzpY/zA3D/X417zByL1w==",
+            "version": "3.4.8",
+            "resolved": "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.4.8.tgz",
+            "integrity": "sha512-HNLBvyhR02p8GaZsW8hAu4YwkDjaG/rcuCT/l4Sdxzsm7szPlFMEVBZ9Ji3Ffzj+9P20OgFJ+VylWs7EkUwJAA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/menu": {
-            "version": "3.9.7",
-            "resolved": "https://registry.npmjs.org/@react-types/menu/-/menu-3.9.7.tgz",
-            "integrity": "sha512-K6KhloJVoGsqwkdeez72fkNI9dfrmLI/sNrB4XuOKo2crDQ/eyZYWyJmzz8giz/tHME9w774k487rVoefoFh5w==",
+            "version": "3.9.8",
+            "resolved": "https://registry.npmjs.org/@react-types/menu/-/menu-3.9.8.tgz",
+            "integrity": "sha512-nkRCsfD3NXsJOv6mAnXCFyH2eGOFsmOOJOBQeOl9dj7BcdX9dcqp2PzUWPl33GrY9rYcXiRx4wsbUoqO1KVU4g==",
             "dependencies": {
-                "@react-types/overlays": "^3.8.5",
-                "@react-types/shared": "^3.22.1"
+                "@react-types/overlays": "^3.8.6",
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/meter": {
-            "version": "3.3.7",
-            "resolved": "https://registry.npmjs.org/@react-types/meter/-/meter-3.3.7.tgz",
-            "integrity": "sha512-p+YJ0+Lpn5MLmlbFZbDH1P0ILv1+AuMcUbxLcXMIVMGn7o0FO7eVZnFuq76D+qTDm9all+TRLJix7bctOrP+5Q==",
+            "version": "3.4.0",
+            "resolved": "https://registry.npmjs.org/@react-types/meter/-/meter-3.4.0.tgz",
+            "integrity": "sha512-1czayiwMcg3QxRxQQSm9hvPbzPk1lyNmP68mDsWdVuY7fUTsUvItF05IkeJCkEB8tIqfBKnJHYAJN1XLY+5bfg==",
             "dependencies": {
-                "@react-types/progress": "^3.5.2"
+                "@react-types/progress": "^3.5.3"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/numberfield": {
-            "version": "3.8.1",
-            "resolved": "https://registry.npmjs.org/@react-types/numberfield/-/numberfield-3.8.1.tgz",
-            "integrity": "sha512-GaCjLQgXUGCt40SLjKk3/COMWFlN2vV/3Xs3VSLAEdFZpk99b+Ik1oR21+7ZP5/iMHuQDc1MJRWdFfIjxCvVDQ==",
+            "version": "3.8.2",
+            "resolved": "https://registry.npmjs.org/@react-types/numberfield/-/numberfield-3.8.2.tgz",
+            "integrity": "sha512-2i7Je7fEYA4ousL9WhKZg+6Hejwgiq1AmoJpan6JfeIMQkvQ92q+klq02cih/lLXY/jvjd/KI3fa1fl3dfnaFw==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/overlays": {
-            "version": "3.8.5",
-            "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.5.tgz",
-            "integrity": "sha512-4D7EEBQigD/m8hE68Ys8eloyyZFHHduqykSIgINJ0edmo0jygRbWlTwuhWFR9USgSP4dK54duN0Mvq0m4HEVEw==",
+            "version": "3.8.6",
+            "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.6.tgz",
+            "integrity": "sha512-7xBuroYqwADppt7IRGfM8lbxVwlZrhMtTzeIdUot595cqFdRlpd/XAo2sRnEeIjYW9OSI8I5v4kt3AG7bdCQlg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/progress": {
-            "version": "3.5.2",
-            "resolved": "https://registry.npmjs.org/@react-types/progress/-/progress-3.5.2.tgz",
-            "integrity": "sha512-aQql22kusEudsHwDEzq6y/Mh29AM+ftRDKdS5E5g4MkCY5J4FMbOYco1T5So83NIvvG9+eKcxPoJUMjQQACAyA==",
+            "version": "3.5.3",
+            "resolved": "https://registry.npmjs.org/@react-types/progress/-/progress-3.5.3.tgz",
+            "integrity": "sha512-IcICNYRPFHQxl6iXi5jDgSZ3I9k2UQ2rIFcnoGo43K0hekv6fRdbbXWJU9ndShs3OfCHTPHEV5ooYB3UujNOAQ==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/provider": {
-            "version": "3.7.2",
-            "resolved": "https://registry.npmjs.org/@react-types/provider/-/provider-3.7.2.tgz",
-            "integrity": "sha512-nzxbfuh/ZJXt/blGAiRPkxi9jAUnnBkHcvljqdfizfLJlN5epaYYaTWNUSde27Oe1tAnpm2WDlLQo5+0C6C0FA==",
+            "version": "3.8.0",
+            "resolved": "https://registry.npmjs.org/@react-types/provider/-/provider-3.8.0.tgz",
+            "integrity": "sha512-7iexxkfJtYBvpiv+fpuh7s62uQ5nzFbVNvgOHU1sBgXiyVGoA6QfTsybf6uQJ/S7yUgqoO5CrVqHKFa4MUxYIg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/radio": {
-            "version": "3.7.1",
-            "resolved": "https://registry.npmjs.org/@react-types/radio/-/radio-3.7.1.tgz",
-            "integrity": "sha512-Zut3rN1odIUBLZdijeyou+UqsLeRE76d9A+npykYGu29ndqmo3w4sLn8QeQcdj1IR71ZnG0pW2Y2BazhK5XrrQ==",
+            "version": "3.8.0",
+            "resolved": "https://registry.npmjs.org/@react-types/radio/-/radio-3.8.0.tgz",
+            "integrity": "sha512-0gvG74lgiaRo0DO46hoB5NxGFXhq5DsHaPZcCcb9VZ8cCzZMrO7U/B3JhF82TI2DndSx/AoiAMOQsc0v4ZwiGg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/searchfield": {
-            "version": "3.5.3",
-            "resolved": "https://registry.npmjs.org/@react-types/searchfield/-/searchfield-3.5.3.tgz",
-            "integrity": "sha512-gBfsT1WpY8UIb74yyYmnjiHpVasph2mdmGj9i8cGF2HUYwx5p+Fr85mtCGDph0uirvRoM5ExMp4snD+ueNAVCg==",
+            "version": "3.5.4",
+            "resolved": "https://registry.npmjs.org/@react-types/searchfield/-/searchfield-3.5.4.tgz",
+            "integrity": "sha512-D7tUwlbUxyTzxhMYWNMdY9lp/a/kdr9mIGB7K3j/QSQhTI2T9H3VPxEKXmYt33cE3T7Q1DDsII1SrChI/KEdxA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1",
-                "@react-types/textfield": "^3.9.1"
+                "@react-types/shared": "^3.23.0",
+                "@react-types/textfield": "^3.9.2"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/select": {
-            "version": "3.9.2",
-            "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.2.tgz",
-            "integrity": "sha512-fGFrunednY3Pq/BBwVOf87Fsuyo/SlevL0wFIE9OOl2V5NXVaTY7/7RYA8hIOHPzmvsMbndy419BEudiNGhv4A==",
+            "version": "3.9.3",
+            "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.3.tgz",
+            "integrity": "sha512-hK5RvA6frMbLdynRkegNW1lMOD0l9aFsW9X8WuTAg0zV6iZouU0hhSCT6JRDefJrv+m0X3fRdohMuVNZOhlA1g==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/shared": {
-            "version": "3.22.1",
-            "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.22.1.tgz",
-            "integrity": "sha512-PCpa+Vo6BKnRMuOEzy5zAZ3/H5tnQg1e80khMhK2xys0j6ZqzkgQC+fHMNZ7VDFNLqqNMj/o0eVeSBDh2POjkw==",
+            "version": "3.23.0",
+            "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.23.0.tgz",
+            "integrity": "sha512-GQm/iPiii3ikcaMNR4WdVkJ4w0mKtV3mLqeSfSqzdqbPr6vONkqXbh3RhPlPmAJs1b4QHnexd/wZQP3U9DHOwQ==",
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/slider": {
-            "version": "3.7.1",
-            "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.7.1.tgz",
-            "integrity": "sha512-FKO3YZYdrBs00XbBW5acP+0L1cCdevl/uRJiXbnLpGysO5PrSFIRS7Wlv4M7ztf6gT7b1Ao4FNC9crbxBr6BzA==",
+            "version": "3.7.2",
+            "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.7.2.tgz",
+            "integrity": "sha512-HvC/Mdt/z741xcU0ymeNxslnowQ5EAHOSzyf2JMgXmle+pEIbbepz5QUVaOmEveQHS3bjxE/+n2yBTKbxP8CJg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/statuslight": {
-            "version": "3.3.7",
-            "resolved": "https://registry.npmjs.org/@react-types/statuslight/-/statuslight-3.3.7.tgz",
-            "integrity": "sha512-PFctvM8NJFENNWHydEK4pCMXKmjrHB2PJ/fdJ2Bi6ABRcTBXzi/qgUUdbeTG7STXlA+P2/xNgKtHheus8K+K3g==",
+            "version": "3.3.8",
+            "resolved": "https://registry.npmjs.org/@react-types/statuslight/-/statuslight-3.3.8.tgz",
+            "integrity": "sha512-6ByVj2LJBctiXU5UeEgoKoIhDkOkJu5pb2GYJR8Rd5hxj5keYTdfmY5d4zYlEA4hR2itm+8//3f9NgYdZqlFhw==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/switch": {
-            "version": "3.5.1",
-            "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.1.tgz",
-            "integrity": "sha512-2LFEKMGeufqyYmeN/5dtkDkCPG6x9O4eu6aaBaJmPGon7C/l3yiFEgRue6oCUYc1HixR7Qlp0sPxk0tQeWzrSg==",
+            "version": "3.5.2",
+            "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.2.tgz",
+            "integrity": "sha512-4i35eZ5GtVDgu9KFhlyLyXanspcQp5WEnPyaBKn3pDRDcpzAL7yNP/Rwqc/JDdcJWngV080o7loJCgEfJ6UFaQ==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/table": {
-            "version": "3.9.3",
-            "resolved": "https://registry.npmjs.org/@react-types/table/-/table-3.9.3.tgz",
-            "integrity": "sha512-Hs/pMbxJdga2zBol4H5pV1FVIiRjCuSTXst6idJjkctanTexR4xkyrtBwl+rdLNoGwQ2pGii49vgklc5bFK7zA==",
+            "version": "3.9.4",
+            "resolved": "https://registry.npmjs.org/@react-types/table/-/table-3.9.4.tgz",
+            "integrity": "sha512-31EI0KAHwX7TbgERLBLVuD3nvpZUo0Wie7S7FEARmirIRfzm1fIkdDk5hfIHry2Lp4mq2/aqXLCY+oDR+lC2pw==",
             "dependencies": {
-                "@react-types/grid": "^3.2.4",
-                "@react-types/shared": "^3.22.1"
+                "@react-types/grid": "^3.2.5",
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/tabs": {
-            "version": "3.3.5",
-            "resolved": "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.5.tgz",
-            "integrity": "sha512-6NTSZBOWekCtApdZrhu5tHhE/8q52oVohQN+J5T7shAXd6ZAtu8PABVR/nH4BWucc8FL0OUajRqunqzQMU13gA==",
+            "version": "3.3.6",
+            "resolved": "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.6.tgz",
+            "integrity": "sha512-ubvB7pB4+e5OpIuYR1CYip53iW9rJRIWvioHTYfcX0DnMabEcVP6Ymdqr5bDh/VsBEhiddsNgMduQwJm6bUTew==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/text": {
-            "version": "3.3.7",
-            "resolved": "https://registry.npmjs.org/@react-types/text/-/text-3.3.7.tgz",
-            "integrity": "sha512-URkJHS314ppyq6JjMPZbg/P4j9vey0H/Lc2+YB96qIcebBKy8iNN0IlwRsLFXimlGDp7dgn6tv+51bdJh+CRCQ==",
+            "version": "3.3.8",
+            "resolved": "https://registry.npmjs.org/@react-types/text/-/text-3.3.8.tgz",
+            "integrity": "sha512-EXTXNdZWg8ggeaOV6isEmNxTzyb1/94BSyUQOSDbazl9OJRnMrC3LAmKDn/ck/LLI2rLpYJ+XfM84zI5ohVzKw==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/textfield": {
-            "version": "3.9.1",
-            "resolved": "https://registry.npmjs.org/@react-types/textfield/-/textfield-3.9.1.tgz",
-            "integrity": "sha512-JBHY9M2CkL6xFaGSfWmUJVu3tEK09FaeB1dU3IEh6P41xxbFnPakYHSSAdnwMXBtXPoSHIVsUBickW/pjgfe5g==",
+            "version": "3.9.2",
+            "resolved": "https://registry.npmjs.org/@react-types/textfield/-/textfield-3.9.2.tgz",
+            "integrity": "sha512-8UcabahYhKm3KTu9CQBhz745FioUWO6CWgYusBpxMDJ+HnlhCC2JWyQvqg5tT98sr5AeSek4Jt/XS3ovzrhCDg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/tooltip": {
-            "version": "3.4.7",
-            "resolved": "https://registry.npmjs.org/@react-types/tooltip/-/tooltip-3.4.7.tgz",
-            "integrity": "sha512-rV4HZRQxLRNhe24yATOxnFQtGRUmsR7mqxMupXCmd1vrw8h+rdKlQv1zW2q8nALAKNmnRXZJHxYQ1SFzb98fgg==",
+            "version": "3.4.8",
+            "resolved": "https://registry.npmjs.org/@react-types/tooltip/-/tooltip-3.4.8.tgz",
+            "integrity": "sha512-6XVQ3cMaXVMif+F5PQCaVwxbgAL8HVRqVjt6DkHs8Xbae43hpEIwPrBYlWWMVpuZAcjXZLTGmmyPjYeORZZJ4A==",
             "dependencies": {
-                "@react-types/overlays": "^3.8.5",
-                "@react-types/shared": "^3.22.1"
+                "@react-types/overlays": "^3.8.6",
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/view": {
-            "version": "3.4.7",
-            "resolved": "https://registry.npmjs.org/@react-types/view/-/view-3.4.7.tgz",
-            "integrity": "sha512-AcgLrlaZkCXH+pD4I7pTPB52Rstg1M+V2yKPP29lnqg/rOjsqLjN/gLLy/EBi97cQ+TbDbg0854vcxAKs6qclg==",
+            "version": "3.4.8",
+            "resolved": "https://registry.npmjs.org/@react-types/view/-/view-3.4.8.tgz",
+            "integrity": "sha512-MlaSm/lDuR8Q60RNWAi7Ygg9T7gPOgZJH/zgs6i7Z8kYZHjGEmU2zpHZLoTIMkyLAdbjNvv2Te4Od0MWNKWdYA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/well": {
-            "version": "3.3.7",
-            "resolved": "https://registry.npmjs.org/@react-types/well/-/well-3.3.7.tgz",
-            "integrity": "sha512-BaZ4utlEeweD8+mAYdqwq2bS8aDrXqi4xFPdfqZpHcpbSJjmGBCCS+GU2y/N2Yl1aODu5l0+gcrvxxr63bV+GA==",
+            "version": "3.3.8",
+            "resolved": "https://registry.npmjs.org/@react-types/well/-/well-3.3.8.tgz",
+            "integrity": "sha512-0IDdmwrb9CMwTii/yJm45ctBwdjCuvA8Gp36idhaM3tD+MuMapHjy48frHhZAFhjSeljDeJTdG1aaHMfnmMW9Q==",
             "dependencies": {
-                "@react-types/shared": "^3.22.1"
+                "@react-types/shared": "^3.23.0"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
@@ -6840,9 +6990,9 @@
             "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="
         },
         "node_modules/@sindresorhus/is": {
-            "version": "6.2.0",
-            "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.2.0.tgz",
-            "integrity": "sha512-yM/IGPkVnYGblhDosFBwq0ZGdnVSBkNV4onUtipGMOjZd4kB6GAu3ys91aftSbyMHh6A2GPdt+KDI5NoWP63MQ==",
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.3.0.tgz",
+            "integrity": "sha512-bOSPck7aIJjASXIg1qvXSIjXhVBpIEKdl2Wxg4pVqoTRPL8wWExKBrnGIh6CEnhkFQHfc36k7APhO3uXV4g5xg==",
             "engines": {
                 "node": ">=16"
             },
@@ -6851,17 +7001,17 @@
             }
         },
         "node_modules/@socket.io/component-emitter": {
-            "version": "3.1.1",
-            "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.1.tgz",
-            "integrity": "sha512-dzJtaDAAoXx4GCOJpbB2eG/Qj8VDpdwkLsWGzGm+0L7E8/434RyMbAHmk9ubXWVAb9nXmc44jUf8GKqVDiKezg=="
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+            "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
         },
         "node_modules/@spectrum-icons/ui": {
-            "version": "3.6.5",
-            "resolved": "https://registry.npmjs.org/@spectrum-icons/ui/-/ui-3.6.5.tgz",
-            "integrity": "sha512-L1QISmlzoA4xuBdMpWT2vkR7NDxuROjSwC5BxS8R2SAZR4oKs0dPfODaw2uju6D/xqJIqVrSM6yQDhV51lPKyA==",
+            "version": "3.6.6",
+            "resolved": "https://registry.npmjs.org/@spectrum-icons/ui/-/ui-3.6.6.tgz",
+            "integrity": "sha512-LQnrjGm1xUeeBJFv0Bxa4ZvqU9dtvyA5KMUHGQUXEgNXoBw4f9eE9kj9apZt6KdVuru1R7+iqNOFTUPlQHIybw==",
             "dependencies": {
                 "@adobe/react-spectrum-ui": "1.2.0",
-                "@react-spectrum/icon": "^3.7.11",
+                "@react-spectrum/icon": "^3.7.12",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6870,12 +7020,12 @@
             }
         },
         "node_modules/@spectrum-icons/workflow": {
-            "version": "4.2.10",
-            "resolved": "https://registry.npmjs.org/@spectrum-icons/workflow/-/workflow-4.2.10.tgz",
-            "integrity": "sha512-CaDYaGMe2bYDozzHztrgEy0tljQWn4OBE9hJC2fAM5cMTve4viiWhcmHYYWFv3MZQyavHy1UwCL1jlXg7bK3PA==",
+            "version": "4.2.11",
+            "resolved": "https://registry.npmjs.org/@spectrum-icons/workflow/-/workflow-4.2.11.tgz",
+            "integrity": "sha512-ALKjJH1AGaUMNZOasnXw2A8SucB6w2wzCxp7j+ZUcX0Pg1PsAY+JFK+uR7hwr3iu4T1l0WkZsKxR/FTjhsPWLg==",
             "dependencies": {
                 "@adobe/react-spectrum-workflow": "2.3.4",
-                "@react-spectrum/icon": "^3.7.11",
+                "@react-spectrum/icon": "^3.7.12",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6884,9 +7034,9 @@
             }
         },
         "node_modules/@swc/helpers": {
-            "version": "0.5.9",
-            "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.9.tgz",
-            "integrity": "sha512-XI76sLwMJoLjJTOK5RblBZkouOJG3X3hjxLCzLnyN1ifAiKQc6Hck3uvnU4Z/dV/Dyk36Ffj8FLvDLV2oWvKTw==",
+            "version": "0.5.11",
+            "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.11.tgz",
+            "integrity": "sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==",
             "dependencies": {
                 "tslib": "^2.4.0"
             }
@@ -8576,9 +8726,9 @@
             "dev": true
         },
         "node_modules/@types/chai": {
-            "version": "4.3.14",
-            "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz",
-            "integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==",
+            "version": "4.3.15",
+            "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.15.tgz",
+            "integrity": "sha512-PYVSvyeZqy9++MoSegq88PxoPndWDDLGbJmE/OZnzUk3D4cCRTmA4N6EX3g0GgLVA+vtys7bj4luhkVCglGTkQ==",
             "dev": true
         },
         "node_modules/@types/color": {
@@ -8638,9 +8788,9 @@
             }
         },
         "node_modules/@types/cookie-session": {
-            "version": "2.0.48",
-            "resolved": "https://registry.npmjs.org/@types/cookie-session/-/cookie-session-2.0.48.tgz",
-            "integrity": "sha512-SeMTGlGVvPPcFGyAqT1kYY8FnkcZvmsURkz5DndHophxv/g3Y1nXQC556/HUDJHr6klPX1mEMP2ppQSBDfRPUA==",
+            "version": "2.0.49",
+            "resolved": "https://registry.npmjs.org/@types/cookie-session/-/cookie-session-2.0.49.tgz",
+            "integrity": "sha512-4E/bBjlqLhU5l4iGPR+NkVJH593hpNsT4dC3DJDr+ODm6Qpe13kZQVkezRIb+TYDXaBMemS3yLQ+0leba3jlkQ==",
             "dev": true,
             "dependencies": {
                 "@types/express": "*",
@@ -8911,9 +9061,9 @@
             "integrity": "sha512-zf2GwV/G6TdaLwpLDcGTIkHnXf8JEf/viMux+khqKQKDa8/8BAUtXXZS563GnvJ4Fg0PBLGAaFf2GekEVSZ6GQ=="
         },
         "node_modules/@types/eslint": {
-            "version": "8.56.9",
-            "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.9.tgz",
-            "integrity": "sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==",
+            "version": "8.56.10",
+            "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
+            "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==",
             "dependencies": {
                 "@types/estree": "*",
                 "@types/json-schema": "*"
@@ -9195,9 +9345,9 @@
             "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
         },
         "node_modules/@types/node": {
-            "version": "20.12.7",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
-            "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
+            "version": "20.12.8",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.8.tgz",
+            "integrity": "sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==",
             "dependencies": {
                 "undici-types": "~5.26.4"
             }
@@ -9221,9 +9371,9 @@
             }
         },
         "node_modules/@types/nodemailer": {
-            "version": "6.4.14",
-            "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.14.tgz",
-            "integrity": "sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==",
+            "version": "6.4.15",
+            "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.15.tgz",
+            "integrity": "sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==",
             "dev": true,
             "dependencies": {
                 "@types/node": "*"
@@ -9258,9 +9408,9 @@
             }
         },
         "node_modules/@types/passport-google-oauth20": {
-            "version": "2.0.14",
-            "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.14.tgz",
-            "integrity": "sha512-ZaZpRUAeMl3vy298ulKO1wGLn9SQtj/CyIfZL/Px5xU9pybMiQU3mhXDCBiWSbg0EK9uXT4ZoWC3ktuWY+5fwQ==",
+            "version": "2.0.15",
+            "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.15.tgz",
+            "integrity": "sha512-//QqtSn4rxB9nfqyTjMK4MIJ8JTQ7+HL2DyvOxoj0yWIo2FF8v7MU34hrZwUqzVEWkyVRBPIjx7+uqF2u94L9Q==",
             "dev": true,
             "dependencies": {
                 "@types/express": "*",
@@ -9311,9 +9461,9 @@
             "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
         },
         "node_modules/@types/qs": {
-            "version": "6.9.14",
-            "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz",
-            "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==",
+            "version": "6.9.15",
+            "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
+            "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==",
             "dev": true
         },
         "node_modules/@types/range-parser": {
@@ -9332,9 +9482,9 @@
             }
         },
         "node_modules/@types/react": {
-            "version": "18.2.78",
-            "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.78.tgz",
-            "integrity": "sha512-qOwdPnnitQY4xKlKayt42q5W5UQrSHjgoXNVEtxeqdITJ99k4VXJOP3vt8Rkm9HmgJpH50UNU+rlqfkfWOqp0A==",
+            "version": "18.3.1",
+            "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz",
+            "integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==",
             "dependencies": {
                 "@types/prop-types": "*",
                 "csstype": "^3.0.2"
@@ -9371,9 +9521,9 @@
             }
         },
         "node_modules/@types/react-dom": {
-            "version": "18.2.25",
-            "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.25.tgz",
-            "integrity": "sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==",
+            "version": "18.3.0",
+            "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
+            "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
             "dev": true,
             "dependencies": {
                 "@types/react": "*"
@@ -9856,20 +10006,6 @@
                 "node": ">=10"
             }
         },
-        "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
-            "version": "9.0.4",
-            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
-            "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
-            "dependencies": {
-                "brace-expansion": "^2.0.1"
-            },
-            "engines": {
-                "node": ">=16 || 14 >=14.17"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/isaacs"
-            }
-        },
         "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
             "version": "7.6.0",
             "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
@@ -9969,55 +10105,55 @@
             "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
         },
         "node_modules/@vue/compiler-core": {
-            "version": "3.4.21",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
-            "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
+            "version": "3.4.26",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.26.tgz",
+            "integrity": "sha512-N9Vil6Hvw7NaiyFUFBPXrAyETIGlQ8KcFMkyk6hW1Cl6NvoqvP+Y8p1Eqvx+UdqsnrnI9+HMUEJegzia3mhXmQ==",
             "dependencies": {
-                "@babel/parser": "^7.23.9",
-                "@vue/shared": "3.4.21",
+                "@babel/parser": "^7.24.4",
+                "@vue/shared": "3.4.26",
                 "entities": "^4.5.0",
                 "estree-walker": "^2.0.2",
-                "source-map-js": "^1.0.2"
+                "source-map-js": "^1.2.0"
             }
         },
         "node_modules/@vue/compiler-dom": {
-            "version": "3.4.21",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
-            "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
+            "version": "3.4.26",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.26.tgz",
+            "integrity": "sha512-4CWbR5vR9fMg23YqFOhr6t6WB1Fjt62d6xdFPyj8pxrYub7d+OgZaObMsoxaF9yBUHPMiPFK303v61PwAuGvZA==",
             "dependencies": {
-                "@vue/compiler-core": "3.4.21",
-                "@vue/shared": "3.4.21"
+                "@vue/compiler-core": "3.4.26",
+                "@vue/shared": "3.4.26"
             }
         },
         "node_modules/@vue/compiler-sfc": {
-            "version": "3.4.21",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
-            "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
-            "dependencies": {
-                "@babel/parser": "^7.23.9",
-                "@vue/compiler-core": "3.4.21",
-                "@vue/compiler-dom": "3.4.21",
-                "@vue/compiler-ssr": "3.4.21",
-                "@vue/shared": "3.4.21",
+            "version": "3.4.26",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.26.tgz",
+            "integrity": "sha512-It1dp+FAOCgluYSVYlDn5DtZBxk1NCiJJfu2mlQqa/b+k8GL6NG/3/zRbJnHdhV2VhxFghaDq5L4K+1dakW6cw==",
+            "dependencies": {
+                "@babel/parser": "^7.24.4",
+                "@vue/compiler-core": "3.4.26",
+                "@vue/compiler-dom": "3.4.26",
+                "@vue/compiler-ssr": "3.4.26",
+                "@vue/shared": "3.4.26",
                 "estree-walker": "^2.0.2",
-                "magic-string": "^0.30.7",
-                "postcss": "^8.4.35",
-                "source-map-js": "^1.0.2"
+                "magic-string": "^0.30.10",
+                "postcss": "^8.4.38",
+                "source-map-js": "^1.2.0"
             }
         },
         "node_modules/@vue/compiler-ssr": {
-            "version": "3.4.21",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
-            "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
+            "version": "3.4.26",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.26.tgz",
+            "integrity": "sha512-FNwLfk7LlEPRY/g+nw2VqiDKcnDTVdCfBREekF8X74cPLiWHUX6oldktf/Vx28yh4STNy7t+/yuLoMBBF7YDiQ==",
             "dependencies": {
-                "@vue/compiler-dom": "3.4.21",
-                "@vue/shared": "3.4.21"
+                "@vue/compiler-dom": "3.4.26",
+                "@vue/shared": "3.4.26"
             }
         },
         "node_modules/@vue/shared": {
-            "version": "3.4.21",
-            "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
-            "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
+            "version": "3.4.26",
+            "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.26.tgz",
+            "integrity": "sha512-Fg4zwR0GNnjzodMt3KRy2AWGMKQXByl56+4HjN87soxLNU9P5xcJkstAlIeEF3cU6UYOzmJl1tV0dVPGIljCnQ=="
         },
         "node_modules/@webassemblyjs/ast": {
             "version": "1.12.1",
@@ -10346,14 +10482,14 @@
             }
         },
         "node_modules/ajv-formats/node_modules/ajv": {
-            "version": "8.12.0",
-            "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
-            "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+            "version": "8.13.0",
+            "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz",
+            "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==",
             "dependencies": {
-                "fast-deep-equal": "^3.1.1",
+                "fast-deep-equal": "^3.1.3",
                 "json-schema-traverse": "^1.0.0",
                 "require-from-string": "^2.0.2",
-                "uri-js": "^4.2.2"
+                "uri-js": "^4.4.1"
             },
             "funding": {
                 "type": "github",
@@ -10980,12 +11116,12 @@
             }
         },
         "node_modules/babel-plugin-polyfill-corejs2": {
-            "version": "0.4.10",
-            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz",
-            "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==",
+            "version": "0.4.11",
+            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz",
+            "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==",
             "dependencies": {
                 "@babel/compat-data": "^7.22.6",
-                "@babel/helper-define-polyfill-provider": "^0.6.1",
+                "@babel/helper-define-polyfill-provider": "^0.6.2",
                 "semver": "^6.3.1"
             },
             "peerDependencies": {
@@ -11005,16 +11141,32 @@
             }
         },
         "node_modules/babel-plugin-polyfill-regenerator": {
-            "version": "0.6.1",
-            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz",
-            "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==",
+            "version": "0.6.2",
+            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz",
+            "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==",
             "dependencies": {
-                "@babel/helper-define-polyfill-provider": "^0.6.1"
+                "@babel/helper-define-polyfill-provider": "^0.6.2"
             },
             "peerDependencies": {
                 "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
             }
         },
+        "node_modules/babel-plugin-styled-components": {
+            "version": "2.1.4",
+            "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz",
+            "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==",
+            "peer": true,
+            "dependencies": {
+                "@babel/helper-annotate-as-pure": "^7.22.5",
+                "@babel/helper-module-imports": "^7.22.5",
+                "@babel/plugin-syntax-jsx": "^7.22.5",
+                "lodash": "^4.17.21",
+                "picomatch": "^2.3.1"
+            },
+            "peerDependencies": {
+                "styled-components": ">= 2"
+            }
+        },
         "node_modules/babel-runtime": {
             "version": "6.26.0",
             "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
@@ -11293,9 +11445,9 @@
             }
         },
         "node_modules/browndash-components": {
-            "version": "0.1.36",
-            "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.36.tgz",
-            "integrity": "sha512-++Xxn67r9ETz7pQr9dE09dMgPJ6rltEzwYWifpI98AQct4RmEBfiPp5eLG94+143XllxZy++KBf9YJ8AYpTEkQ==",
+            "version": "0.1.44",
+            "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.44.tgz",
+            "integrity": "sha512-+QYEHDqd3iauijdKiVA0jXGFkOMEKn+qb7Y2iG+VK0xAh8Fho3rAUoSMv6snfg+kMc1oMGhACzJa5XauTtvguQ==",
             "dependencies": {
                 "@emotion/react": "^11.11.1",
                 "@emotion/styled": "^11.11.0",
@@ -11305,8 +11457,39 @@
                 "npm": "^9.8.1",
                 "react-color": "^2.19.3",
                 "react-icons": "^4.3.1",
-                "react-measure": "^2.5.2",
-                "styled-components": "^6.0.3"
+                "react-measure": "^2.5.2"
+            }
+        },
+        "node_modules/browndash-components/node_modules/@emotion/unitless": {
+            "version": "0.7.5",
+            "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+            "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
+            "peer": true
+        },
+        "node_modules/browndash-components/node_modules/@mui/styled-engine-sc": {
+            "version": "5.14.12",
+            "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.14.12.tgz",
+            "integrity": "sha512-FQ5KDd17OkRurE0ljR4Pddekv1uPSoJxcBqXa9tdoOETGULVCefM5Gd9CRGzT+alNPDyHBoUeEYKulIkDN9ytA==",
+            "dependencies": {
+                "@babel/runtime": "^7.23.1",
+                "csstype": "^3.1.2",
+                "prop-types": "^15.8.1"
+            },
+            "engines": {
+                "node": ">=12.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/mui"
+            },
+            "peerDependencies": {
+                "@types/styled-components": "^5.1.14",
+                "styled-components": "^5.3.1"
+            },
+            "peerDependenciesMeta": {
+                "@types/styled-components": {
+                    "optional": true
+                }
             }
         },
         "node_modules/browndash-components/node_modules/npm": {
@@ -14338,6 +14521,36 @@
                 "react": "*"
             }
         },
+        "node_modules/browndash-components/node_modules/styled-components": {
+            "version": "5.3.11",
+            "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz",
+            "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==",
+            "peer": true,
+            "dependencies": {
+                "@babel/helper-module-imports": "^7.0.0",
+                "@babel/traverse": "^7.4.5",
+                "@emotion/is-prop-valid": "^1.1.0",
+                "@emotion/stylis": "^0.8.4",
+                "@emotion/unitless": "^0.7.4",
+                "babel-plugin-styled-components": ">= 1.12.0",
+                "css-to-react-native": "^3.0.0",
+                "hoist-non-react-statics": "^3.0.0",
+                "shallowequal": "^1.1.0",
+                "supports-color": "^5.5.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/styled-components"
+            },
+            "peerDependencies": {
+                "react": ">= 16.8.0",
+                "react-dom": ">= 16.8.0",
+                "react-is": ">= 16.8.0"
+            }
+        },
         "node_modules/browser-assert": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz",
@@ -14381,9 +14594,9 @@
             }
         },
         "node_modules/bson": {
-            "version": "6.6.0",
-            "resolved": "https://registry.npmjs.org/bson/-/bson-6.6.0.tgz",
-            "integrity": "sha512-BVINv2SgcMjL4oYbBuCQTpE3/VKOSxrOA8Cj/wQP7izSzlBGVomdm+TcUd0Pzy0ytLSSDweCKQ6X3f5veM5LQA==",
+            "version": "6.7.0",
+            "resolved": "https://registry.npmjs.org/bson/-/bson-6.7.0.tgz",
+            "integrity": "sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==",
             "engines": {
                 "node": ">=16.20.1"
             }
@@ -14592,9 +14805,9 @@
             }
         },
         "node_modules/caniuse-lite": {
-            "version": "1.0.30001610",
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001610.tgz",
-            "integrity": "sha512-QFutAY4NgaelojVMjY63o6XlZyORPaLfyMnsl3HgnWdJUcX6K0oaJymHjH8PT5Gk7sTm8rvC/c5COUQKXqmOMA==",
+            "version": "1.0.30001615",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001615.tgz",
+            "integrity": "sha512-1IpazM5G3r38meiae0bHRnPhz+CBQ3ZLqbQMtrg+AsTPKAXgW38JNsXkyZ+v8waCsDmPq87lmfun5Q2AGysNEQ==",
             "funding": [
                 {
                     "type": "opencollective",
@@ -15026,9 +15239,9 @@
             }
         },
         "node_modules/clsx": {
-            "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
-            "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+            "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
             "engines": {
                 "node": ">=6"
             }
@@ -15388,9 +15601,9 @@
             }
         },
         "node_modules/core-js": {
-            "version": "3.36.1",
-            "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz",
-            "integrity": "sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==",
+            "version": "3.37.0",
+            "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.0.tgz",
+            "integrity": "sha512-fu5vHevQ8ZG4og+LXug8ulUtVxjOcEYvifJr7L5Bfq9GOztVqsKd9/59hUk2ZSbCrS3BqUr3EpaYGIYzq7g3Ug==",
             "hasInstallScript": true,
             "funding": {
                 "type": "opencollective",
@@ -15398,9 +15611,9 @@
             }
         },
         "node_modules/core-js-compat": {
-            "version": "3.36.1",
-            "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz",
-            "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==",
+            "version": "3.37.0",
+            "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.0.tgz",
+            "integrity": "sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==",
             "dependencies": {
                 "browserslist": "^4.23.0"
             },
@@ -15410,9 +15623,9 @@
             }
         },
         "node_modules/core-js-pure": {
-            "version": "3.36.1",
-            "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.1.tgz",
-            "integrity": "sha512-NXCvHvSVYSrewP0L5OhltzXeWFJLo2AL2TYnj6iLV3Bw8mM62wAQMNgUCRI6EBu6hVVpbCxmOPlxh1Ikw2PfUA==",
+            "version": "3.37.0",
+            "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.37.0.tgz",
+            "integrity": "sha512-d3BrpyFr5eD4KcbRvQ3FTUx/KWmaDesr7+a3+1+P46IUnNoEt+oiLijPINZMEon7w9oGkIINWxrBAU9DEciwFQ==",
             "hasInstallScript": true,
             "funding": {
                 "type": "opencollective",
@@ -16538,6 +16751,20 @@
                 "node": ">=10"
             }
         },
+        "node_modules/depcheck/node_modules/minimatch": {
+            "version": "7.4.6",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz",
+            "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==",
+            "dependencies": {
+                "brace-expansion": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
         "node_modules/depcheck/node_modules/semver": {
             "version": "7.6.0",
             "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
@@ -16818,9 +17045,9 @@
             "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
         },
         "node_modules/electron-to-chromium": {
-            "version": "1.4.736",
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.736.tgz",
-            "integrity": "sha512-Rer6wc3ynLelKNM4lOCg7/zPQj8tPOCB2hzD32PX9wd3hgRRi9MxEbmkFCokzcEhRVMiOVLjnL9ig9cefJ+6+Q=="
+            "version": "1.4.756",
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.756.tgz",
+            "integrity": "sha512-RJKZ9+vEBMeiPAvKNWyZjuYyUqMndcP1f335oHqn3BEQbs2NFtVrnK5+6Xg5wSM9TknNNpWghGDUCKGYF+xWXw=="
         },
         "node_modules/emoji-regex": {
             "version": "9.2.2",
@@ -16947,9 +17174,9 @@
             }
         },
         "node_modules/envinfo": {
-            "version": "7.12.0",
-            "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.12.0.tgz",
-            "integrity": "sha512-Iw9rQJBGpJRd3rwXm9ft/JiGoAZmLxxJZELYDQoPRZ4USVhkKtIcNBPw6U+/K2mBpaqM25JSV6Yl4Az9vO2wJg==",
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz",
+            "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==",
             "bin": {
                 "envinfo": "dist/cli.js"
             },
@@ -17045,14 +17272,14 @@
             }
         },
         "node_modules/es-iterator-helpers": {
-            "version": "1.0.18",
-            "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz",
-            "integrity": "sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==",
+            "version": "1.0.19",
+            "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz",
+            "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==",
             "dev": true,
             "dependencies": {
                 "call-bind": "^1.0.7",
                 "define-properties": "^1.2.1",
-                "es-abstract": "^1.23.0",
+                "es-abstract": "^1.23.3",
                 "es-errors": "^1.3.0",
                 "es-set-tostringtag": "^2.0.3",
                 "function-bind": "^1.1.2",
@@ -17070,9 +17297,9 @@
             }
         },
         "node_modules/es-module-lexer": {
-            "version": "1.5.0",
-            "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz",
-            "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw=="
+            "version": "1.5.2",
+            "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.2.tgz",
+            "integrity": "sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA=="
         },
         "node_modules/es-object-atoms": {
             "version": "1.0.0",
@@ -18453,9 +18680,9 @@
             }
         },
         "node_modules/eslint-plugin-react-hooks": {
-            "version": "4.6.0",
-            "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
-            "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
+            "version": "4.6.2",
+            "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz",
+            "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
             "dev": true,
             "engines": {
                 "node": ">=10"
@@ -18597,42 +18824,6 @@
                 "webpack": "^5.0.0"
             }
         },
-        "node_modules/eslint-webpack-plugin/node_modules/has-flag": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-            "engines": {
-                "node": ">=8"
-            }
-        },
-        "node_modules/eslint-webpack-plugin/node_modules/jest-worker": {
-            "version": "29.7.0",
-            "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
-            "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
-            "dependencies": {
-                "@types/node": "*",
-                "jest-util": "^29.7.0",
-                "merge-stream": "^2.0.0",
-                "supports-color": "^8.0.0"
-            },
-            "engines": {
-                "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
-            }
-        },
-        "node_modules/eslint-webpack-plugin/node_modules/supports-color": {
-            "version": "8.1.1",
-            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
-            "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
-            "dependencies": {
-                "has-flag": "^4.0.0"
-            },
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/supports-color?sponsor=1"
-            }
-        },
         "node_modules/eslint/node_modules/@eslint/js": {
             "version": "8.57.0",
             "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
@@ -19924,9 +20115,9 @@
             "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
         },
         "node_modules/fs-monkey": {
-            "version": "1.0.5",
-            "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz",
-            "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew=="
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz",
+            "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg=="
         },
         "node_modules/fs.realpath": {
             "version": "1.0.0",
@@ -20047,9 +20238,9 @@
             }
         },
         "node_modules/gaxios": {
-            "version": "6.4.0",
-            "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.4.0.tgz",
-            "integrity": "sha512-apAloYrY4dlBGlhauDAYSZveafb5U6+L9titing1wox6BvWM0TSXBp603zTrLpyLMGkrcFgohnUN150dFN/zOA==",
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.5.0.tgz",
+            "integrity": "sha512-R9QGdv8j4/dlNoQbX3hSaK/S0rkMijqjVvW3YM06CoBdbU/VdKd159j4hePpng0KuE6Lh6JJ7UdmVGJZFcAG1w==",
             "dependencies": {
                 "extend": "^3.0.2",
                 "https-proxy-agent": "^7.0.1",
@@ -20324,20 +20515,6 @@
             "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
             "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
         },
-        "node_modules/glob/node_modules/minimatch": {
-            "version": "9.0.4",
-            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
-            "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
-            "dependencies": {
-                "brace-expansion": "^2.0.1"
-            },
-            "engines": {
-                "node": ">=16 || 14 >=14.17"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/isaacs"
-            }
-        },
         "node_modules/global": {
             "version": "4.4.0",
             "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
@@ -20399,12 +20576,13 @@
             }
         },
         "node_modules/globalthis": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
-            "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+            "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
             "dev": true,
             "dependencies": {
-                "define-properties": "^1.1.3"
+                "define-properties": "^1.2.1",
+                "gopd": "^1.0.1"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -20438,9 +20616,9 @@
             "integrity": "sha512-sIVQCiRWOymHbVD1Aw/T9/ijbPYAVGBlgGYd1N9MRKfcyBNSpjr87Vg9nSHm+RCT8ELrvK8IJYJV0QRJuVUkCQ=="
         },
         "node_modules/google-auth-library": {
-            "version": "9.7.0",
-            "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.7.0.tgz",
-            "integrity": "sha512-I/AvzBiUXDzLOy4iIZ2W+Zq33W4lcukQv1nl7C8WUA6SQwyQwUwu3waNmWNAvzds//FG8SZ+DnKnW/2k6mQS8A==",
+            "version": "9.9.0",
+            "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.9.0.tgz",
+            "integrity": "sha512-9l+zO07h1tDJdIHN74SpnWIlNR+OuOemXlWJlLP9pXy6vFtizgpEzMuwJa4lqY9UAdiAv5DVd5ql0Am916I+aA==",
             "dependencies": {
                 "base64-js": "^1.3.0",
                 "ecdsa-sig-formatter": "^1.0.11",
@@ -20478,9 +20656,9 @@
             }
         },
         "node_modules/googleapis-common": {
-            "version": "7.1.0",
-            "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.1.0.tgz",
-            "integrity": "sha512-p3KHiWDBBWJEXk6SYauBEvxw5+UmRy7k2scxGtsNv9eHsTbpopJ3/7If4OrNnzJ9XMLg3IlyQXpVp8YPQsStiw==",
+            "version": "7.2.0",
+            "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz",
+            "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==",
             "dependencies": {
                 "extend": "^3.0.2",
                 "gaxios": "^6.0.3",
@@ -21227,6 +21405,14 @@
                 "npm": ">=5.3.0"
             }
         },
+        "node_modules/hyperdyperid": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz",
+            "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==",
+            "engines": {
+                "node": ">=10.18"
+            }
+        },
         "node_modules/i": {
             "version": "0.3.7",
             "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz",
@@ -22650,16 +22836,17 @@
             }
         },
         "node_modules/jest-worker": {
-            "version": "27.5.1",
-            "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
-            "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+            "version": "29.7.0",
+            "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+            "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
             "dependencies": {
                 "@types/node": "*",
+                "jest-util": "^29.7.0",
                 "merge-stream": "^2.0.0",
                 "supports-color": "^8.0.0"
             },
             "engines": {
-                "node": ">= 10.13.0"
+                "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
             }
         },
         "node_modules/jest-worker/node_modules/has-flag": {
@@ -23503,14 +23690,11 @@
             }
         },
         "node_modules/magic-string": {
-            "version": "0.30.9",
-            "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz",
-            "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==",
+            "version": "0.30.10",
+            "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
+            "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
             "dependencies": {
                 "@jridgewell/sourcemap-codec": "^1.4.15"
-            },
-            "engines": {
-                "node": ">=12"
             }
         },
         "node_modules/magicli": {
@@ -24051,9 +24235,9 @@
             }
         },
         "node_modules/micromark-core-commonmark": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz",
-            "integrity": "sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==",
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz",
+            "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==",
             "funding": [
                 {
                     "type": "GitHub Sponsors",
@@ -24501,9 +24685,9 @@
             }
         },
         "node_modules/micromark-util-subtokenize": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz",
-            "integrity": "sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==",
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz",
+            "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==",
             "funding": [
                 {
                     "type": "GitHub Sponsors",
@@ -24627,14 +24811,14 @@
             "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
         },
         "node_modules/minimatch": {
-            "version": "7.4.6",
-            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz",
-            "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==",
+            "version": "9.0.4",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
+            "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
             "dependencies": {
                 "brace-expansion": "^2.0.1"
             },
             "engines": {
-                "node": ">=10"
+                "node": ">=16 || 14 >=14.17"
             },
             "funding": {
                 "url": "https://github.com/sponsors/isaacs"
@@ -24928,12 +25112,12 @@
             }
         },
         "node_modules/mongodb": {
-            "version": "6.5.0",
-            "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz",
-            "integrity": "sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==",
+            "version": "6.6.0",
+            "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.6.0.tgz",
+            "integrity": "sha512-LoR0hel6y1i7pubPOzbflccdc89RDOapIGqwce4aenOfhhLKOqiQ2F4yQHP+Hqa7TuPhpVqDVjixbvUSx7R0rQ==",
             "dependencies": {
                 "@mongodb-js/saslprep": "^1.1.5",
-                "bson": "^6.4.0",
+                "bson": "^6.7.0",
                 "mongodb-connection-string-url": "^3.0.0"
             },
             "engines": {
@@ -25005,9 +25189,9 @@
             }
         },
         "node_modules/mongoose": {
-            "version": "8.3.1",
-            "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.3.1.tgz",
-            "integrity": "sha512-D78C+s7QI4+pJQhs3XbOxzrHFEti4x+BDhaH94QrdV1/cmMA7fHc50LgLSXjzA/5q89TBK8DAXyf3VwDZbQJlA==",
+            "version": "8.3.3",
+            "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.3.3.tgz",
+            "integrity": "sha512-3kSk0db9DM2tLttCdS6WNRqewPleamFEa4Vz/Qldc0dB4Zow/FiZxb9GExHTJjBZQ9T2xiGleQ3GzRrES3hhsA==",
             "dependencies": {
                 "bson": "^6.5.0",
                 "kareem": "2.6.3",
@@ -25025,6 +25209,51 @@
                 "url": "https://opencollective.com/mongoose"
             }
         },
+        "node_modules/mongoose/node_modules/mongodb": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz",
+            "integrity": "sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==",
+            "dependencies": {
+                "@mongodb-js/saslprep": "^1.1.5",
+                "bson": "^6.4.0",
+                "mongodb-connection-string-url": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=16.20.1"
+            },
+            "peerDependencies": {
+                "@aws-sdk/credential-providers": "^3.188.0",
+                "@mongodb-js/zstd": "^1.1.0",
+                "gcp-metadata": "^5.2.0",
+                "kerberos": "^2.0.1",
+                "mongodb-client-encryption": ">=6.0.0 <7",
+                "snappy": "^7.2.2",
+                "socks": "^2.7.1"
+            },
+            "peerDependenciesMeta": {
+                "@aws-sdk/credential-providers": {
+                    "optional": true
+                },
+                "@mongodb-js/zstd": {
+                    "optional": true
+                },
+                "gcp-metadata": {
+                    "optional": true
+                },
+                "kerberos": {
+                    "optional": true
+                },
+                "mongodb-client-encryption": {
+                    "optional": true
+                },
+                "snappy": {
+                    "optional": true
+                },
+                "socks": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/mongoose/node_modules/ms": {
             "version": "2.1.3",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -25424,9 +25653,9 @@
             }
         },
         "node_modules/npm": {
-            "version": "10.5.2",
-            "resolved": "https://registry.npmjs.org/npm/-/npm-10.5.2.tgz",
-            "integrity": "sha512-cHVG7QEJwJdZyOrK0dKX5uf3R5Fd0E8AcmSES1jLtO52UT1enUKZ96Onw/xwq4CbrTZEnDuu2Vf9kCQh/Sd12w==",
+            "version": "10.7.0",
+            "resolved": "https://registry.npmjs.org/npm/-/npm-10.7.0.tgz",
+            "integrity": "sha512-FXylyYSXNjgXx3l82BT8RSQvCoGIQ3h8YdRFGKNvo3Pv/bKscK4pdWkx/onwTpHDqGw+oeLf4Rxln9WVyxAxlQ==",
             "bundleDependencies": [
                 "@isaacs/string-locale-compare",
                 "@npmcli/arborist",
@@ -25444,8 +25673,6 @@
                 "chalk",
                 "ci-info",
                 "cli-columns",
-                "cli-table3",
-                "columnify",
                 "fastest-levenshtein",
                 "fs-minipass",
                 "glob",
@@ -25481,7 +25708,6 @@
                 "npm-profile",
                 "npm-registry-fetch",
                 "npm-user-validate",
-                "npmlog",
                 "p-map",
                 "pacote",
                 "parse-conflict-json",
@@ -25506,10 +25732,10 @@
                 "@npmcli/config": "^8.0.2",
                 "@npmcli/fs": "^3.1.0",
                 "@npmcli/map-workspaces": "^3.0.6",
-                "@npmcli/package-json": "^5.0.2",
+                "@npmcli/package-json": "^5.1.0",
                 "@npmcli/promise-spawn": "^7.0.1",
-                "@npmcli/redact": "^1.1.0",
-                "@npmcli/run-script": "^7.0.4",
+                "@npmcli/redact": "^2.0.0",
+                "@npmcli/run-script": "^8.1.0",
                 "@sigstore/tuf": "^2.3.2",
                 "abbrev": "^2.0.0",
                 "archy": "~1.0.0",
@@ -25517,8 +25743,6 @@
                 "chalk": "^5.3.0",
                 "ci-info": "^4.0.0",
                 "cli-columns": "^4.0.0",
-                "cli-table3": "^0.6.4",
-                "columnify": "^1.6.0",
                 "fastest-levenshtein": "^1.0.16",
                 "fs-minipass": "^3.0.3",
                 "glob": "^10.3.12",
@@ -25530,16 +25754,16 @@
                 "json-parse-even-better-errors": "^3.0.1",
                 "libnpmaccess": "^8.0.1",
                 "libnpmdiff": "^6.0.3",
-                "libnpmexec": "^7.0.4",
+                "libnpmexec": "^8.0.0",
                 "libnpmfund": "^5.0.1",
                 "libnpmhook": "^10.0.0",
                 "libnpmorg": "^6.0.1",
-                "libnpmpack": "^6.0.3",
+                "libnpmpack": "^7.0.0",
                 "libnpmpublish": "^9.0.2",
                 "libnpmsearch": "^7.0.0",
                 "libnpmteam": "^6.0.0",
-                "libnpmversion": "^5.0.1",
-                "make-fetch-happen": "^13.0.0",
+                "libnpmversion": "^6.0.0",
+                "make-fetch-happen": "^13.0.1",
                 "minimatch": "^9.0.4",
                 "minipass": "^7.0.4",
                 "minipass-pipeline": "^1.2.4",
@@ -25549,16 +25773,15 @@
                 "normalize-package-data": "^6.0.0",
                 "npm-audit-report": "^5.0.0",
                 "npm-install-checks": "^6.3.0",
-                "npm-package-arg": "^11.0.1",
+                "npm-package-arg": "^11.0.2",
                 "npm-pick-manifest": "^9.0.0",
-                "npm-profile": "^9.0.0",
-                "npm-registry-fetch": "^16.2.0",
+                "npm-profile": "^9.0.2",
+                "npm-registry-fetch": "^17.0.0",
                 "npm-user-validate": "^2.0.0",
-                "npmlog": "^7.0.1",
                 "p-map": "^4.0.0",
-                "pacote": "^17.0.6",
+                "pacote": "^18.0.3",
                 "parse-conflict-json": "^3.0.1",
-                "proc-log": "^3.0.0",
+                "proc-log": "^4.2.0",
                 "qrcode-terminal": "^0.12.0",
                 "read": "^3.0.1",
                 "semver": "^7.6.0",
@@ -25593,15 +25816,6 @@
                 "node": ">=8"
             }
         },
-        "node_modules/npm/node_modules/@colors/colors": {
-            "version": "1.5.0",
-            "inBundle": true,
-            "license": "MIT",
-            "optional": true,
-            "engines": {
-                "node": ">=0.1.90"
-            }
-        },
         "node_modules/npm/node_modules/@isaacs/cliui": {
             "version": "8.0.2",
             "inBundle": true,
@@ -25685,21 +25899,21 @@
             }
         },
         "node_modules/npm/node_modules/@npmcli/arborist": {
-            "version": "7.4.2",
+            "version": "7.5.1",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "@isaacs/string-locale-compare": "^1.1.0",
                 "@npmcli/fs": "^3.1.0",
-                "@npmcli/installed-package-contents": "^2.0.2",
+                "@npmcli/installed-package-contents": "^2.1.0",
                 "@npmcli/map-workspaces": "^3.0.2",
-                "@npmcli/metavuln-calculator": "^7.0.0",
+                "@npmcli/metavuln-calculator": "^7.1.0",
                 "@npmcli/name-from-folder": "^2.0.0",
                 "@npmcli/node-gyp": "^3.0.0",
-                "@npmcli/package-json": "^5.0.0",
+                "@npmcli/package-json": "^5.1.0",
                 "@npmcli/query": "^3.1.0",
-                "@npmcli/redact": "^1.1.0",
-                "@npmcli/run-script": "^7.0.2",
+                "@npmcli/redact": "^2.0.0",
+                "@npmcli/run-script": "^8.1.0",
                 "bin-links": "^4.0.1",
                 "cacache": "^18.0.0",
                 "common-ancestor-path": "^1.0.1",
@@ -25709,13 +25923,13 @@
                 "minimatch": "^9.0.4",
                 "nopt": "^7.0.0",
                 "npm-install-checks": "^6.2.0",
-                "npm-package-arg": "^11.0.1",
+                "npm-package-arg": "^11.0.2",
                 "npm-pick-manifest": "^9.0.0",
-                "npm-registry-fetch": "^16.2.0",
-                "npmlog": "^7.0.1",
-                "pacote": "^17.0.4",
+                "npm-registry-fetch": "^17.0.0",
+                "pacote": "^18.0.1",
                 "parse-conflict-json": "^3.0.0",
-                "proc-log": "^3.0.0",
+                "proc-log": "^4.2.0",
+                "proggy": "^2.0.0",
                 "promise-all-reject-late": "^1.0.0",
                 "promise-call-limit": "^3.0.1",
                 "read-package-json-fast": "^3.0.2",
@@ -25732,7 +25946,7 @@
             }
         },
         "node_modules/npm/node_modules/@npmcli/config": {
-            "version": "8.2.2",
+            "version": "8.3.1",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -25740,7 +25954,7 @@
                 "ci-info": "^4.0.0",
                 "ini": "^4.1.2",
                 "nopt": "^7.0.0",
-                "proc-log": "^3.0.0",
+                "proc-log": "^4.2.0",
                 "read-package-json-fast": "^3.0.2",
                 "semver": "^7.3.5",
                 "walk-up-path": "^3.0.1"
@@ -25749,31 +25963,6 @@
                 "node": "^16.14.0 || >=18.0.0"
             }
         },
-        "node_modules/npm/node_modules/@npmcli/disparity-colors": {
-            "version": "3.0.0",
-            "inBundle": true,
-            "license": "ISC",
-            "dependencies": {
-                "ansi-styles": "^4.3.0"
-            },
-            "engines": {
-                "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
-            }
-        },
-        "node_modules/npm/node_modules/@npmcli/disparity-colors/node_modules/ansi-styles": {
-            "version": "4.3.0",
-            "inBundle": true,
-            "license": "MIT",
-            "dependencies": {
-                "color-convert": "^2.0.1"
-            },
-            "engines": {
-                "node": ">=8"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-            }
-        },
         "node_modules/npm/node_modules/@npmcli/fs": {
             "version": "3.1.0",
             "inBundle": true,
@@ -25786,14 +25975,14 @@
             }
         },
         "node_modules/npm/node_modules/@npmcli/git": {
-            "version": "5.0.5",
+            "version": "5.0.6",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "@npmcli/promise-spawn": "^7.0.0",
                 "lru-cache": "^10.0.1",
                 "npm-pick-manifest": "^9.0.0",
-                "proc-log": "^3.0.0",
+                "proc-log": "^4.0.0",
                 "promise-inflight": "^1.0.1",
                 "promise-retry": "^2.0.1",
                 "semver": "^7.3.5",
@@ -25804,7 +25993,7 @@
             }
         },
         "node_modules/npm/node_modules/@npmcli/installed-package-contents": {
-            "version": "2.0.2",
+            "version": "2.1.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -25812,7 +26001,7 @@
                 "npm-normalize-package-bin": "^3.0.0"
             },
             "bin": {
-                "installed-package-contents": "lib/index.js"
+                "installed-package-contents": "bin/index.js"
             },
             "engines": {
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
@@ -25833,13 +26022,14 @@
             }
         },
         "node_modules/npm/node_modules/@npmcli/metavuln-calculator": {
-            "version": "7.0.0",
+            "version": "7.1.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "cacache": "^18.0.0",
                 "json-parse-even-better-errors": "^3.0.0",
-                "pacote": "^17.0.0",
+                "pacote": "^18.0.0",
+                "proc-log": "^4.1.0",
                 "semver": "^7.3.5"
             },
             "engines": {
@@ -25863,7 +26053,7 @@
             }
         },
         "node_modules/npm/node_modules/@npmcli/package-json": {
-            "version": "5.0.2",
+            "version": "5.1.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -25872,7 +26062,7 @@
                 "hosted-git-info": "^7.0.0",
                 "json-parse-even-better-errors": "^3.0.0",
                 "normalize-package-data": "^6.0.0",
-                "proc-log": "^3.0.0",
+                "proc-log": "^4.0.0",
                 "semver": "^7.5.3"
             },
             "engines": {
@@ -25902,7 +26092,7 @@
             }
         },
         "node_modules/npm/node_modules/@npmcli/redact": {
-            "version": "1.1.0",
+            "version": "2.0.0",
             "inBundle": true,
             "license": "ISC",
             "engines": {
@@ -25910,7 +26100,7 @@
             }
         },
         "node_modules/npm/node_modules/@npmcli/run-script": {
-            "version": "7.0.4",
+            "version": "8.1.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -25918,6 +26108,7 @@
                 "@npmcli/package-json": "^5.0.0",
                 "@npmcli/promise-spawn": "^7.0.0",
                 "node-gyp": "^10.0.0",
+                "proc-log": "^4.0.0",
                 "which": "^4.0.0"
             },
             "engines": {
@@ -26079,14 +26270,6 @@
             "inBundle": true,
             "license": "MIT"
         },
-        "node_modules/npm/node_modules/are-we-there-yet": {
-            "version": "4.0.2",
-            "inBundle": true,
-            "license": "ISC",
-            "engines": {
-                "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
-            }
-        },
         "node_modules/npm/node_modules/balanced-match": {
             "version": "1.0.2",
             "inBundle": true,
@@ -26219,28 +26402,6 @@
                 "node": ">= 10"
             }
         },
-        "node_modules/npm/node_modules/cli-table3": {
-            "version": "0.6.4",
-            "inBundle": true,
-            "license": "MIT",
-            "dependencies": {
-                "string-width": "^4.2.0"
-            },
-            "engines": {
-                "node": "10.* || >= 12.*"
-            },
-            "optionalDependencies": {
-                "@colors/colors": "1.5.0"
-            }
-        },
-        "node_modules/npm/node_modules/clone": {
-            "version": "1.0.4",
-            "inBundle": true,
-            "license": "MIT",
-            "engines": {
-                "node": ">=0.8"
-            }
-        },
         "node_modules/npm/node_modules/cmd-shim": {
             "version": "6.0.2",
             "inBundle": true,
@@ -26265,36 +26426,11 @@
             "inBundle": true,
             "license": "MIT"
         },
-        "node_modules/npm/node_modules/color-support": {
-            "version": "1.1.3",
-            "inBundle": true,
-            "license": "ISC",
-            "bin": {
-                "color-support": "bin.js"
-            }
-        },
-        "node_modules/npm/node_modules/columnify": {
-            "version": "1.6.0",
-            "inBundle": true,
-            "license": "MIT",
-            "dependencies": {
-                "strip-ansi": "^6.0.1",
-                "wcwidth": "^1.0.0"
-            },
-            "engines": {
-                "node": ">=8.0.0"
-            }
-        },
         "node_modules/npm/node_modules/common-ancestor-path": {
             "version": "1.0.1",
             "inBundle": true,
             "license": "ISC"
         },
-        "node_modules/npm/node_modules/console-control-strings": {
-            "version": "1.1.0",
-            "inBundle": true,
-            "license": "ISC"
-        },
         "node_modules/npm/node_modules/cross-spawn": {
             "version": "7.0.3",
             "inBundle": true,
@@ -26354,17 +26490,6 @@
             "inBundle": true,
             "license": "MIT"
         },
-        "node_modules/npm/node_modules/defaults": {
-            "version": "1.0.4",
-            "inBundle": true,
-            "license": "MIT",
-            "dependencies": {
-                "clone": "^1.0.2"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
         "node_modules/npm/node_modules/diff": {
             "version": "5.2.0",
             "inBundle": true,
@@ -26452,24 +26577,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/npm/node_modules/gauge": {
-            "version": "5.0.1",
-            "inBundle": true,
-            "license": "ISC",
-            "dependencies": {
-                "aproba": "^1.0.3 || ^2.0.0",
-                "color-support": "^1.1.3",
-                "console-control-strings": "^1.1.0",
-                "has-unicode": "^2.0.1",
-                "signal-exit": "^4.0.1",
-                "string-width": "^4.2.3",
-                "strip-ansi": "^6.0.1",
-                "wide-align": "^1.1.5"
-            },
-            "engines": {
-                "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
-            }
-        },
         "node_modules/npm/node_modules/glob": {
             "version": "10.3.12",
             "inBundle": true,
@@ -26496,11 +26603,6 @@
             "inBundle": true,
             "license": "ISC"
         },
-        "node_modules/npm/node_modules/has-unicode": {
-            "version": "2.0.1",
-            "inBundle": true,
-            "license": "ISC"
-        },
         "node_modules/npm/node_modules/hasown": {
             "version": "2.0.2",
             "inBundle": true,
@@ -26628,11 +26730,6 @@
                 "node": ">= 12"
             }
         },
-        "node_modules/npm/node_modules/ip-address/node_modules/sprintf-js": {
-            "version": "1.1.3",
-            "inBundle": true,
-            "license": "BSD-3-Clause"
-        },
         "node_modules/npm/node_modules/ip-regex": {
             "version": "5.0.0",
             "inBundle": true,
@@ -26741,30 +26838,29 @@
             "license": "MIT"
         },
         "node_modules/npm/node_modules/libnpmaccess": {
-            "version": "8.0.3",
+            "version": "8.0.5",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
-                "npm-package-arg": "^11.0.1",
-                "npm-registry-fetch": "^16.2.0"
+                "npm-package-arg": "^11.0.2",
+                "npm-registry-fetch": "^17.0.0"
             },
             "engines": {
                 "node": "^16.14.0 || >=18.0.0"
             }
         },
         "node_modules/npm/node_modules/libnpmdiff": {
-            "version": "6.0.9",
+            "version": "6.1.1",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "@npmcli/arborist": "^7.2.1",
-                "@npmcli/disparity-colors": "^3.0.0",
-                "@npmcli/installed-package-contents": "^2.0.2",
+                "@npmcli/installed-package-contents": "^2.1.0",
                 "binary-extensions": "^2.3.0",
                 "diff": "^5.1.0",
                 "minimatch": "^9.0.4",
-                "npm-package-arg": "^11.0.1",
-                "pacote": "^17.0.4",
+                "npm-package-arg": "^11.0.2",
+                "pacote": "^18.0.1",
                 "tar": "^6.2.1"
             },
             "engines": {
@@ -26772,17 +26868,16 @@
             }
         },
         "node_modules/npm/node_modules/libnpmexec": {
-            "version": "7.0.10",
+            "version": "8.1.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "@npmcli/arborist": "^7.2.1",
-                "@npmcli/run-script": "^7.0.2",
+                "@npmcli/run-script": "^8.1.0",
                 "ci-info": "^4.0.0",
-                "npm-package-arg": "^11.0.1",
-                "npmlog": "^7.0.1",
-                "pacote": "^17.0.4",
-                "proc-log": "^3.0.0",
+                "npm-package-arg": "^11.0.2",
+                "pacote": "^18.0.1",
+                "proc-log": "^4.2.0",
                 "read": "^3.0.1",
                 "read-package-json-fast": "^3.0.2",
                 "semver": "^7.3.7",
@@ -26793,7 +26888,7 @@
             }
         },
         "node_modules/npm/node_modules/libnpmfund": {
-            "version": "5.0.7",
+            "version": "5.0.9",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -26804,53 +26899,53 @@
             }
         },
         "node_modules/npm/node_modules/libnpmhook": {
-            "version": "10.0.2",
+            "version": "10.0.4",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "aproba": "^2.0.0",
-                "npm-registry-fetch": "^16.2.0"
+                "npm-registry-fetch": "^17.0.0"
             },
             "engines": {
                 "node": "^16.14.0 || >=18.0.0"
             }
         },
         "node_modules/npm/node_modules/libnpmorg": {
-            "version": "6.0.3",
+            "version": "6.0.5",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "aproba": "^2.0.0",
-                "npm-registry-fetch": "^16.2.0"
+                "npm-registry-fetch": "^17.0.0"
             },
             "engines": {
                 "node": "^16.14.0 || >=18.0.0"
             }
         },
         "node_modules/npm/node_modules/libnpmpack": {
-            "version": "6.0.9",
+            "version": "7.0.1",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "@npmcli/arborist": "^7.2.1",
-                "@npmcli/run-script": "^7.0.2",
-                "npm-package-arg": "^11.0.1",
-                "pacote": "^17.0.4"
+                "@npmcli/run-script": "^8.1.0",
+                "npm-package-arg": "^11.0.2",
+                "pacote": "^18.0.1"
             },
             "engines": {
                 "node": "^16.14.0 || >=18.0.0"
             }
         },
         "node_modules/npm/node_modules/libnpmpublish": {
-            "version": "9.0.5",
+            "version": "9.0.7",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "ci-info": "^4.0.0",
                 "normalize-package-data": "^6.0.0",
-                "npm-package-arg": "^11.0.1",
-                "npm-registry-fetch": "^16.2.0",
-                "proc-log": "^3.0.0",
+                "npm-package-arg": "^11.0.2",
+                "npm-registry-fetch": "^17.0.0",
+                "proc-log": "^4.2.0",
                 "semver": "^7.3.7",
                 "sigstore": "^2.2.0",
                 "ssri": "^10.0.5"
@@ -26860,37 +26955,37 @@
             }
         },
         "node_modules/npm/node_modules/libnpmsearch": {
-            "version": "7.0.2",
+            "version": "7.0.4",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
-                "npm-registry-fetch": "^16.2.0"
+                "npm-registry-fetch": "^17.0.0"
             },
             "engines": {
                 "node": "^16.14.0 || >=18.0.0"
             }
         },
         "node_modules/npm/node_modules/libnpmteam": {
-            "version": "6.0.2",
+            "version": "6.0.4",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "aproba": "^2.0.0",
-                "npm-registry-fetch": "^16.2.0"
+                "npm-registry-fetch": "^17.0.0"
             },
             "engines": {
                 "node": "^16.14.0 || >=18.0.0"
             }
         },
         "node_modules/npm/node_modules/libnpmversion": {
-            "version": "5.0.2",
+            "version": "6.0.1",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
-                "@npmcli/git": "^5.0.3",
-                "@npmcli/run-script": "^7.0.2",
+                "@npmcli/git": "^5.0.6",
+                "@npmcli/run-script": "^8.1.0",
                 "json-parse-even-better-errors": "^3.0.0",
-                "proc-log": "^3.0.0",
+                "proc-log": "^4.2.0",
                 "semver": "^7.3.7"
             },
             "engines": {
@@ -26898,7 +26993,7 @@
             }
         },
         "node_modules/npm/node_modules/lru-cache": {
-            "version": "10.2.0",
+            "version": "10.2.2",
             "inBundle": true,
             "license": "ISC",
             "engines": {
@@ -26906,7 +27001,7 @@
             }
         },
         "node_modules/npm/node_modules/make-fetch-happen": {
-            "version": "13.0.0",
+            "version": "13.0.1",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
@@ -26919,6 +27014,7 @@
                 "minipass-flush": "^1.0.5",
                 "minipass-pipeline": "^1.2.4",
                 "negotiator": "^0.6.3",
+                "proc-log": "^4.2.0",
                 "promise-retry": "^2.0.1",
                 "ssri": "^10.0.0"
             },
@@ -27139,6 +27235,14 @@
                 "node": "^16.14.0 || >=18.0.0"
             }
         },
+        "node_modules/npm/node_modules/node-gyp/node_modules/proc-log": {
+            "version": "3.0.0",
+            "inBundle": true,
+            "license": "ISC",
+            "engines": {
+                "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+            }
+        },
         "node_modules/npm/node_modules/nopt": {
             "version": "7.2.0",
             "inBundle": true,
@@ -27206,12 +27310,12 @@
             }
         },
         "node_modules/npm/node_modules/npm-package-arg": {
-            "version": "11.0.1",
+            "version": "11.0.2",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "hosted-git-info": "^7.0.0",
-                "proc-log": "^3.0.0",
+                "proc-log": "^4.0.0",
                 "semver": "^7.3.5",
                 "validate-npm-package-name": "^5.0.0"
             },
@@ -27245,30 +27349,30 @@
             }
         },
         "node_modules/npm/node_modules/npm-profile": {
-            "version": "9.0.0",
+            "version": "9.0.2",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
-                "npm-registry-fetch": "^16.0.0",
-                "proc-log": "^3.0.0"
+                "npm-registry-fetch": "^17.0.0",
+                "proc-log": "^4.0.0"
             },
             "engines": {
                 "node": "^16.14.0 || >=18.0.0"
             }
         },
         "node_modules/npm/node_modules/npm-registry-fetch": {
-            "version": "16.2.0",
+            "version": "17.0.0",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
-                "@npmcli/redact": "^1.1.0",
+                "@npmcli/redact": "^2.0.0",
                 "make-fetch-happen": "^13.0.0",
                 "minipass": "^7.0.2",
                 "minipass-fetch": "^3.0.0",
                 "minipass-json-stream": "^1.0.1",
                 "minizlib": "^2.1.2",
                 "npm-package-arg": "^11.0.0",
-                "proc-log": "^3.0.0"
+                "proc-log": "^4.0.0"
             },
             "engines": {
                 "node": "^16.14.0 || >=18.0.0"
@@ -27282,20 +27386,6 @@
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
             }
         },
-        "node_modules/npm/node_modules/npmlog": {
-            "version": "7.0.1",
-            "inBundle": true,
-            "license": "ISC",
-            "dependencies": {
-                "are-we-there-yet": "^4.0.0",
-                "console-control-strings": "^1.1.0",
-                "gauge": "^5.0.0",
-                "set-blocking": "^2.0.0"
-            },
-            "engines": {
-                "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
-            }
-        },
         "node_modules/npm/node_modules/p-map": {
             "version": "4.0.0",
             "inBundle": true,
@@ -27311,25 +27401,24 @@
             }
         },
         "node_modules/npm/node_modules/pacote": {
-            "version": "17.0.6",
+            "version": "18.0.3",
             "inBundle": true,
             "license": "ISC",
             "dependencies": {
                 "@npmcli/git": "^5.0.0",
                 "@npmcli/installed-package-contents": "^2.0.1",
+                "@npmcli/package-json": "^5.1.0",
                 "@npmcli/promise-spawn": "^7.0.0",
-                "@npmcli/run-script": "^7.0.0",
+                "@npmcli/run-script": "^8.0.0",
                 "cacache": "^18.0.0",
                 "fs-minipass": "^3.0.0",
                 "minipass": "^7.0.2",
                 "npm-package-arg": "^11.0.0",
                 "npm-packlist": "^8.0.0",
                 "npm-pick-manifest": "^9.0.0",
-                "npm-registry-fetch": "^16.0.0",
-                "proc-log": "^3.0.0",
+                "npm-registry-fetch": "^17.0.0",
+                "proc-log": "^4.0.0",
                 "promise-retry": "^2.0.1",
-                "read-package-json": "^7.0.0",
-                "read-package-json-fast": "^3.0.0",
                 "sigstore": "^2.2.0",
                 "ssri": "^10.0.0",
                 "tar": "^6.1.11"
@@ -27390,7 +27479,15 @@
             }
         },
         "node_modules/npm/node_modules/proc-log": {
-            "version": "3.0.0",
+            "version": "4.2.0",
+            "inBundle": true,
+            "license": "ISC",
+            "engines": {
+                "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+            }
+        },
+        "node_modules/npm/node_modules/proggy": {
+            "version": "2.0.0",
             "inBundle": true,
             "license": "ISC",
             "engines": {
@@ -27467,20 +27564,6 @@
                 "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
             }
         },
-        "node_modules/npm/node_modules/read-package-json": {
-            "version": "7.0.0",
-            "inBundle": true,
-            "license": "ISC",
-            "dependencies": {
-                "glob": "^10.2.2",
-                "json-parse-even-better-errors": "^3.0.0",
-                "normalize-package-data": "^6.0.0",
-                "npm-normalize-package-bin": "^3.0.0"
-            },
-            "engines": {
-                "node": "^16.14.0 || >=18.0.0"
-            }
-        },
         "node_modules/npm/node_modules/read-package-json-fast": {
             "version": "3.0.2",
             "inBundle": true,
@@ -27532,11 +27615,6 @@
                 "node": ">=10"
             }
         },
-        "node_modules/npm/node_modules/set-blocking": {
-            "version": "2.0.0",
-            "inBundle": true,
-            "license": "ISC"
-        },
         "node_modules/npm/node_modules/shebang-command": {
             "version": "2.0.0",
             "inBundle": true,
@@ -27655,6 +27733,11 @@
             "inBundle": true,
             "license": "CC0-1.0"
         },
+        "node_modules/npm/node_modules/sprintf-js": {
+            "version": "1.1.3",
+            "inBundle": true,
+            "license": "BSD-3-Clause"
+        },
         "node_modules/npm/node_modules/ssri": {
             "version": "10.0.5",
             "inBundle": true,
@@ -27865,14 +27948,6 @@
             "inBundle": true,
             "license": "ISC"
         },
-        "node_modules/npm/node_modules/wcwidth": {
-            "version": "1.0.1",
-            "inBundle": true,
-            "license": "MIT",
-            "dependencies": {
-                "defaults": "^1.0.3"
-            }
-        },
         "node_modules/npm/node_modules/which": {
             "version": "4.0.0",
             "inBundle": true,
@@ -27895,14 +27970,6 @@
                 "node": ">=16"
             }
         },
-        "node_modules/npm/node_modules/wide-align": {
-            "version": "1.1.5",
-            "inBundle": true,
-            "license": "ISC",
-            "dependencies": {
-                "string-width": "^1.0.2 || 2 || 3 || 4"
-            }
-        },
         "node_modules/npm/node_modules/wrap-ansi": {
             "version": "8.1.0",
             "inBundle": true,
@@ -28036,9 +28103,9 @@
             }
         },
         "node_modules/nwsapi": {
-            "version": "2.2.7",
-            "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
-            "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==",
+            "version": "2.2.9",
+            "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.9.tgz",
+            "integrity": "sha512-2f3F0SEEer8bBu0dsNCFF50N0cTThV1nWFYcEYFZttdW0lDAoybv9cQoK7X7/68Z89S7FoRrVjP1LPX4XRf9vg==",
             "dev": true
         },
         "node_modules/oauth": {
@@ -28322,9 +28389,9 @@
             }
         },
         "node_modules/openai": {
-            "version": "4.33.1",
-            "resolved": "https://registry.npmjs.org/openai/-/openai-4.33.1.tgz",
-            "integrity": "sha512-0DH572aSxGTT1JPOXgJQ9mjiuSPg/7scPot8hLc5I1mfQxPxLXTZWJpWerKaIWOuPkR2nrB0SamGDEehH8RuWA==",
+            "version": "4.40.2",
+            "resolved": "https://registry.npmjs.org/openai/-/openai-4.40.2.tgz",
+            "integrity": "sha512-r9AIaYQNHw8HGJpnny6Rcu/0moGUVqvpv0wTJfP0hKlk8ja5DVUMUCdPWEVfg7lxQMC+wIh+Qjp3onDIhVBemA==",
             "dependencies": {
                 "@types/node": "^18.11.18",
                 "@types/node-fetch": "^2.6.4",
@@ -28368,16 +28435,16 @@
             }
         },
         "node_modules/optionator": {
-            "version": "0.9.3",
-            "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
-            "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+            "version": "0.9.4",
+            "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+            "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
             "dependencies": {
-                "@aashutoshrathi/word-wrap": "^1.2.3",
                 "deep-is": "^0.1.3",
                 "fast-levenshtein": "^2.0.6",
                 "levn": "^0.4.1",
                 "prelude-ls": "^1.2.1",
-                "type-check": "^0.4.0"
+                "type-check": "^0.4.0",
+                "word-wrap": "^1.2.5"
             },
             "engines": {
                 "node": ">= 0.8.0"
@@ -28737,9 +28804,9 @@
             }
         },
         "node_modules/path-scurry/node_modules/lru-cache": {
-            "version": "10.2.0",
-            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
-            "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
+            "version": "10.2.2",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
+            "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
             "engines": {
                 "node": "14 || >=16.14"
             }
@@ -28758,9 +28825,9 @@
             }
         },
         "node_modules/path2d": {
-            "version": "0.1.2",
-            "resolved": "https://registry.npmjs.org/path2d/-/path2d-0.1.2.tgz",
-            "integrity": "sha512-LW++2uxgHNL/FANhgGTPo/yDDQcgsVbKotwIVbpTgTBgRlKUpjOpjp3s3+KjG4OWCQ/r6z+WLDljH1/fC03PWw==",
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/path2d/-/path2d-0.2.0.tgz",
+            "integrity": "sha512-KdPAykQX6kmLSOO6Jpu2KNcCED7CKjmaBNGGNuctOsG0hgYO1OdYQaan6cYXJiG0WmXOwZZPILPBimu5QAIw3A==",
             "optional": true,
             "engines": {
                 "node": ">=6"
@@ -28829,15 +28896,15 @@
             }
         },
         "node_modules/pdfjs-dist": {
-            "version": "4.1.392",
-            "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.1.392.tgz",
-            "integrity": "sha512-fUV14+CG81uDLjgZ2Nmy35GvJsLIekotJb2VhXAoUfMCrWHhQtPJbqryUuevAdSHyEiAdr675ULikoD087+lMg==",
+            "version": "4.2.67",
+            "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.2.67.tgz",
+            "integrity": "sha512-rJmuBDFpD7cqC8WIkQUEClyB4UAH05K4AsyewToMTp2gSy3Rrx8c1ydAVqlJlGv3yZSOrhEERQU/4ScQQFlLHA==",
             "engines": {
                 "node": ">=18"
             },
             "optionalDependencies": {
                 "canvas": "^2.11.2",
-                "path2d": "^0.1.2"
+                "path2d": "^0.2.0"
             }
         },
         "node_modules/pdfjs/node_modules/pako": {
@@ -29376,9 +29443,9 @@
             }
         },
         "node_modules/prosemirror-view": {
-            "version": "1.33.4",
-            "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.4.tgz",
-            "integrity": "sha512-xQqAhH8/HGleVpKDhQsrd+oqdyeKMxFtdCWDxWMmP+n0k27fBpyUqa8pA+RB5cFY8rqDDc1hll69aRZQa7UaAw==",
+            "version": "1.33.6",
+            "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.6.tgz",
+            "integrity": "sha512-zRLUNgLIQfd8IfGprsXxWTjdA8xEAFJe8cDNrOptj6Mop9sj+BMeVbJvceyAYCm5G2dOdT2prctH7K9dfnpIMw==",
             "dependencies": {
                 "prosemirror-model": "^1.20.0",
                 "prosemirror-state": "^1.0.0",
@@ -29753,9 +29820,9 @@
             }
         },
         "node_modules/react": {
-            "version": "18.2.0",
-            "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
-            "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+            "version": "18.3.1",
+            "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+            "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
             "dependencies": {
                 "loose-envify": "^1.1.0"
             },
@@ -29763,6 +29830,88 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/react-aria": {
+            "version": "3.33.0",
+            "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.33.0.tgz",
+            "integrity": "sha512-aKn9SQn5TMlmpUsIjfRMtse2v3okGcSo+gWLGrj9JVjxs4PL4FSU4mclj4Bg2JUXZTGgfLSq6PWUBzQ4gIP2zg==",
+            "dependencies": {
+                "@internationalized/string": "^3.2.2",
+                "@react-aria/breadcrumbs": "^3.5.12",
+                "@react-aria/button": "^3.9.4",
+                "@react-aria/calendar": "^3.5.7",
+                "@react-aria/checkbox": "^3.14.2",
+                "@react-aria/combobox": "^3.9.0",
+                "@react-aria/datepicker": "^3.10.0",
+                "@react-aria/dialog": "^3.5.13",
+                "@react-aria/dnd": "^3.6.0",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/gridlist": "^3.8.0",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/label": "^3.7.7",
+                "@react-aria/link": "^3.7.0",
+                "@react-aria/listbox": "^3.12.0",
+                "@react-aria/menu": "^3.14.0",
+                "@react-aria/meter": "^3.4.12",
+                "@react-aria/numberfield": "^3.11.2",
+                "@react-aria/overlays": "^3.22.0",
+                "@react-aria/progress": "^3.4.12",
+                "@react-aria/radio": "^3.10.3",
+                "@react-aria/searchfield": "^3.7.4",
+                "@react-aria/select": "^3.14.4",
+                "@react-aria/selection": "^3.18.0",
+                "@react-aria/separator": "^3.3.12",
+                "@react-aria/slider": "^3.7.7",
+                "@react-aria/ssr": "^3.9.3",
+                "@react-aria/switch": "^3.6.3",
+                "@react-aria/table": "^3.14.0",
+                "@react-aria/tabs": "^3.9.0",
+                "@react-aria/tag": "^3.4.0",
+                "@react-aria/textfield": "^3.14.4",
+                "@react-aria/tooltip": "^3.7.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/visually-hidden": "^3.8.11",
+                "@react-types/shared": "^3.23.0"
+            },
+            "peerDependencies": {
+                "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
+                "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+            }
+        },
+        "node_modules/react-aria-components": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.2.0.tgz",
+            "integrity": "sha512-Cqr1tewYe/SgNcVOptqYdm0PPAV0Xsvd2uzWX7EqJsPGHvKStl/mh2a3Ebe6dYuoOddLsqqj36DdiLC5Pb5okw==",
+            "dependencies": {
+                "@internationalized/date": "^3.5.3",
+                "@internationalized/string": "^3.2.2",
+                "@react-aria/color": "3.0.0-beta.32",
+                "@react-aria/focus": "^3.17.0",
+                "@react-aria/interactions": "^3.21.2",
+                "@react-aria/menu": "^3.14.0",
+                "@react-aria/toolbar": "3.0.0-beta.4",
+                "@react-aria/tree": "3.0.0-alpha.0",
+                "@react-aria/utils": "^3.24.0",
+                "@react-stately/color": "^3.6.0",
+                "@react-stately/menu": "^3.7.0",
+                "@react-stately/table": "^3.11.7",
+                "@react-stately/utils": "^3.10.0",
+                "@react-types/color": "3.0.0-beta.24",
+                "@react-types/form": "^3.7.3",
+                "@react-types/grid": "^3.2.5",
+                "@react-types/shared": "^3.23.0",
+                "@react-types/table": "^3.9.4",
+                "@swc/helpers": "^0.5.0",
+                "client-only": "^0.0.1",
+                "react-aria": "^3.33.0",
+                "react-stately": "^3.31.0",
+                "use-sync-external-store": "^1.2.0"
+            },
+            "peerDependencies": {
+                "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
+                "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+            }
+        },
         "node_modules/react-autosuggest": {
             "version": "10.1.0",
             "resolved": "https://registry.npmjs.org/react-autosuggest/-/react-autosuggest-10.1.0.tgz",
@@ -29779,9 +29928,9 @@
             }
         },
         "node_modules/react-awesome-reveal": {
-            "version": "4.2.8",
-            "resolved": "https://registry.npmjs.org/react-awesome-reveal/-/react-awesome-reveal-4.2.8.tgz",
-            "integrity": "sha512-G961DVTMwFbkmg7z+M5omz8Y+iicLc9gKm8hmtwJ/lFnYx9fEg7lSY/AyaNZFXYKNfAS5lv/WzL72kNRMCJDZA==",
+            "version": "4.2.10",
+            "resolved": "https://registry.npmjs.org/react-awesome-reveal/-/react-awesome-reveal-4.2.10.tgz",
+            "integrity": "sha512-+QflgSAvgtxZvnAUbIFChQzOUZ+VdErGouv2e4Ab2RI9PFyr05EvOdrAO4FGqca/9Xb3LoOPVPuqCrSmiTuZhA==",
             "funding": [
                 {
                     "type": "github",
@@ -29793,8 +29942,8 @@
                 }
             ],
             "dependencies": {
-                "react-intersection-observer": "^9.6.0",
-                "react-is": "^18.2.0"
+                "react-intersection-observer": "^9.10.1",
+                "react-is": "^18.3.1"
             },
             "peerDependencies": {
                 "@emotion/react": "^11.0.0",
@@ -29845,9 +29994,9 @@
             "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
         },
         "node_modules/react-datepicker": {
-            "version": "6.7.1",
-            "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-6.7.1.tgz",
-            "integrity": "sha512-W6mBZR+3aLTblFs5AFQ5Yp+OltipEtmLnNl0bnsPTuTDanufyz+5ndN7jwhD7EXEQGX4PUnEztWu0CpY+jyNwQ==",
+            "version": "6.9.0",
+            "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-6.9.0.tgz",
+            "integrity": "sha512-QTxuzeem7BUfVFWv+g5WuvzT0c5BPo+XTCNbMTZKSZQLU+cMMwSUHwspaxuIcDlwNcOH0tiJ+bh1fJ2yxOGYWA==",
             "dependencies": {
                 "@floating-ui/react": "^0.26.2",
                 "clsx": "^2.1.0",
@@ -29861,15 +30010,15 @@
             }
         },
         "node_modules/react-dom": {
-            "version": "18.2.0",
-            "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
-            "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+            "version": "18.3.1",
+            "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+            "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
             "dependencies": {
                 "loose-envify": "^1.1.0",
-                "scheduler": "^0.23.0"
+                "scheduler": "^0.23.2"
             },
             "peerDependencies": {
-                "react": "^18.2.0"
+                "react": "^18.3.1"
             }
         },
         "node_modules/react-draggable": {
@@ -29911,20 +30060,20 @@
             }
         },
         "node_modules/react-icons": {
-            "version": "5.1.0",
-            "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.1.0.tgz",
-            "integrity": "sha512-D3zug1270S4hbSlIRJ0CUS97QE1yNNKDjzQe3HqY0aefp2CBn9VgzgES27sRR2gOvFK+0CNx/BW0ggOESp6fqQ==",
+            "version": "5.2.0",
+            "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.0.tgz",
+            "integrity": "sha512-n52Y7Eb4MgQZHsSZOhSXv1zs2668/hBYKfSRIvKh42yExjyhZu0d1IK2CLLZ3BZB1oo13lDfwx2vOh2z9FTV6Q==",
             "peerDependencies": {
                 "react": "*"
             }
         },
         "node_modules/react-intersection-observer": {
-            "version": "9.8.2",
-            "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.8.2.tgz",
-            "integrity": "sha512-901naEiiZmse3p+AmtbQ3NL9xx+gQ8TXLiGDc+8GiE3JKJkNV3vP737aGuWTAXBA+1QqxPrDDE+fIEgYpGDlrQ==",
+            "version": "9.10.2",
+            "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.10.2.tgz",
+            "integrity": "sha512-j2hGADK2hCbAlfaq6L3tVLb4iqngoN7B1fT16MwJ4J16YW/vWLcmAIinLsw0lgpZeMi4UDUWtHC9QDde0/P1yQ==",
             "peerDependencies": {
-                "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
-                "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+                "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+                "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
             },
             "peerDependenciesMeta": {
                 "react-dom": {
@@ -29933,9 +30082,9 @@
             }
         },
         "node_modules/react-is": {
-            "version": "18.2.0",
-            "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
-            "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+            "version": "18.3.1",
+            "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+            "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
         },
         "node_modules/react-jsx-parser": {
             "version": "1.29.0",
@@ -30117,6 +30266,39 @@
                 "node": ">=6.0.0"
             }
         },
+        "node_modules/react-stately": {
+            "version": "3.31.0",
+            "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.31.0.tgz",
+            "integrity": "sha512-G6y7t6qpP3LU4mLM2RlRTgdW5eiZrR2yB0XZbLo8qVplazxyRzlDJRBdE8OBTpw2SO1q5Auub3NOTH3vH0qCHg==",
+            "dependencies": {
+                "@react-stately/calendar": "^3.5.0",
+                "@react-stately/checkbox": "^3.6.4",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/combobox": "^3.8.3",
+                "@react-stately/data": "^3.11.3",
+                "@react-stately/datepicker": "^3.9.3",
+                "@react-stately/dnd": "^3.3.0",
+                "@react-stately/form": "^3.0.2",
+                "@react-stately/list": "^3.10.4",
+                "@react-stately/menu": "^3.7.0",
+                "@react-stately/numberfield": "^3.9.2",
+                "@react-stately/overlays": "^3.6.6",
+                "@react-stately/radio": "^3.10.3",
+                "@react-stately/searchfield": "^3.5.2",
+                "@react-stately/select": "^3.6.3",
+                "@react-stately/selection": "^3.15.0",
+                "@react-stately/slider": "^3.5.3",
+                "@react-stately/table": "^3.11.7",
+                "@react-stately/tabs": "^3.6.5",
+                "@react-stately/toggle": "^3.7.3",
+                "@react-stately/tooltip": "^3.4.8",
+                "@react-stately/tree": "^3.8.0",
+                "@react-types/shared": "^3.23.0"
+            },
+            "peerDependencies": {
+                "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+            }
+        },
         "node_modules/react-themeable": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/react-themeable/-/react-themeable-1.1.0.tgz",
@@ -30285,9 +30467,9 @@
             "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
         },
         "node_modules/recharts": {
-            "version": "2.12.5",
-            "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.5.tgz",
-            "integrity": "sha512-Cy+BkqrFIYTHJCyKHJEPvbHE2kVQEP6PKbOHJ8ztRGTAhvHuUnCwDaKVb13OwRFZ0QNUk1QvGTDdgWSMbuMtKw==",
+            "version": "2.12.6",
+            "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.6.tgz",
+            "integrity": "sha512-D+7j9WI+D0NHauah3fKHuNNcRK8bOypPW7os1DERinogGBGaHI7i6tQKJ0aUF3JXyBZ63dyfKIW2WTOPJDxJ8w==",
             "dependencies": {
                 "clsx": "^2.0.0",
                 "eventemitter3": "^4.0.1",
@@ -31049,9 +31231,9 @@
             "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
         },
         "node_modules/sass": {
-            "version": "1.75.0",
-            "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz",
-            "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==",
+            "version": "1.76.0",
+            "resolved": "https://registry.npmjs.org/sass/-/sass-1.76.0.tgz",
+            "integrity": "sha512-nc3LeqvF2FNW5xGF1zxZifdW3ffIz5aBb7I7tSvOoNu7z1RQ6pFt9MBuiPtjgaI62YWrM/txjWlOCFiGtf2xpw==",
             "dependencies": {
                 "chokidar": ">=3.0.0 <4.0.0",
                 "immutable": "^4.0.0",
@@ -31065,9 +31247,9 @@
             }
         },
         "node_modules/sass-loader": {
-            "version": "14.2.0",
-            "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.2.0.tgz",
-            "integrity": "sha512-jxmjDXD9OYNDb2bp9JvopdE6QjecQY9beTWik/6sEHrsMxyo90Gyc471A4NUz60NLs4WsAh6yVtIvhLwEZcXeg==",
+            "version": "14.2.1",
+            "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.2.1.tgz",
+            "integrity": "sha512-G0VcnMYU18a4N7VoNDegg2OuMjYtxnqzQWARVWCIVSZwJeiL9kg8QMsuIZOplsJgTzZLF6jGxI3AClj8I9nRdQ==",
             "dependencies": {
                 "neo-async": "^2.6.2"
             },
@@ -31121,9 +31303,9 @@
             }
         },
         "node_modules/scheduler": {
-            "version": "0.23.0",
-            "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
-            "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+            "version": "0.23.2",
+            "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+            "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
             "dependencies": {
                 "loose-envify": "^1.1.0"
             }
@@ -31147,14 +31329,14 @@
             }
         },
         "node_modules/schema-utils/node_modules/ajv": {
-            "version": "8.12.0",
-            "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
-            "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+            "version": "8.13.0",
+            "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz",
+            "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==",
             "dependencies": {
-                "fast-deep-equal": "^3.1.1",
+                "fast-deep-equal": "^3.1.3",
                 "json-schema-traverse": "^1.0.0",
                 "require-from-string": "^2.0.2",
-                "uri-js": "^4.2.2"
+                "uri-js": "^4.4.1"
             },
             "funding": {
                 "type": "github",
@@ -31815,6 +31997,24 @@
                 "uuid": "dist/bin/uuid"
             }
         },
+        "node_modules/sonic-forest": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/sonic-forest/-/sonic-forest-1.0.2.tgz",
+            "integrity": "sha512-2rICdwIJi5kVlehMUVtJeHn3ohh5YZV4pDv0P0c1M11cRz/gXNViItpM94HQwfvnXuzybpqK0LZJgTa3lEwtAw==",
+            "dependencies": {
+                "tree-dump": "^1.0.0"
+            },
+            "engines": {
+                "node": ">=10.0"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/streamich"
+            },
+            "peerDependencies": {
+                "tslib": "2"
+            }
+        },
         "node_modules/sort-asc": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz",
@@ -32410,12 +32610,12 @@
             }
         },
         "node_modules/styled-components": {
-            "version": "6.1.8",
-            "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz",
-            "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==",
+            "version": "6.1.9",
+            "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.9.tgz",
+            "integrity": "sha512-aBOqs0uMsYufFXSE4q6cA6Ty1fwZuMk4BJRHfiGSna59F1otnxiDelwhN4fEwmBtIymmF0ZqXHnpSigr2ps9Cg==",
             "dependencies": {
                 "@emotion/is-prop-valid": "1.2.1",
-                "@emotion/unitless": "0.8.0",
+                "@emotion/unitless": "0.8.1",
                 "@types/stylis": "4.2.0",
                 "css-to-react-native": "3.2.0",
                 "csstype": "3.1.2",
@@ -32444,11 +32644,6 @@
                 "@emotion/memoize": "^0.8.1"
             }
         },
-        "node_modules/styled-components/node_modules/@emotion/unitless": {
-            "version": "0.8.0",
-            "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
-            "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw=="
-        },
         "node_modules/styled-components/node_modules/csstype": {
             "version": "3.1.2",
             "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
@@ -32681,9 +32876,9 @@
             "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
         },
         "node_modules/terser": {
-            "version": "5.30.3",
-            "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz",
-            "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==",
+            "version": "5.31.0",
+            "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz",
+            "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==",
             "dependencies": {
                 "@jridgewell/source-map": "^0.3.3",
                 "acorn": "^8.8.2",
@@ -32730,6 +32925,27 @@
                 }
             }
         },
+        "node_modules/terser-webpack-plugin/node_modules/has-flag": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/terser-webpack-plugin/node_modules/jest-worker": {
+            "version": "27.5.1",
+            "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
+            "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+            "dependencies": {
+                "@types/node": "*",
+                "merge-stream": "^2.0.0",
+                "supports-color": "^8.0.0"
+            },
+            "engines": {
+                "node": ">= 10.13.0"
+            }
+        },
         "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
             "version": "3.3.0",
             "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
@@ -32755,6 +32971,20 @@
                 "randombytes": "^2.1.0"
             }
         },
+        "node_modules/terser-webpack-plugin/node_modules/supports-color": {
+            "version": "8.1.1",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+            "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+            "dependencies": {
+                "has-flag": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/supports-color?sponsor=1"
+            }
+        },
         "node_modules/terser/node_modules/commander": {
             "version": "2.20.3",
             "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@@ -32770,6 +33000,17 @@
             "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz",
             "integrity": "sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q=="
         },
+        "node_modules/thingies": {
+            "version": "1.21.0",
+            "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz",
+            "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==",
+            "engines": {
+                "node": ">=10.18"
+            },
+            "peerDependencies": {
+                "tslib": "^2"
+            }
+        },
         "node_modules/through": {
             "version": "2.3.8",
             "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -32798,9 +33039,9 @@
             "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
         },
         "node_modules/tiny-lru": {
-            "version": "11.2.5",
-            "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.2.5.tgz",
-            "integrity": "sha512-JpqM0K33lG6iQGKiigcwuURAKZlq6rHXfrgeL4/I8/REoyJTGU+tEMszvT/oTRVHG2OiylhGDjqPp1jWMlr3bw==",
+            "version": "11.2.6",
+            "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.2.6.tgz",
+            "integrity": "sha512-0PU3c9PjMnltZaFo2sGYv/nnJsMjG0Cxx8X6FXHPPGjFyoo1SJDxvUXW1207rdiSxYizf31roo+GrkIByQeZoA==",
             "engines": {
                 "node": ">=12"
             }
@@ -32935,9 +33176,9 @@
             }
         },
         "node_modules/tough-cookie": {
-            "version": "4.1.3",
-            "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
-            "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
+            "version": "4.1.4",
+            "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+            "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
             "dependencies": {
                 "psl": "^1.1.33",
                 "punycode": "^2.1.1",
@@ -32973,6 +33214,21 @@
             "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
             "integrity": "sha512-up6Yvai4PYKhpNp5PkYtx50m3KbwQrqDwbuZP/ItyL64YEWHAvH6Md83LFLV/GRSk/BoUVwwgUzX6SOQSbsfAg=="
         },
+        "node_modules/tree-dump": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.1.tgz",
+            "integrity": "sha512-WCkcRBVPSlHHq1dc/px9iOfqklvzCbdRwvlNfxGZsrHqf6aZttfPrd7DJTt6oR10dwUfpFFQeVTkPbBIZxX/YA==",
+            "engines": {
+                "node": ">=10.0"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/streamich"
+            },
+            "peerDependencies": {
+                "tslib": "2"
+            }
+        },
         "node_modules/tree-kill": {
             "version": "1.2.2",
             "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
@@ -34031,9 +34287,9 @@
             }
         },
         "node_modules/update-browserslist-db": {
-            "version": "1.0.13",
-            "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
-            "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+            "version": "1.0.15",
+            "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz",
+            "integrity": "sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==",
             "funding": [
                 {
                     "type": "opencollective",
@@ -34049,7 +34305,7 @@
                 }
             ],
             "dependencies": {
-                "escalade": "^3.1.1",
+                "escalade": "^3.1.2",
                 "picocolors": "^1.0.0"
             },
             "bin": {
@@ -34166,9 +34422,9 @@
             }
         },
         "node_modules/use-sync-external-store": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
-            "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+            "version": "1.2.2",
+            "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
+            "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
             }
@@ -34573,10 +34829,13 @@
             }
         },
         "node_modules/webpack-dev-middleware/node_modules/memfs": {
-            "version": "4.8.2",
-            "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.8.2.tgz",
-            "integrity": "sha512-j4WKth315edViMBGkHW6NTF0QBjsTrcRDmYNcGsPq+ozMEyCCCIlX2d2mJ5wuh6iHvJ3FevUrr48v58YRqVdYg==",
+            "version": "4.9.2",
+            "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.9.2.tgz",
+            "integrity": "sha512-f16coDZlTG1jskq3mxarwB+fGRrd0uXWt+o1WIhRfOwbXQZqUDsTVxQBFK9JjRQHblg8eAG2JSbprDXKjc7ijQ==",
             "dependencies": {
+                "@jsonjoy.com/json-pack": "^1.0.3",
+                "@jsonjoy.com/util": "^1.1.2",
+                "sonic-forest": "^1.0.0",
                 "tslib": "^2.0.0"
             },
             "engines": {
@@ -34647,9 +34906,9 @@
             }
         },
         "node_modules/webpack-dev-server/node_modules/ipaddr.js": {
-            "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
-            "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==",
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
+            "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
             "dev": true,
             "engines": {
                 "node": ">= 10"
@@ -34960,7 +35219,6 @@
             "version": "1.2.5",
             "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
             "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
-            "dev": true,
             "engines": {
                 "node": ">=0.10.0"
             }
@@ -35140,9 +35398,9 @@
             }
         },
         "node_modules/ws": {
-            "version": "8.16.0",
-            "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
-            "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+            "version": "8.17.0",
+            "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
+            "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
             "dev": true,
             "engines": {
                 "node": ">=10.0.0"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a36c36261..21a069bd8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -22,7 +22,7 @@ import { ImageField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
 import { Gestures, PointData } from '../../../../pen-gestures/GestureTypes';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, emptyFunction, intersectRect, Utils } from '../../../../Utils';
+import { aggregateBounds, clamp, emptyFunction, intersectRect, Utils } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DocUtils } from '../../../documents/DocUtils';
@@ -377,7 +377,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (didMove) {
             const focusTime = options?.instant ? 0 : options.zoomTime ?? 500;
             (options.zoomScale ?? options.willZoomCentered) && scale && (this.Document[this.scaleFieldKey] = scale);
-            this.setPan(panX, panY, focusTime, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+            this.setPan(panX, panY, focusTime); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
             return focusTime;
         }
         return undefined;
@@ -399,8 +399,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const refDoc = docDragData.droppedDocuments[0];
         const fromScreenXf = NumCast(refDoc.z) ? this.ScreenToLocalBoxXf() : this.screenToFreeformContentsXf;
         const [xpo, ypo] = fromScreenXf.transformPoint(de.x, de.y);
-        const x = xpo - docDragData.offset[0];
-        const y = ypo - docDragData.offset[1];
+        const [x, y] = [xpo - docDragData.offset[0], ypo - docDragData.offset[1]];
         const zsorted = this.childLayoutPairs
             .map(pair => pair.layout)
             .slice()
@@ -411,8 +410,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
         const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
 
-        for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
-            const d = docDragData.droppedDocuments[i];
+        docDragData.droppedDocuments.forEach((d, i) => {
             const layoutDoc = Doc.Layout(d);
             const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], fromScreenXf.Rotate);
             if (this.Document._currentFrame !== undefined) {
@@ -431,7 +429,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             layoutDoc._width = NumCast(layoutDoc._width, 300);
             layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? (nd[1] / nd[0]) * NumCast(layoutDoc._width) : 300);
             !d._keepZWhenDragged && (d.zIndex = zsorted.length + 1 + i); // bringToFront
-        }
+        });
 
         (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this._clusters.addDocuments(docDragData.droppedDocuments);
         return true;
@@ -457,11 +455,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData) {
         if (this.DocumentView?.() && linkDragData.linkDragView.containerViewPath?.().includes(this.DocumentView())) {
             const [x, y] = this.screenToFreeformContentsXf.transformPoint(de.x, de.y);
-            let added = false;
             //  do nothing if link is dropped into any freeform view parent of dragged document
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x, y, title: 'dropped annotation' });
-            added = !!this._props.addDocument?.(source);
-            de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
+            const added = !!this._props.addDocument?.(source);
+            de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' });
             if (de.complete.linkDocument) {
                 de.complete.linkDocument.layout_isSvg = true;
                 this.addDocument(de.complete.linkDocument);
@@ -572,18 +569,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     scrollPan = (e: WheelEvent | { deltaX: number; deltaY: number }): void => {
         SnappingManager.TriggerUserPanned();
-        this.setPan(NumCast(this.Document[this.panXFieldKey]) - e.deltaX, NumCast(this.Document[this.panYFieldKey]) - e.deltaY, 0, true);
+        this.setPan(NumCast(this.Document[this.panXFieldKey]) - e.deltaX, NumCast(this.Document[this.panYFieldKey]) - e.deltaY, 0);
     };
 
     @action
     pan = (e: PointerEvent): void => {
-        const ctrlKey = e.ctrlKey && !e.shiftKey;
-        const shiftKey = e.shiftKey && !e.ctrlKey;
+        const [ctrlKey, shiftKey] = [e.ctrlKey && !e.shiftKey, e.shiftKey && !e.ctrlKey];
         SnappingManager.TriggerUserPanned();
         this.DocumentView?.().clearViewTransition();
         const [dxi, dyi] = this.screenToFreeformContentsXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this.ScreenToLocalBoxXf().Rotate);
-        this.setPan(NumCast(this.Document[this.panXFieldKey]) - (ctrlKey ? 0 : dx), NumCast(this.Document[this.panYFieldKey]) - (shiftKey ? 0 : dy), 0, true);
+        this.setPan(NumCast(this.Document[this.panXFieldKey]) - (ctrlKey ? 0 : dx), NumCast(this.Document[this.panYFieldKey]) - (shiftKey ? 0 : dy), 0);
         this._lastX = e.clientX;
         this._lastY = e.clientY;
     };
@@ -795,18 +791,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             this.setPan(0, 0);
             return;
         }
-        if (deltaScale * invTransform.Scale > NumCast(this.Document[this.scaleFieldKey + '_max'], Number.MAX_VALUE)) {
-            deltaScale = NumCast(this.Document[this.scaleFieldKey + '_max'], 1) / invTransform.Scale;
-        }
-        if (deltaScale * invTransform.Scale < NumCast(this.Document[this.scaleFieldKey + '_min'], this.isAnnotationOverlay ? 1 : 0)) {
-            deltaScale = NumCast(this.Document[this.scaleFieldKey + '_min'], 1) / invTransform.Scale;
-        }
-
+        const minScale = NumCast(this.Document[this.scaleFieldKey + '_min'], this.isAnnotationOverlay ? 1 : 0);
+        const maxScale = NumCast(this.Document[this.scaleFieldKey + '_max'], Number.MAX_VALUE);
+        deltaScale = clamp(deltaScale, minScale / invTransform.Scale, maxScale / invTransform.Scale);
         const localTransform = invTransform.scaleAbout(deltaScale, x, y);
         if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) {
             const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20);
+            const allowScroll = this.Document[this.scaleFieldKey] !== minScale && Math.abs(safeScale) === minScale;
             this.Document[this.scaleFieldKey] = Math.abs(safeScale);
-            this.setPan(-localTransform.TranslateX / safeScale, (this._props.originTopLeft ? undefined : NumCast(this.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
+            this.setPan(-localTransform.TranslateX / safeScale, (this._props.originTopLeft ? undefined : NumCast(this.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale, undefined, allowScroll);
         }
     };
 
@@ -843,35 +836,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     @action
-    setPan(panXIn: number, panYIn: number, panTime: number = 0, clamp: boolean = false) {
-        let panX = panXIn;
-        let panY = panYIn;
+    setPan(panXIn: number, panYIn: number, panTime: number = 0, allowScroll = false) {
+        let [panX, panY] = [panXIn, panYIn];
         // this is the easiest way to do this -> will talk with Bob about using mobx to do this to remove this line of code.
         if (Doc.UserDoc()?.presentationMode === 'watching') ReplayMovements.Instance.pauseFromInteraction();
 
-        if (!this.isAnnotationOverlay && clamp) {
+        if (!this.isAnnotationOverlay && this.childDocs.length) {
             // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
-            const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc && doc.type !== DocumentType.LINK);
-            const measuredDocs = docs.map(doc => ({ x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }));
-            if (measuredDocs.length) {
-                const { originTopLeft } = this._props;
-                // const xrangeMin = Math.min(...measuredDocs.map(doc => doc.x), originTopLeft ? 0 : Number.MAX_VALUE);
-                // const yrangeMin = Math.min(...measuredDocs.map(doc => doc.y), originTopLeft ? 0 : Number.MAX_VALUE);
-                // const xrangeMax = Math.max(...measuredDocs.map(doc => doc.x + doc.width));
-                // const yrangeMax = Math.max(...measuredDocs.map(doc => doc.y + doc.height));
-                const { bounds: { x: xrangeMin, y: yrangeMin, r: xrangeMax, b: yrangeMax } } = this.contentBounds(); // prettier-ignore
-                const nativeScaling = this._props.NativeDimScaling?.() || 1;
-                const scaling = this.zoomScaling() * nativeScaling;
-                const [widScaling, hgtScaling] = [this._props.PanelWidth() / scaling, this._props.PanelHeight() / scaling];
-                const panelWidMax = widScaling * (originTopLeft ? 2 / nativeScaling : 1);
-                const panelHgtMax = hgtScaling * (originTopLeft ? 2 / nativeScaling : 1);
-                const panelWidMin = widScaling * (originTopLeft ? 0 : 1);
-                const panelHgtMin = hgtScaling * (originTopLeft ? 0 : 1);
-                if      (xrangeMin >= panX + panelWidMax / 2) panX = xrangeMax + (originTopLeft ? 0               : panelWidMax / 2);
-                else if (xrangeMax <= panX - panelWidMin / 2) panX = xrangeMin - (originTopLeft ? panelWidMax / 2 : panelWidMin / 2); // prettier-ignore
-                if      (yrangeMin >= panY + panelHgtMax / 2) panY = yrangeMax + (originTopLeft ? 0               : panelHgtMax / 2);
-                else if (yrangeMax <= panY - panelHgtMin / 2) panY = yrangeMin - (originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2); // prettier-ignore
-            }
+            const { bounds: { x: xrangeMin, y: yrangeMin, r: xrangeMax, b: yrangeMax } } = this.contentBounds(); // prettier-ignore
+            const scaling = this.zoomScaling() * (this._props.NativeDimScaling?.() || 1);
+            const [widScaling, hgtScaling] = [this._props.PanelWidth() / scaling, this._props.PanelHeight() / scaling];
+            panX = clamp(panX, xrangeMin - widScaling / 2, xrangeMax + widScaling / 2);
+            panY = clamp(panY, yrangeMin - hgtScaling / 2, yrangeMax + hgtScaling / 2);
         }
         if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc) {
             this.setPanZoomTransition(panTime);
@@ -880,22 +856,22 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const minPanX = NumCast(this.dataDoc._freeform_panX_min, 0);
             const minPanY = NumCast(this.dataDoc._freeform_panY_min, 0);
             const maxPanX = NumCast(this.dataDoc._freeform_panX_max, this.nativeWidth);
-            const newPanX = Math.min(minPanX + scale * maxPanX, Math.max(minPanX, panX));
+            const newPanX = clamp(panX, minPanX, minPanX + scale * maxPanX);
             const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this._props.PanelWidth() - this._props.PanelHeight()) * this.ScreenToLocalBoxXf().Scale) / minScale;
             const nativeHeight = (this._props.PanelHeight() / this._props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
             const maxScrollTop = this.nativeHeight / this.ScreenToLocalBoxXf().Scale - this._props.PanelHeight();
             const maxPanY =
                 minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth
-                scale * NumCast(this.dataDoc._panY_max, nativeHeight) +
+                scale * NumCast(this.dataDoc._freeform_panY_max, nativeHeight) +
                 (!this._props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
-            let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
-            if (fitYscroll > 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) {
-                const maxPanScrollY = minPanY + fitYscroll;
-                const relTop = (panY - minPanY) / (maxPanScrollY - minPanY);
+            const newPanY = clamp(panY, minPanY, maxPanY);
+            // this mess fixes a problem when zooming to the default on an image that is fit width and can scroll.
+            // Without this, the scroll always goes to the top, instead of matching the pan position.
+            if (fitYscroll > 2 && allowScroll && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) {
                 setTimeout(() => {
+                    const relTop = (clamp(panY, minPanY, fitYscroll) - minPanY) / fitYscroll;
                     this.layoutDoc.layout_scrollTop = relTop * maxScrollTop;
                 }, 10);
-                newPanY = minPanY;
             }
             !this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX);
             !this.Document._horizontalScroll && (this.Document[this.panYFieldKey] = this.isAnnotationOverlay ? newPanY : panY);
@@ -909,8 +885,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             this.setPan(
                 NumCast(this.layoutDoc[this.panXFieldKey]) + ((this._props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
                 NumCast(this.layoutDoc[this.panYFieldKey]) + ((this._props.PanelHeight() / 2) * -y) / this.zoomScaling(),
-                nudgeTime,
-                true
+                nudgeTime
             );
             return true;
         }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d4f8b5550..3d10408d6 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -60,7 +60,7 @@ export class ImageEditorData {
     public static set AddDoc(addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, this.imageData.source, addDoc); } // prettier-ignore
 }
 @observer
-export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
+export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ImageBox, fieldKey);
     }
@@ -191,12 +191,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
         this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
         this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw;
-        this.layoutDoc._freeform_panX = nw * NumCast(this.layoutDoc._freeform_panX);
-        this.layoutDoc._freeform_panY = nw * NumCast(this.layoutDoc._freeform_panY);
-        this.dataDoc._freeform_panXMax = this.dataDoc._freeform_panXMax ? nw * NumCast(this.dataDoc._freeform_panXMax) : undefined;
-        this.dataDoc._freeform_panXMin = this.dataDoc._freeform_panXMin ? nw * NumCast(this.dataDoc._freeform_panXMin) : undefined;
-        this.dataDoc._freeform_panYMax = this.dataDoc._freeform_panYMax ? nw * NumCast(this.dataDoc._freeform_panYMax) : undefined;
-        this.dataDoc._freeform_panYMin = this.dataDoc._freeform_panYMin ? nw * NumCast(this.dataDoc._freeform_panYMin) : undefined;
+        this.dataDoc._freeform_panX = nw * NumCast(this.dataDoc._freeform_panX);
+        this.dataDoc._freeform_panY = nw * NumCast(this.dataDoc._freeform_panY);
+        this.dataDoc._freeform_panX_max = this.dataDoc._freeform_panX_max ? nw * NumCast(this.dataDoc._freeform_panX_max) : undefined;
+        this.dataDoc._freeform_panX_min = this.dataDoc._freeform_panX_min ? nw * NumCast(this.dataDoc._freeform_panX_min) : undefined;
+        this.dataDoc._freeform_panY_max = this.dataDoc._freeform_panY_max ? nw * NumCast(this.dataDoc._freeform_panY_max) : undefined;
+        this.dataDoc._freeform_panY_min = this.dataDoc._freeform_panY_min ? nw * NumCast(this.dataDoc._freeform_panY_min) : undefined;
     });
     @undoBatch
     rotate = action(() => {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index b8b9f63a9..1dc194c42 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -656,11 +656,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 );
             else {
                 if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise);
-                PDFBox.pdfpromise.get(href)?.then(
-                    action((pdf: any) => {
-                        PDFBox.pdfcache.set(href, (this._pdf = pdf));
-                    })
-                );
+                PDFBox.pdfpromise.get(href)?.then((pdf: any) => {
+                    PDFBox.pdfcache.set(href, (this._pdf = pdf));
+                });
             }
         }
         return pdfView ?? this.renderTitleBox;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 198652310..b6ef36974 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1049,7 +1049,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             setContentViewBox={this.setInnerContent}
             NativeWidth={returnZero}
             NativeHeight={returnZero}
-            originTopLeft={false}
             isAnnotationOverlayScrollable
             renderDepth={this._props.renderDepth + 1}
             isAnnotationOverlay
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 45b1d727e..2327ee0d8 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -33,7 +33,7 @@ import './PDFViewer.scss';
 
 // pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
 // The workerSrc property shall be specified.
-Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.1.392/build/pdf.worker.mjs';
+Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.2.67/build/pdf.worker.mjs';
 
 interface IViewerProps extends FieldViewProps {
     pdfBox: PDFBox;
-- 
cgit v1.2.3-70-g09d2

