From 923b0d20cd95311122ec5886698b3fb2dfadb882 Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Fri, 12 Jan 2024 12:56:09 -0500
Subject: live schema toggle works on refresh

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx  | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8268a47d8..de4f01014 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -2005,15 +2005,14 @@ ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
             csvRows.push(eachRow);
         }
         const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
-        const options = { x: 0, y: -300, title: 'schemaTable', _width: 300, _height: 100, type: 'text/csv' };
+        const options = { x: 0, y: 0, title: 'schemaTable', _width: 300, _height: 100, type: 'text/csv' };
         const file = new File([blob], 'schemaTable', options);
         const loading = Docs.Create.LoadingDocument(file, options);
         loading.presentation_openInLightbox = true;
         DocUtils.uploadFileToDoc(file, {}, loading);
 
         if (view.ComponentView?.addDocument) {
-            //  loading.dataViz_fromSchema = true;
-            loading.dataViz_asSchema = view.layoutDoc;
+            loading._dataViz_asSchema = view.layoutDoc;
             SchemaCSVPopUp.Instance.setView(view);
             SchemaCSVPopUp.Instance.setTarget(view.layoutDoc);
             SchemaCSVPopUp.Instance.setDataVizDoc(loading);
-- 
cgit v1.2.3-70-g09d2


From db51496360e8eac55b5e83283370c4aeec7a11d8 Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Wed, 17 Jan 2024 15:01:30 -0500
Subject: cleaner

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 116 +++++++++------------
 .../views/nodes/DataVizBox/components/TableBox.tsx |   2 -
 3 files changed, 52 insertions(+), 68 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index de4f01014..881c80a1a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1985,6 +1985,7 @@ ScriptingGlobals.add(function sendToBack(doc: Doc) {
     SelectionManager.Views.forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document, true));
 });
 ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
+    // creating a dataviz doc to represent the schema table
     SelectionManager.Views.forEach(view => {
         if (!view.layoutDoc.schema_columnKeys) {
             view.layoutDoc.schema_columnKeys = new List<string>(['title', 'type', 'author', 'author_date']);
@@ -2011,6 +2012,7 @@ ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
         loading.presentation_openInLightbox = true;
         DocUtils.uploadFileToDoc(file, {}, loading);
 
+        // holds the doc in a popup until it is dragged onto a canvas
         if (view.ComponentView?.addDocument) {
             loading._dataViz_asSchema = view.layoutDoc;
             SchemaCSVPopUp.Instance.setView(view);
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 6d060f535..981ec0aa1 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
-import { ObservableMap, action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { ObservableMap, action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
@@ -17,7 +17,6 @@ import { UndoManager, undoable } from '../../../util/UndoManager';
 import { ViewBoxAnnotatableComponent } from '../../DocComponent';
 import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { SidebarAnnos } from '../../SidebarAnnos';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
 import { DocFocusOptions, DocumentView } from '../DocumentView';
@@ -339,59 +338,63 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
 
     @action
+    // handles when the dataviz doc was made by copying a schema table
     updateSchemaViz = () => {
         const href = CsvCast(this.Document[this.fieldKey]).url.href;
-
-        if (this.layoutDoc.dataViz_schemaLive || !DataVizBox.datasetSchemaOG.has(href)){
-            const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
-            const keys = Cast(getFrom.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
-            if (!keys) return;
-            const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]);
-
-            if (!this.layoutDoc._dataViz_schemaOG){
-                let csvRows = [];
-                csvRows.push(keys.join(','));
-                for (let i = 0; i < children.length; i++) {
-                    let eachRow = [];
-                    for (let j = 0; j < keys.length; j++) {
-                        var cell = children[i][keys[j]];
-                        if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
-                        eachRow.push(cell);
+        const getFromDoc = DocCast(this.layoutDoc.dataViz_asSchema);
+        const children = DocListCast(getFromDoc[Doc.LayoutFieldKey(getFromDoc)]);
+
+        if (children.length != this.schemaBoxLength) {
+            if (this.layoutDoc.dataViz_schemaLive || !DataVizBox.datasetSchemaOG.has(href)){
+                const keys = Cast(getFromDoc.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
+                if (!keys) return;
+
+                if (!this.layoutDoc._dataViz_schemaOG){
+                    // makes a copy of the original table for the "live" toggle
+                    let csvRows = [];
+                    csvRows.push(keys.join(','));
+                    for (let i = 0; i < children.length; i++) {
+                        let eachRow = [];
+                        for (let j = 0; j < keys.length; j++) {
+                            var cell = children[i][keys[j]];
+                            if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+                            eachRow.push(cell);
+                        }
+                        csvRows.push(eachRow);
                     }
-                    csvRows.push(eachRow);
+                    const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
+                    const options = { x: 0, y: 0, title: 'schemaTable for static dataviz', _width: 300, _height: 100, type: 'text/csv' };
+                    const file = new File([blob], 'schemaTable for static dataviz', options);
+                    const loading = Docs.Create.LoadingDocument(file, options);
+                    DocUtils.uploadFileToDoc(file, {}, loading);
+                    this.layoutDoc._dataViz_schemaOG = loading;
                 }
-                const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
-                const options = { x: 0, y: 0, title: 'schemaTable for static dataviz', _width: 300, _height: 100, type: 'text/csv' };
-                const file = new File([blob], 'schemaTable for static dataviz', options);
-                const loading = Docs.Create.LoadingDocument(file, options);
-                DocUtils.uploadFileToDoc(file, {}, loading);
-                this.layoutDoc._dataViz_schemaOG = loading;
-            }
 
-            const ogDoc = this.layoutDoc._dataViz_schemaOG as Doc
-            const ogHref = CsvCast(ogDoc[this.fieldKey])? CsvCast(ogDoc[this.fieldKey]).url.href : undefined;
-            if (ogHref && !DataVizBox.datasetSchemaOG.has(href)){
-                DataVizBox.datasetSchemaOG.set(href, []);
-                fetch('/csvData?uri=' + ogHref)
-                    .then(res => res.json().then(action(res => !res.errno && DataVizBox.datasetSchemaOG.set(href, res))));
-            }
+                const ogDoc = this.layoutDoc._dataViz_schemaOG as Doc
+                const ogHref = CsvCast(ogDoc[this.fieldKey])? CsvCast(ogDoc[this.fieldKey]).url.href : undefined;
+                if (ogHref && !DataVizBox.datasetSchemaOG.has(href)){
+                    DataVizBox.datasetSchemaOG.set(href, []);
+                    fetch('/csvData?uri=' + ogHref)
+                        .then(res => res.json().then(action(res => !res.errno && DataVizBox.datasetSchemaOG.set(href, res))));
+                }
 
-            var current: { [key: string]: string }[] = [];
-            for (let i = 0; i < children.length; i++) {
-                var row: { [key: string]: string } = {};
-                if (children[i]) {
-                    for (let j = 0; j < keys.length; j++) {
-                        var cell = children[i][keys[j]];
-                        if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
-                        row[keys[j]] = StrCast(cell);
+                var current: { [key: string]: string }[] = [];
+                for (let i = 0; i < children.length; i++) {
+                    var row: { [key: string]: string } = {};
+                    if (children[i]) {
+                        for (let j = 0; j < keys.length; j++) {
+                            var cell = children[i][keys[j]];
+                            if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+                            row[keys[j]] = StrCast(cell);
+                        }
                     }
+                    current.push(row);
                 }
-                current.push(row);
+                this.setDataset(href, current);
+            }
+            else {
+                this.setDataset(href, DataVizBox.datasetSchemaOG.get(href));
             }
-            this.setDataset(href, current);
-        }
-        else {
-            this.setDataset(href, DataVizBox.datasetSchemaOG.get(href));
         }
     };
 
@@ -425,26 +428,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     <Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == -DataVizView.PIECHART} />
                 </div>
 
-                {/* <CollectionFreeFormView
-                    ref={this._ffref}
-                    {...this._props}
-                    setContentView={emptyFunction}
-                    renderDepth={this._props.renderDepth - 1}
-                    fieldKey={this.annotationKey}
-                    styleProvider={this._props.styleProvider}
-                    isAnnotationOverlay={true}
-                    annotationLayerHostsContent={false}
-                    PanelWidth={this._props.PanelWidth}
-                    PanelHeight={this._props.PanelHeight}
-                    select={emptyFunction}
-                    isAnyChildContentActive={returnFalse}
-                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                    removeDocument={this.removeDocument}
-                    moveDocument={this.moveDocument}
-                    addDocument={this.addDocument}>
-                    {this.renderVizView}
-                </CollectionFreeFormView> */}
-
                 {(this.layoutDoc && this.layoutDoc.dataViz_asSchema)?(
                     <div className={'liveSchema-checkBox'} style={{ width: this._props.width }}>
                     <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
@@ -453,6 +436,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 ) : null}
 
                 {this.renderVizView}
+
                 <div className="dataviz-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }} onPointerDown={this.onPointerDown}>
                     <SidebarAnnos
                         ref={this._sidebarRef}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index ed44d9269..c5fda18ae 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -83,14 +83,12 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
         return this._props.docView?.()?.screenToViewTransform().Scale || 1;
     }
     @computed get rowHeight() {
-        console.log('scale = ' + this.viewScale + '  table = ' + this._tableHeight + ' ids = ' + this._tableDataIds.length);
         return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
     }
     @computed get startID() {
         return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight) - 1, 0) : 0;
     }
     @computed get endID() {
-        console.log('start = ' + this.startID + '  container = ' + this._tableContainerHeight + ' scale = ' + this.viewScale + ' row = ' + this.rowHeight);
         return Math.ceil(this.startID + (this._tableContainerHeight * this.viewScale) / (this.rowHeight || 1));
     }
     @action handleScroll = () => {
-- 
cgit v1.2.3-70-g09d2


From 5b5730a7df073659cbb6c326f748f7fcbe6625e8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 18 Jan 2024 15:05:53 -0500
Subject: lots of changes to try to simplify API for viewPaths and related

---
 src/Utils.ts                                       |   3 +-
 src/client/documents/Documents.ts                  |   6 +-
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/util/DocumentManager.ts                 |  15 +-
 .../util/Import & Export/DirectoryImportBox.tsx    |   2 +-
 src/client/views/DocComponent.tsx                  |  56 +++-
 src/client/views/DocumentButtonBar.tsx             |   2 +-
 src/client/views/DocumentDecorations.tsx           |   6 +-
 src/client/views/GestureOverlay.tsx                |   2 +-
 src/client/views/InkControlPtHandles.tsx           |  12 +-
 src/client/views/InkStrokeProperties.ts            |   3 +-
 src/client/views/InkTangentHandles.tsx             |   4 +-
 src/client/views/InkingStroke.tsx                  |   9 +-
 src/client/views/LightboxView.tsx                  |   2 +-
 src/client/views/MainView.tsx                      |  47 +---
 src/client/views/MarqueeAnnotator.tsx              |   2 +-
 src/client/views/OverlayView.tsx                   |   2 +-
 src/client/views/PreviewCursor.tsx                 |  25 +-
 src/client/views/PropertiesButtons.tsx             |  46 ++-
 src/client/views/PropertiesDocContextSelector.tsx  |   2 +-
 src/client/views/PropertiesView.tsx                |   2 +-
 src/client/views/StyleProvider.tsx                 |  33 ++-
 src/client/views/TemplateMenu.tsx                  |   2 -
 .../views/collections/CollectionCarouselView.tsx   |   4 +-
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 src/client/views/collections/CollectionMenu.tsx    |   2 -
 .../views/collections/CollectionNoteTakingView.tsx |  10 +-
 .../views/collections/CollectionPileView.tsx       |   2 +-
 .../collections/CollectionStackedTimeline.tsx      |   4 +-
 .../views/collections/CollectionStackingView.tsx   |  12 +-
 src/client/views/collections/CollectionSubView.tsx |   4 +-
 .../views/collections/CollectionTreeView.tsx       |   4 +-
 src/client/views/collections/CollectionView.tsx    |  10 +-
 src/client/views/collections/TabDocView.tsx        |   9 +-
 src/client/views/collections/TreeView.tsx          |  12 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  51 ++--
 .../collectionLinear/CollectionLinearView.tsx      |   6 +-
 .../CollectionMulticolumnView.tsx                  |   6 +-
 .../CollectionMultirowView.tsx                     |   7 +-
 .../collectionSchema/CollectionSchemaView.tsx      |   7 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |   8 +-
 .../collectionSchema/SchemaTableCell.tsx           |   1 -
 src/client/views/global/globalScripts.ts           |   4 +-
 src/client/views/linking/LinkMenuItem.tsx          |   4 +-
 src/client/views/linking/LinkPopup.tsx             |   2 -
 src/client/views/newlightbox/NewLightboxView.tsx   |   2 +-
 src/client/views/nodes/AudioBox.tsx                |   4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   8 +-
 src/client/views/nodes/ComparisonBox.tsx           |   5 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  41 +--
 src/client/views/nodes/DocumentContentsView.tsx    |  11 +-
 src/client/views/nodes/DocumentIcon.tsx            |   2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   4 +-
 src/client/views/nodes/DocumentView.tsx            | 308 +++++++++++----------
 src/client/views/nodes/EquationBox.tsx             |   2 +-
 src/client/views/nodes/FieldView.tsx               |   9 +-
 src/client/views/nodes/ImageBox.tsx                |   8 +-
 src/client/views/nodes/KeyValuePair.tsx            |  65 ++---
 src/client/views/nodes/LinkAnchorBox.tsx           |  14 +-
 src/client/views/nodes/LinkBox.tsx                 |   9 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  12 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   4 +-
 src/client/views/nodes/MapBox/MapPushpinBox.tsx    |   4 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |   4 +-
 src/client/views/nodes/PDFBox.tsx                  |   8 +-
 src/client/views/nodes/VideoBox.tsx                |  18 +-
 src/client/views/nodes/WebBox.tsx                  |  36 +--
 .../views/nodes/formattedText/DashDocView.tsx      |   4 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  16 +-
 .../formattedText/FormattedTextBoxComment.tsx      |   3 +-
 src/client/views/nodes/trails/PresBox.tsx          |  12 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |  10 +-
 src/client/views/pdf/PDFViewer.tsx                 |  12 +-
 src/client/views/topbar/TopBar.tsx                 |   2 -
 src/mobile/MobileInterface.tsx                     |   2 +-
 src/server/DashStats.ts                            |   4 +-
 77 files changed, 566 insertions(+), 537 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index b5c218e01..852083834 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -7,6 +7,7 @@ import { Socket } from '../node_modules/socket.io/dist/index';
 import { DocumentType } from './client/documents/DocumentTypes';
 import { Colors } from './client/views/global/globalEnums';
 import { Message } from './server/Message';
+import { DocumentView } from './client/views/nodes/DocumentView';
 
 export namespace Utils {
     export let CLICK_TIME = 300;
@@ -583,7 +584,7 @@ export function returnEmptyDoclist() {
     return [] as any[];
 }
 
-export let emptyPath = [];
+export let emptyPath: DocumentView[] = [];
 
 export function emptyFunction() {
     return undefined;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 92a9ddfe8..40cffcf45 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -244,7 +244,6 @@ export class DocumentOptions {
     _layout_reflowVertical?: BOOLt = new BoolInfo('native height can be changed independent of width by dragging decoration resizers');
     _layout_reflowHorizontal?: BOOLt = new BoolInfo('whether a doc with a native size can be horizonally resized, causing some form of reflow');
     layout_boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow
-    layout_maxAutoHeight?: NUMt = new NumInfo('maximum height for newly created (eg, from pasting) text documents', false);
     _layout_autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents');
     _layout_curPage?: NUMt = new NumInfo('current page of a PDF or other? paginated document', false);
     _layout_currentTimecode?: NUMt = new NumInfo('the current timecode of a time-based document (e.g., current time of a video)  value is in seconds', false);
@@ -290,7 +289,7 @@ export class DocumentOptions {
     dontPlayLinkOnSelect?: BOOLt = new BoolInfo('whether an audio/video should start playing when a link is followed to it.');
     openFactoryLocation?: string; // an OpenWhere value to place the factory created document
     openFactoryAsDelegate?: boolean; //
-    updateContentsScript?: ScriptField; // reactive script invoked when viewing a document that can update contents of a collection (or do anything)
+    onViewMounted?: ScriptField; // reactive script invoked Doc is viewed (used by showBackLinks view to update collection of links to Doc)
     toolTip?: string; // tooltip to display on hover
     toolType?: string; // type of pen tool
     expertMode?: BOOLt = new BoolInfo('something available only in expert (not novice) mode');
@@ -1895,12 +1894,11 @@ export namespace DocUtils {
         }
     }
 
-    export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) {
+    export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, backgroundColor?: string) {
         const tbox = Docs.Create.TextDocument('', {
             _xMargin: noMargins ? 0 : undefined,
             _yMargin: noMargins ? 0 : undefined,
             annotationOn,
-            layout_maxAutoHeight: maxHeight,
             backgroundColor,
             _width: width || 200,
             _height: 35,
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 4391e87d6..2dfd91314 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -111,7 +111,7 @@ export class CurrentUserUtils {
         const tempClicks = DocCast(doc[field]);
         const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, isSystem: true};
         const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
-            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCastAsync(documentView?.props.docViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([self])))"},
+            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCastAsync(documentView?.containerViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([self])))"},
             { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
@@ -285,7 +285,7 @@ export class CurrentUserUtils {
                                                                                                 treeView_HasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true, 
                                                                                                 dropAction:'move', treeView_Type: TreeViewType.outline, 
                                                                                                 backgroundColor: "white", _xMargin: 0, _yMargin: 0, _createDocOnCR: true 
-                                                                                            }, funcs: {title: 'this.text?.Text'}},
+                                                                                            }, funcs: {title: 'pptSlide'}},//'this.text?.Text'}},
             ];
 
         emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs));
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7fef5cc79..07357a764 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -153,8 +153,8 @@ export class DocumentManager {
         return passes.reduce(
             (toReturn, pass) =>
                 toReturn ??
-                docViewArray.filter(view => view.Document === target).find(view => !pass || view._props.docViewPath().lastElement() === preferredCollection) ??
-                docViewArray.filter(view => Doc.AreProtosEqual(view.Document, target)).find(view => !pass || view._props.docViewPath().lastElement() === preferredCollection),
+                docViewArray.filter(view => view.Document === target).find(view => !pass || view.containerViewPath?.().lastElement() === preferredCollection) ??
+                docViewArray.filter(view => Doc.AreProtosEqual(view.Document, target)).find(view => !pass || view.containerViewPath?.().lastElement() === preferredCollection),
             undefined as Opt<DocumentView>
         );
     }
@@ -224,7 +224,7 @@ export class DocumentManager {
     }
 
     public static removeOverlayViews() {
-        DocumentManager._overlayViews?.forEach(action(view => (view.textHtmlOverlay = undefined)));
+        DocumentManager._overlayViews?.forEach(view => view.setTextHtmlOverlay(undefined, undefined));
         DocumentManager._overlayViews?.clear();
     }
     static _overlayViews = new ObservableSet<DocumentView>();
@@ -274,7 +274,7 @@ export class DocumentManager {
             // even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox)
             const target = DocCast(targetDoc.annotationOn, targetDoc);
             const contextView = this.getDocumentView(DocCast(target.embedContainer));
-            if (contextView?.docView?._componentView?.addDocTab?.(target, OpenWhere.lightbox)) {
+            if (contextView?.ComponentView?.addDocTab?.(target, OpenWhere.lightbox)) {
                 await new Promise<void>(waitres => setTimeout(() => waitres()));
             }
         }
@@ -327,15 +327,12 @@ export class DocumentManager {
 
             if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && targetDoc.text_html) {
                 // if the docView is a text anchor, the contextView is the PDF/Web/Text doc
-                contextView.htmlOverlayEffect = options.effect;
-                contextView.textHtmlOverlayTime = options.zoomTime;
-                contextView.textHtmlOverlay = StrCast(targetDoc.text_html);
+                contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect);
                 DocumentManager._overlayViews.add(contextView);
             }
             Doc.AddUnHighlightWatcher(() => {
                 docView.Document[Animation] = undefined;
                 DocumentManager.removeOverlayViews();
-                contextView && (contextView.htmlOverlayEffect = undefined);
             });
         }
     }
@@ -344,7 +341,7 @@ export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCe
     const func = () => {
         const cv = DocumentManager.Instance.getDocumentView(containingDoc);
         const dv = DocumentManager.Instance.getDocumentView(doc, cv);
-        if (dv && (!containingDoc || dv._props.docViewPath().lastElement()?.Document === containingDoc)) {
+        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));
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index b6dbea33a..398ba3c04 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -156,7 +156,7 @@
 //             x: NumCast(doc.x),
 //             y: NumCast(doc.y) + offset,
 //         };
-//         const parent = this.props.DocumentView?.()._props.docViewPath().lastElement();
+//         const parent = this.props.DocumentView?.().containerViewPath().lastElement();
 //         if (parent?.Document.type === DocumentType.COL) {
 //             let importContainer: Doc;
 //             if (docs.length < 50) {
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index dfc298840..fb8ef7c36 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,6 +1,6 @@
 import { action, computed, makeObservable, observable } from 'mobx';
 import * as React from 'react';
-import { returnFalse } from '../../Utils';
+import { emptyPath, returnFalse } from '../../Utils';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
@@ -9,17 +9,20 @@ import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
+import { Transform } from '../util/Transform';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { DocumentView } from './nodes/DocumentView';
-import { Transform } from '../util/Transform';
 
-///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
+/**
+ * DocComponent returns a generic React base class used by Doc views (not the 'Box' views that render the contents of Doc views)
+ *    (e.g.,CollectionFreeFormDocumentView, DocumentViewInternal)
+ *
+ * */
 export interface DocComponentProps {
     Document: Doc;
     LayoutTemplate?: () => Opt<Doc>;
     LayoutTemplateString?: string;
-    ScreenToLocalTransform: () => Transform;
 }
 export function DocComponent<P extends DocComponentProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
@@ -28,8 +31,6 @@ export function DocComponent<P extends DocComponentProps>() {
             makeObservable(this);
         }
 
-        ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
-
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         get Document() {
             return this._props.Document;
@@ -46,21 +47,40 @@ export function DocComponent<P extends DocComponentProps>() {
     return Component;
 }
 
-/// FieldViewBoxProps  -  a generic base class for field views that are not annotatable (e.g. InkingStroke, ColorBox)
-interface ViewBoxBaseProps {
+/**
+ *  ViewBoxBaseComponent  -  base class for non-annotatable views that render the interior contents of a DocumentView
+ *                           (e.g. InkingStroke, ColorBox)
+ */
+export interface ViewBoxBaseProps {
     Document: Doc;
     TemplateDataDocument?: Doc;
     DocumentView?: () => DocumentView;
+    containerViewPath?: () => DocumentView[];
     fieldKey: string;
     isSelected: () => boolean;
     isContentActive: () => boolean | undefined;
     ScreenToLocalTransform: () => Transform;
     renderDepth: number;
 }
+function returnEmptyDocViewList() {
+    return [] as DocumentView[];
+}
 export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
         ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
 
+        get DocumentView() {
+            return this._props.DocumentView;
+        }
+        get docViewPath() {
+            return this.DocumentView?.().docViewPath ?? emptyPath;
+        }
+        get docViewPathFunc() {
+            return this.DocumentView?.().docViewPathFunc ?? returnEmptyDocViewList;
+        }
+        get containerViewPath() {
+            return this._props.containerViewPath;
+        }
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         get Document() {
             return this._props.Document;
@@ -81,10 +101,15 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
     return Component;
 }
 
-///  DocAnnotatbleComponent -return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image)
+/**
+ * DocAnnotatableComponent - base class for annotatable views that render the interior contents of a DocumentView
+ *                       (e.g., PdfBox, ImageBox)
+ * These views should be interactive (respond to pointerEvents) when their conatainer DocumentView is selected
+ */
 export interface ViewBoxAnnotatableProps {
     Document: Doc;
     TemplateDataDocument?: Doc;
+    DocumentView?: () => DocumentView;
     fieldKey: string;
     filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
     isContentActive: () => boolean | undefined;
@@ -106,6 +131,19 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
 
         @observable _annotationKeySuffix = () => 'annotations';
         @observable _isAnyChildContentActive = false;
+
+        get DocumentView() {
+            return this._props.DocumentView;
+        }
+        get docViewPath() {
+            return this.DocumentView?.().docViewPath ?? emptyPath;
+        }
+        get docViewPathFunc() {
+            return this.DocumentView?.().docViewPathFunc ?? returnEmptyDocViewList;
+        }
+        get containerViewPath() {
+            return this.DocumentView?.().containerViewPath ?? returnEmptyDocViewList;
+        }
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         @computed get Document() {
             return this._props.Document;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index bb28d9ec9..8a4b42ae0 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -72,7 +72,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 <div
                     className="documentButtonBar-icon documentButtonBar-follow"
                     style={{ backgroundColor: followLink ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: followLink ? Colors.BLACK : Colors.WHITE }}
-                    onClick={undoBatch(e => this._props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}>
+                    onClick={undoBatch(e => this._props.views().map(view => view?.toggleFollowLink(undefined, false)))}>
                     <div className="documentButtonBar-followTypes">
                         {followBtn(
                             true,
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 788e78ed2..c96253405 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -303,7 +303,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         SelectionManager.DeselectAll();
     };
 
-    onSelectContainerDocClick = () => SelectionManager.Views?.[0]?._props.docViewPath?.().lastElement()?.select(false);
+    onSelectContainerDocClick = () => SelectionManager.Views?.[0]?.containerViewPath?.().lastElement()?.select(false);
     /**
      * sets up events when user clicks on the border radius editor
      */
@@ -677,7 +677,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             seldocview._props.hideDeleteButton ||
             seldocview.Document.hideDeleteButton ||
             SelectionManager.Views.some(docView => {
-                const collectionAcl = docView._props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView._props.docViewPath().lastElement().dataDoc) : AclEdit;
+                const collectionAcl = docView.containerViewPath?.()?.lastElement() ? GetEffectiveAcl(docView.containerViewPath?.().lastElement().dataDoc) : AclEdit;
                 return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.Document) !== AclAdmin;
             });
         const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
@@ -805,7 +805,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                     <div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} />
                                 </>
                             )}
-                            {seldocview._props.renderDepth <= 1 || !seldocview._props.docViewPath().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectContainerDocClick, 'tap to select containing document')}
+                            {seldocview._props.renderDepth <= 1 || !seldocview.containerViewPath?.().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectContainerDocClick, 'tap to select containing document')}
                             {useRounding && (
                                 <div
                                     className="documentDecorations-borderRadius"
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index b1b04237c..3c7b5eeca 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -476,7 +476,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                 isContentActive={returnFalse}
                 renderDepth={0}
                 styleProvider={returnEmptyString}
-                docViewPath={returnEmptyDoclist}
+                containerViewPath={returnEmptyDoclist}
                 focus={emptyFunction}
                 whenChildContentsActiveChanged={emptyFunction}
                 bringToFront={emptyFunction}
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index a5c2d99d2..7dd57e04d 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -27,7 +27,7 @@ export interface InkControlProps {
 export class InkControlPtHandles extends React.Component<InkControlProps> {
     @observable private _overControl = -1;
     get docView() {
-        return this.props.inkView.props.docViewPath().lastElement();
+        return this.props.inkView.DocumentView?.();
     }
 
     componentDidMount() {
@@ -58,11 +58,11 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
                     if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt');
                     const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
                     const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
-                    InkStrokeProperties.Instance.moveControlPtHandle(this.docView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk);
+                    this.docView && InkStrokeProperties.Instance.moveControlPtHandle(this.docView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk);
                     return false;
                 }),
                 action(() => {
-                    if (this.props.inkView.controlUndo) {
+                    if (this.props.inkView.controlUndo && this.docView) {
                         InkStrokeProperties.Instance.snapControl(this.docView, controlIndex);
                     }
                     this.props.inkView.controlUndo?.end();
@@ -78,11 +78,11 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
                         } else {
                             if (brokenIndices?.includes(equivIndex)) {
                                 if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
-                                InkStrokeProperties.Instance.snapHandleTangent(this.docView, equivIndex, handleIndexA, handleIndexB);
+                                this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, equivIndex, handleIndexA, handleIndexB);
                             }
                             if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
                                 if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth');
-                                InkStrokeProperties.Instance.snapHandleTangent(this.docView, controlIndex, handleIndexA, handleIndexB);
+                                this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, controlIndex, handleIndexA, handleIndexB);
                             }
                         }
                         this.props.inkView.controlUndo?.end();
@@ -113,7 +113,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
     @action
     onDelete = (e: KeyboardEvent) => {
         if (['-', 'Backspace', 'Delete'].includes(e.key)) {
-            InkStrokeProperties.Instance.deletePoints(this.docView, e.shiftKey);
+            this.docView && InkStrokeProperties.Instance.deletePoints(this.docView, e.shiftKey);
             e.stopPropagation();
         }
     };
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index f31ea2994..52ea89cde 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -241,6 +241,7 @@ export class InkStrokeProperties {
      */
     @undoBatch
     moveControlPtHandle = (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number, origInk?: InkData) =>
+        inkView &&
         this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
             const order = controlIndex % 4;
             const closed = InkingStroke.IsClosed(ink);
@@ -369,7 +370,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 containingDocView = containingCollection?.props.DocumentView?.();
+        const containingDocView = containingCollection?.DocumentView?.();
         containingCollection?.childDocs
             .filter(doc => doc.type === DocumentType.INK)
             .forEach(doc => {
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index a00c26a07..c20399698 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -23,7 +23,7 @@ export interface InkHandlesProps {
 @observer
 export class InkTangentHandles extends React.Component<InkHandlesProps> {
     get docView() {
-        return this.props.inkView.props.docViewPath().lastElement();
+        return this.props.inkView.DocumentView?.();
     }
     /**
      * Handles the movement of a selected handle point when the user clicks and drags.
@@ -42,7 +42,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
                 if (e.altKey) this.onBreakTangent(controlIndex);
                 const inkMoveEnd = this.props.inkView.ptFromScreen({ X: delta[0], Y: delta[1] });
                 const inkMoveStart = this.props.inkView.ptFromScreen({ X: 0, Y: 0 });
-                InkStrokeProperties.Instance.moveTangentHandle(this.docView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex);
+                this.docView && InkStrokeProperties.Instance.moveTangentHandle(this.docView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex);
                 return false;
             }),
             action(() => {
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index c652e8966..11b05352d 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -34,7 +34,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
 import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
 import { ContextMenu } from './ContextMenu';
-import { ViewBoxBaseComponent } from './DocComponent';
+import { ViewBoxBaseComponent, ViewBoxBaseProps } from './DocComponent';
 import { Colors } from './global/globalEnums';
 import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
 import './InkStroke.scss';
@@ -48,7 +48,7 @@ import { StyleProp } from './StyleProvider';
 import { Transform } from '../util/Transform';
 const { default: { INK_MASK_SIZE } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 @observer
-export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
+export class InkingStroke extends ViewBoxBaseComponent<ViewBoxBaseProps & FieldViewProps>() {
     static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(InkingStroke, fieldStr);
@@ -122,7 +122,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     onPointerDown = (e: React.PointerEvent) => {
         this._handledClick = false;
-        const inkView = this._props.docViewPath().lastElement();
+        const inkView = this.DocumentView?.();
+        if (!inkView) return;
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const screenPts = inkData
             .map(point =>
@@ -162,7 +163,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                     InkStrokeProperties.Instance._currentPoint = -1;
                     this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
                     if (isEditing) {
-                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(this._props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
+                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(inkView, this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
                     }
                 }
             }),
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index ab3be3d7a..aec9e32fc 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -257,7 +257,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                             styleProvider={DefaultStyleProvider}
                             ScreenToLocalTransform={this.lightboxScreenToLocal}
                             renderDepth={0}
-                            docViewPath={returnEmptyDoclist}
+                            containerViewPath={returnEmptyDoclist}
                             childFilters={returnEmptyFilter}
                             childFiltersByRanges={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 35ffab337..cd6aeea41 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -606,7 +606,7 @@ export class MainView extends ObservableReactComponent<{}> {
                     Document={this.headerBarDoc}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
                     pinToPres={emptyFunction}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     styleProvider={DefaultStyleProvider}
                     addDocument={this.addHeaderDoc}
                     removeDocument={this.removeHeaderDoc}
@@ -641,7 +641,7 @@ export class MainView extends ObservableReactComponent<{}> {
                     addDocument={undefined}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
                     pinToPres={emptyFunction}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     styleProvider={this._hideUI ? DefaultStyleProvider : undefined}
                     isContentActive={returnTrue}
                     removeDocument={undefined}
@@ -735,7 +735,7 @@ export class MainView extends ObservableReactComponent<{}> {
                         addDocument={undefined}
                         addDocTab={DocumentViewInternal.addDocTabFunc}
                         pinToPres={TabDocView.PinDoc}
-                        docViewPath={returnEmptyDoclist}
+                        containerViewPath={returnEmptyDoclist}
                         styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider}
                         removeDocument={returnFalse}
                         ScreenToLocalTransform={this.mainContainerXf}
@@ -769,7 +769,7 @@ export class MainView extends ObservableReactComponent<{}> {
                     PanelWidth={this.leftMenuWidth}
                     PanelHeight={this.leftMenuHeight}
                     renderDepth={0}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     focus={emptyFunction}
                     styleProvider={DefaultStyleProvider}
                     isContentActive={returnTrue}
@@ -900,14 +900,13 @@ export class MainView extends ObservableReactComponent<{}> {
                     Document={Doc.MyDockedBtns}
                     fieldKey="data"
                     dropAction="embed"
-                    setHeight={returnFalse}
                     styleProvider={DefaultStyleProvider}
                     bringToFront={emptyFunction}
                     select={emptyFunction}
                     isAnyChildContentActive={returnFalse}
                     isContentActive={emptyFunction}
                     isSelected={returnFalse}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     moveDocument={this.moveButtonDoc}
                     addDocument={this.addButtonDoc}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
@@ -969,41 +968,6 @@ export class MainView extends ObservableReactComponent<{}> {
         );
     }
 
-    @observable mapBoxHackBool = false;
-    @computed get mapBoxHack() {
-        return this.mapBoxHackBool ? null : (
-            <MapBox
-                ref={action((r: any) => r && (this.mapBoxHackBool = true))}
-                fieldKey="data"
-                select={returnFalse}
-                isSelected={returnFalse}
-                Document={this.headerBarDoc}
-                addDocTab={returnFalse}
-                pinToPres={emptyFunction}
-                docViewPath={returnEmptyDoclist}
-                styleProvider={DefaultStyleProvider}
-                addDocument={returnFalse}
-                removeDocument={returnFalse}
-                fitContentsToBox={returnTrue}
-                isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
-                isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
-                ScreenToLocalTransform={Transform.Identity}
-                childHideResizeHandles={true}
-                childDragAction="move"
-                dontRegisterView={true}
-                PanelWidth={this.headerBarDocWidth}
-                PanelHeight={this.headerBarDocHeight}
-                renderDepth={0}
-                focus={emptyFunction}
-                whenChildContentsActiveChanged={emptyFunction}
-                bringToFront={emptyFunction}
-                childFilters={returnEmptyFilter}
-                childFiltersByRanges={returnEmptyFilter}
-                searchFilterDocs={returnEmptyDoclist}
-            />
-        );
-    }
-
     render() {
         return (
             <div
@@ -1070,7 +1034,6 @@ export class MainView extends ObservableReactComponent<{}> {
                 <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
                 <CollectionFreeFormLinksView />
                 <OverlayView />
-                {/* {this.mapBoxHack} */}
                 <GPTPopup key="gptpopup" />
                 <SchemaCSVPopUp key="schemacsvpopup" />
                 <GenerativeFill imageEditorOpen={ImageBox.imageEditorOpen} imageEditorSource={ImageBox.imageEditorSource} imageRootDoc={ImageBox.imageRootDoc} addDoc={ImageBox.addDoc} />
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 9d828364d..cfdc648b4 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -198,7 +198,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
 
             const targetCreator = (annotationOn: Doc | undefined) => {
-                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
                 FormattedTextBox.SetSelectOnLoad(target);
                 return target;
             };
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 915c3c18f..89527c415 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -234,7 +234,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
                         whenChildContentsActiveChanged={emptyFunction}
                         focus={emptyFunction}
                         styleProvider={DefaultStyleProvider}
-                        docViewPath={returnEmptyDoclist}
+                        containerViewPath={returnEmptyDoclist}
                         addDocTab={DocumentViewInternal.addDocTabFunc}
                         pinToPres={emptyFunction}
                         childFilters={returnEmptyFilter}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index a69a5f5e8..456b753b4 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -60,18 +60,17 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
                 } else if (re.test(plain)) {
                     const url = plain;
                     if (!url.startsWith(window.location.href)) {
-                        undoBatch(
-                            () =>
-                                this._addDocument?.(
-                                    Docs.Create.WebDocument(url, {
-                                        title: url,
-                                        _width: 500,
-                                        _height: 300,
-                                        data_useCors: true,
-                                        x: newPoint[0],
-                                        y: newPoint[1],
-                                    })
-                                )
+                        undoBatch(() =>
+                            this._addDocument?.(
+                                Docs.Create.WebDocument(url, {
+                                    title: url,
+                                    _width: 500,
+                                    _height: 300,
+                                    data_useCors: true,
+                                    x: newPoint[0],
+                                    y: newPoint[1],
+                                })
+                            )
                         )();
                     } else alert('cannot paste dash into itself');
                 } else if (plain.startsWith('__DashDocId(') || plain.startsWith('__DashCloneId(')) {
@@ -86,7 +85,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
                 } else {
                     FormattedTextBox.PasteOnLoad = e;
                     if (e.clipboardData.getData('dash/pdfAnchor')) e.preventDefault();
-                    UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), 'paste');
+                    UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined)), 'paste');
                 }
             }
             //pasting in images
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 5905f8dbc..bba6285c2 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -345,9 +345,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @undoBatch
     handlePerspectiveChange = (e: any) => {
         this.selectedDoc && (this.selectedDoc._type_collection = e.target.value);
-        SelectionManager.Views.filter(dv => dv.docView)
-            .map(dv => dv.docView!)
-            .forEach(docView => (docView.layoutDoc._type_collection = e.target.value));
+        SelectionManager.Views.forEach(docView => (docView.layoutDoc._type_collection = e.target.value));
     };
     @computed get onClickVal() {
         const linkButton = IsFollowLinkScript(this.selectedDoc.onClick);
@@ -408,28 +406,26 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @undoBatch
     @action
     handleOptionChange = (onClick: string) => {
-        SelectionManager.Views.filter(dv => dv.docView)
-            .map(dv => dv.docView!)
-            .forEach(docView => {
-                const linkButton = IsFollowLinkScript(docView.Document.onClick);
-                docView.noOnClick();
-                switch (onClick) {
-                    case 'enterPortal':
-                        docView.makeIntoPortal();
-                        break;
-                    case 'toggleDetail':
-                        docView.setToggleDetail();
-                        break;
-                    case 'linkInPlace':
-                        docView.toggleFollowLink(false, false);
-                        docView.Document.followLinkLocation = linkButton ? OpenWhere.lightbox : undefined;
-                        break;
-                    case 'linkOnRight':
-                        docView.toggleFollowLink(false, false);
-                        docView.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined;
-                        break;
-                }
-            });
+        SelectionManager.Views.forEach(docView => {
+            const linkButton = IsFollowLinkScript(docView.Document.onClick);
+            docView.noOnClick();
+            switch (onClick) {
+                case 'enterPortal':
+                    docView.makeIntoPortal();
+                    break;
+                case 'toggleDetail':
+                    docView.setToggleDetail();
+                    break;
+                case 'linkInPlace':
+                    docView.toggleFollowLink(false, false);
+                    docView.Document.followLinkLocation = linkButton ? OpenWhere.lightbox : undefined;
+                    break;
+                case 'linkOnRight':
+                    docView.toggleFollowLink(false, false);
+                    docView.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined;
+                    break;
+            }
+        });
     };
 
     @undoBatch
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index a710e7816..54f141a36 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -27,7 +27,7 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope
     @computed get _docs() {
         if (!this._props.DocView) return [];
         const target = this._props.DocView._props.Document;
-        const targetContext = this._props.DocView._props.docViewPath().lastElement()?.Document;
+        const targetContext = this._props.DocView.containerViewPath?.().lastElement()?.Document;
         const embeddings = DocListCast(target.proto_embeddings);
         const containerProtos = embeddings.filter(embedding => embedding.embedContainer && embedding.embedContainer instanceof Doc).reduce((set, embedding) => set.add(Cast(embedding.embedContainer, Doc, null)), new Set<Doc>());
         const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.proto_embeddings));
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 2fcb5d12a..13057ffbf 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -299,7 +299,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                         renderDepth={1}
                         fitContentsToBox={returnTrue}
                         styleProvider={DefaultStyleProvider}
-                        docViewPath={returnEmptyDoclist}
+                        containerViewPath={returnEmptyDoclist}
                         dontCenter={'y'}
                         isDocumentActive={returnFalse}
                         isContentActive={emptyFunction}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index d077543f1..572971391 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -20,7 +20,7 @@ import { SnappingManager } from '../util/SnappingManager';
 import { undoBatch, UndoManager } from '../util/UndoManager';
 import { TreeSort } from './collections/TreeSort';
 import { Colors } from './global/globalEnums';
-import { DocumentViewInternalProps, DocumentViewProps } from './nodes/DocumentView';
+import { DocumentViewProps } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PropertiesView } from './PropertiesView';
@@ -80,12 +80,14 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
 
 // 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<DocumentViewInternalProps | FieldViewProps>, property: string): any {
+export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any {
     const remoteDocHeader = 'author;author_date;noMargin';
     const docProps = testDocProps(props) ? props : undefined;
     const fieldProps = testFieldProps(props) ? props : undefined;
     const isCaption = property.includes(':caption');
     const isAnchor = property.includes(':anchor');
+    const isNonTransparent = property.includes(':nonTransparent');
+    const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent');
     const isContent = property.includes(':content');
     const isAnnotated = property.includes(':annotated');
     const isInk = () => doc?._layout_isSvg && !docProps?.LayoutTemplateString;
@@ -95,7 +97,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
     const fieldKey = fieldProps?.fieldKey ? fieldProps.fieldKey + '_' : isCaption ? 'caption_' : '';
     const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
     const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight);
-    const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
+    const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1));
     const color = () => props?.styleProvider?.(doc, props, StyleProp.Color);
     const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
     const layout_showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
@@ -140,7 +142,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
         case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)));
         case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight)));
         case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent'));
-        case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel || props?.hideCaptions ? undefined : StrCast(doc?._layout_showCaption);
+        case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
         case StyleProp.TitleHeight:
             return (props?.DocumentView?.().screenToViewTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30)
         case StyleProp.ShowTitle:
@@ -167,8 +169,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
             if (doc?.type === DocumentType.FONTICON) return SettingsManager.userColor;
             const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
             if (docColor) return docColor;
-            const docView = props?.DocumentView?.();
-            const backColor = backgroundCol() || docView?._props.styleProvider?.(docView._props.treeViewDoc, docView.docView?._props, StyleProp.BackgroundColor);
+            const parView = props?.DocumentView?.();
+            const backColor = backgroundCol();
             return backColor ? lightOrDark(backColor) : undefined;
         case StyleProp.BorderRounding:
             return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
@@ -198,18 +200,18 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
                 : 0;
         case StyleProp.BackgroundColor: {
             if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item
-            let docColor: Opt<string> = StrCast(doc?.[fieldKey + '_backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
+            let docColor: Opt<string> = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
             // prettier-ignore
             switch (doc?.type) {
                 case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
                 case DocumentType.PRES:        docColor = docColor || 'transparent'; break;
                 case DocumentType.FONTICON:    docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY; break;
-                case DocumentType.RTF:         docColor = docColor ||  Colors.LIGHT_GRAY; break;
+                case DocumentType.RTF:         docColor = docColor || Colors.LIGHT_GRAY; break;
                 case DocumentType.LINK:        docColor = (isAnchor ? docColor : undefined); break;
                 case DocumentType.INK:         docColor = doc?.stroke_isInkMask ? 'rgba(0,0,0,0.7)' : undefined; break;
                 case DocumentType.EQUATION:    docColor = docColor || 'transparent'; break;
                 case DocumentType.LABEL:       docColor = docColor || Colors.LIGHT_GRAY; break;
-                case DocumentType.BUTTON:      docColor = docColor ||  Colors.LIGHT_GRAY; break;
+                case DocumentType.BUTTON:      docColor = docColor || Colors.LIGHT_GRAY; break;
                 case DocumentType.IMG:
                 case DocumentType.WEB:
                 case DocumentType.PDF:
@@ -230,6 +232,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
                 //if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
                 default: docColor = docColor || (Colors.WHITE);
             }
+            if (isNonTransparent && isNonTransparentLevel < 9 && (!docColor || docColor === 'transparent') && doc?.embedContainer && props?.styleProvider) {
+                return props.styleProvider(DocCast(doc.embedContainer), props, StyleProp.BackgroundColor+":nonTransparent"+(isNonTransparentLevel+1));
+            }
             return (docColor && !doc) ? DashColor(docColor).fade(0.5).toString() : docColor;
         }
         case StyleProp.BoxShadow: {
@@ -252,7 +257,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
                 default:
                     return doc.z
                         ? `#9c9396  ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
-                        : props?.docViewPath().lastElement()?.Document._freeform_useClusters
+                        : props?.DocumentView?.().containerViewPath?.().lastElement()?.Document._freeform_useClusters
                         ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : NumCast(doc.group, -1) !== -1
                         ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
@@ -277,7 +282,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations:
             const lock = () => {
-                if (props?.docViewPath().lastElement()?.Document?._type_collection === CollectionViewType.Freeform) {
+                if (props?.DocumentView?.().containerViewPath?.().lastElement()?.Document?._type_collection === CollectionViewType.Freeform) {
                     return doc?.pointerEvents !== 'none' ? null : (
                         <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
                             <FontAwesomeIcon icon='lock' size="lg" />
@@ -321,7 +326,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
                                 "this view inherits filters from one of its parents"}
                             color={SettingsManager.userColor}
                             background={showFilterIcon}
-                            items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[]), ...(props?.DocumentView?[props?.DocumentView?.()]:[])]
+                            items={[ ...(dashView ? [dashView]: []), ...(props?.DocumentView?.().docViewPath??[])]
                                     .filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length)
                                     .map(dv => ({
                                 text: StrCast(dv?.Document.title),
@@ -339,7 +344,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInter
                 const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' };
                 return (
                     <Tooltip title={<div>{StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}</div>}>
-                        <div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.docView?.playAnnotation()}>
+                        <div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.playAnnotation()}>
                             <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon={'file-audio'} size="sm" />
                         </div>
                     </Tooltip>
@@ -374,7 +379,7 @@ export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp,
 /**
  * add hide button decorations for the "Dashboards" flyout TreeView
  */
-export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewInternalProps>, property: string) {
+export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) {
     if (doc && property.split(':')[0] === StyleProp.Decorations) {
         return doc._type_collection === CollectionViewType.Docking || Doc.IsSystem(doc)
             ? null
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index b42f3c1e9..e5154efcb 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -114,8 +114,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 <CollectionTreeView
                     Document={Doc.MyTemplates}
                     styleProvider={DefaultStyleProvider}
-                    setHeight={returnFalse}
-                    docViewPath={returnEmptyDoclist}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index a125f1356..208fc45b5 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -7,7 +7,7 @@ import { Doc, Opt } from '../../../fields/Doc';
 import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { StyleProp } from '../StyleProvider';
-import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionCarouselView.scss';
 import { CollectionSubView } from './CollectionSubView';
@@ -41,7 +41,7 @@ export class CollectionCarouselView extends CollectionSubView() {
         e.stopPropagation();
         this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
     };
-    captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => {
         // first look for properties on the document in the carousel, then fallback to properties on the container
         const childValue = doc?.['caption-' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined;
         return childValue ?? this._props.styleProvider?.(this.layoutDoc, captionProps, property);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 874cdffd9..1f867fc44 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -431,7 +431,7 @@ export class CollectionDockingView extends CollectionSubView() {
     };
 
     public CaptureThumbnail() {
-        const content = this._props.DocumentView?.()?.ContentDiv;
+        const content = this.DocumentView?.()?.ContentDiv;
         if (content) {
             const _width = Number(getComputedStyle(content).width.replace('px', ''));
             const _height = Number(getComputedStyle(content).height.replace('px', ''));
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 98ae01591..a54a5ec81 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -97,14 +97,12 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     Document={selDoc}
                     fieldKey="data"
                     dropAction="embed"
-                    setHeight={returnFalse}
                     styleProvider={DefaultStyleProvider}
                     bringToFront={emptyFunction}
                     select={emptyFunction}
                     isContentActive={returnTrue}
                     isAnyChildContentActive={returnFalse}
                     isSelected={returnFalse}
-                    docViewPath={returnEmptyDoclist}
                     moveDocument={returnFalse}
                     addDocument={returnFalse}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 302ccd2db..0795104d9 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -18,7 +18,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { LightboxView } from '../LightboxView';
-import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
@@ -203,7 +203,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         }
     };
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
         switch (property) {
             case StyleProp.BoxShadow:
                 if (doc && DragManager.docsBeingDragged.includes(doc)) {
@@ -238,7 +238,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 PanelWidth={width}
                 PanelHeight={height}
                 styleProvider={this.styleProvider}
-                docViewPath={this._props.docViewPath}
+                containerViewPath={this.docViewPathFunc}
                 layout_fitWidth={this._props.childLayoutFitWidth}
                 isContentActive={emptyFunction}
                 onKey={this.onKeyDown}
@@ -511,7 +511,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                             action((entries: any) => {
                                 if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
                                     const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
-                                    if (!LightboxView.IsLightboxDocView(this._props.docViewPath())) {
+                                    if (!LightboxView.IsLightboxDocView(this.docViewPath)) {
                                         this._props.setHeight?.(height);
                                     }
                                 }
@@ -643,7 +643,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 onDragOver={e => this.onPointerMove(true, e.clientX, e.clientY)}
                 onDrop={this.onExternalDrop.bind(this)}
                 onContextMenu={this.onContextMenu}
-                onWheel={e => this._props.isContentActive(true) && e.stopPropagation()}>
+                onWheel={e => this._props.isContentActive() && e.stopPropagation()}>
                 {this.renderedSections}
             </div>
         );
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index b1d379631..d0df77cbe 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -48,7 +48,7 @@ export class CollectionPileView extends CollectionSubView() {
     removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
         (doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d));
         const ret = this._props.moveDocument?.(doc, targetCollection, addDoc) || false;
-        if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this._props.DocumentView?.()._props.removeDocument?.(this.Document);
+        if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.DocumentView?.()._props.removeDocument?.(this.Document);
         return ret;
     };
 
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 22a67c501..1239a038a 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -23,7 +23,7 @@ import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { CollectionSubView } from '../collections/CollectionSubView';
 import { LightboxView } from '../LightboxView';
 import { AudioWaveform } from '../nodes/audio/AudioWaveform';
-import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
+import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
 import { LabelBox } from '../nodes/LabelBox';
 import { VideoBox } from '../nodes/VideoBox';
 import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -825,7 +825,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
                     ref={action((r: DocumentView | null) => (anchor.view = r))}
                     Document={mark}
                     TemplateDataDocument={undefined}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     pointerEvents={this.noEvents ? returnNone : undefined}
                     styleProvider={this._props.styleProvider}
                     renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 995f071ca..11ca62b39 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -24,7 +24,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { LightboxView } from '../LightboxView';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
@@ -265,7 +265,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         return undefined;
     };
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
         if (property === StyleProp.Opacity && doc) {
             if (this._props.childOpacity) {
                 return this._props.childOpacity();
@@ -325,9 +325,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 renderDepth={this._props.renderDepth + 1}
                 PanelWidth={panelWidth}
                 PanelHeight={panelHeight}
-                pointerEvents={this._props.DocumentView?.()._props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
+                pointerEvents={this.DocumentView?.()._props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
                 styleProvider={this.styleProvider}
-                docViewPath={this._props.docViewPath}
+                containerViewPath={this.docViewPathFunc}
                 layout_fitWidth={this.childFitWidth}
                 isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
                 onKey={this.onKeyDown}
@@ -540,7 +540,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                             action((entries: any) => {
                                 if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
                                     const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
-                                    if (!LightboxView.IsLightboxDocView(this._props.docViewPath())) {
+                                    if (!LightboxView.IsLightboxDocView(this.docViewPath)) {
                                         this._props.setHeight?.(height);
                                     }
                                 }
@@ -681,7 +681,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                     renderDepth={this._props.renderDepth}
                     focus={emptyFunction}
                     styleProvider={this._props.styleProvider}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     childFilters={this._props.childFilters}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 570330174..2e0a75281 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -21,7 +21,7 @@ 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 { ViewBoxBaseComponent, ViewBoxBaseProps } from '../DocComponent';
 import { LoadingBox } from '../nodes/LoadingBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { CollectionView, CollectionViewProps } from './CollectionView';
@@ -31,7 +31,7 @@ export interface SubCollectionViewProps extends CollectionViewProps {
 }
 
 export function CollectionSubView<X>(moreProps?: X) {
-    class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps>() {
+    class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps & ViewBoxBaseProps>() {
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected _mainCont?: HTMLDivElement;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 18e0b98ef..76b934802 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -169,7 +169,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             this._props.removeDocument?.(doc);
             if (ind > 0 && prev) {
                 FormattedTextBox.SetSelectOnLoad(prev);
-                DocumentManager.Instance.getDocumentView(prev, this._props.DocumentView?.())?.select(false);
+                DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false);
             }
             return true;
         }
@@ -347,7 +347,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                     renderDepth={this._props.renderDepth + 1}
                     focus={emptyFunction}
                     styleProvider={this._props.styleProvider}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     childFilters={this._props.childFilters}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 4a239e4b1..7c9ef9f48 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -232,13 +232,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
     childLayoutTemplate = () => this._props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null);
     isContentActive = (outsideReaction?: boolean) => this._isContentActive;
 
-    pointerEvents = () => {
-        const viewPath = this._props.DocumentView?.()?._props.docViewPath();
-        return (
-            this.layoutDoc._lockedPosition && //
-            viewPath?.lastElement()?.Document?._type_collection === CollectionViewType.Freeform
-        );
-    };
+    pointerEvents = () =>
+        this.layoutDoc._lockedPosition && //
+        this.Document?._type_collection === CollectionViewType.Freeform;
 
     render() {
         TraceMobx();
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 80808be92..2441c67e6 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -29,7 +29,7 @@ import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
 import { Colors } from '../global/globalEnums';
-import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { DashFieldView } from '../nodes/formattedText/DashFieldView';
 import { PinProps, PresBox, PresMovement } from '../nodes/trails';
@@ -469,7 +469,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                     dontCenter={'y'}
                     whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
                     focus={this.focusFunc}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     bringToFront={emptyFunction}
                     pinToPres={TabDocView.PinDoc}
                 />
@@ -528,7 +528,7 @@ class TabMiniThumb extends React.Component<TabMiniThumbProps> {
 }
 @observer
 export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> {
-    static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    static miniStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
         if (doc) {
             switch (property.split(':')[0]) {
                 default:
@@ -595,10 +595,9 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
             <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
                 <CollectionFreeFormView
                     Document={this._props.document}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
                     noOverlay={true} // don't render overlay Docs since they won't scale
-                    setHeight={returnFalse}
                     isContentActive={emptyFunction}
                     isAnyChildContentActive={returnFalse}
                     select={emptyFunction}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 01b80e209..205d536de 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -27,7 +27,7 @@ import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
 import { EditableView } from '../EditableView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
-import { DocumentView, DocumentViewInternal, DocumentViewInternalProps, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
@@ -198,7 +198,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         const ind = DocListCast(this.dataDoc[key]).indexOf(doc instanceof Doc ? doc : doc.lastElement());
 
         const res = (doc instanceof Doc ? [doc] : doc).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._props.DocumentView?.())?.select(false);
+        res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView.DocumentView?.())?.select(false);
         return res;
     };
 
@@ -867,7 +867,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             e.preventDefault();
         }
     };
-    titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    titleStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
 
         const treeView = this.treeView;
@@ -899,7 +899,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         }
         return treeView._props.styleProvider?.(doc, props, property);
     };
-    embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    embeddedStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (property.startsWith(StyleProp.Decorations)) return null;
         return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
     };
@@ -977,7 +977,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 hideResizeHandles={this.treeView.outlineMode}
                 styleProvider={this.titleStyleProvider}
                 onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
-                docViewPath={this.treeView._props.docViewPath}
+                containerViewPath={this.treeView.docViewPathFunc}
                 treeViewDoc={this.treeView.Document}
                 addDocument={undefined}
                 addDocTab={this._props.addDocTab}
@@ -1084,7 +1084,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     ScreenToLocalTransform={this.docTransform}
                     renderDepth={this._props.renderDepth + 1}
                     treeViewDoc={this.treeView?.Document}
-                    docViewPath={this.treeView._props.docViewPath}
+                    containerViewPath={this.treeView.docViewPathFunc}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8268a47d8..a8f22aaae 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -38,7 +38,7 @@ import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } fr
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
 import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
-import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../../nodes/trails/PresBox';
@@ -162,7 +162,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : this._props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedCenteringShiftY(): number {
-        const dv = this._props.DocumentView?.();
+        const dv = this.DocumentView?.();
         const fitWidth = this._props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth;
         const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling;
         // if freeform has a native aspect, then the panel height needs to be adjusted to match it
@@ -250,7 +250,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
-        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
+        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
     };
     addDocument = (newBox: Doc | Doc[]) => {
         let retVal = false;
@@ -409,7 +409,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @undoBatch
     internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData) {
-        if (linkDragData.linkDragView.props.docViewPath().includes(this._props.docViewPath().lastElement())) {
+        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
@@ -485,7 +485,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const ptsParent = e;
             if (ptsParent) {
                 const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
-                const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this._props.DocumentView?.())!);
+                const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.DocumentView?.())!);
                 const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
                 const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
                 de.moveDocument = this._props.moveDocument;
@@ -586,7 +586,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
 
-    clusterStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
+    clusterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => {
         let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props'  used to be renderDepth + 1
         if (doc && this.childDocList?.includes(doc))
             switch (property) {
@@ -629,7 +629,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this._downY = this._lastY = e.pageY;
         this._downTime = Date.now();
         const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode;
-        if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this._props.isContentActive(true)) {
+        if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this._props.isContentActive()) {
             if (!this.Document.isGroup) {
                 // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
                 // prettier-ignore
@@ -704,7 +704,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     @action
     onEraserUp = (e: PointerEvent): void => {
-        this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.Document));
+        this._deleteList.forEach(ink => ink._props.removeDocument?.(ink.Document));
         this._deleteList = [];
         this._batch?.end();
     };
@@ -714,7 +714,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (this._lightboxDoc) this._lightboxDoc = undefined;
         if (Utils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) {
             if (this.onBrowseClickHandler()) {
-                this.onBrowseClickHandler().script.run({ documentView: this._props.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
+                this.onBrowseClickHandler().script.run({ documentView: this.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
                 e.stopPropagation();
                 e.preventDefault();
             } else if (this.isContentActive() && e.shiftKey) {
@@ -737,7 +737,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const ctrlKey = e.ctrlKey && !e.shiftKey;
         const shiftKey = e.shiftKey && !e.ctrlKey;
         PresBox.Instance?.pauseAutoPres();
-        this._props.DocumentView?.().clearViewTransition();
+        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);
@@ -808,7 +808,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
 
         return this.childDocs
-            .map(doc => DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.()))
+            .map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.()))
             .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
             .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
             .filter(
@@ -905,7 +905,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._props.DocumentView?.())?.ComponentView as InkingStroke;
+                const otherInk = DocumentManager.Instance.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));
@@ -979,7 +979,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         freeformScrollMode.Zoom : freeformScrollMode.Pan // prettier-ignore
         ) {
             case freeformScrollMode.Pan:
-                if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this._props.isContentActive(true)) {
+                if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this._props.isContentActive()) {
                     const deltaX = e.shiftKey ? e.deltaX : e.ctrlKey ? 0 : e.deltaX;
                     const deltaY = e.shiftKey ? 0 : e.ctrlKey ? e.deltaY : e.deltaY;
                     this.scrollPan({ deltaX: -deltaX * this.screenToFreeformContentsXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToFreeformContentsXf.Scale });
@@ -987,7 +987,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 }
             default:
             case freeformScrollMode.Zoom:
-                if ((e.ctrlKey || !scrollable) && this._props.isContentActive(true)) {
+                if ((e.ctrlKey || !scrollable) && this._props.isContentActive()) {
                     this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this._props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
                     e.preventDefault();
                 }
@@ -1065,7 +1065,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     nudge = (x: number, y: number, nudgeTime: number = 500) => {
-        const collectionDoc = this._props.docViewPath().lastElement().Document;
+        const collectionDoc = this.Document;
         if (collectionDoc?._type_collection !== CollectionViewType.Freeform) {
             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
@@ -1244,7 +1244,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 moveDocument={this._props.moveDocument}
                 pinToPres={this._props.pinToPres}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                docViewPath={this._props.docViewPath}
+                containerViewPath={this.docViewPathFunc}
                 styleProvider={this.clusterStyleProvider}
                 dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
                 bringToFront={this.bringToFront}
@@ -1540,7 +1540,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     updateIcon = () =>
         CollectionFreeFormView.UpdateIcon(
             this.layoutDoc[Id] + '-icon' + new Date().getTime(),
-            this._props.docViewPath().lastElement().ContentDiv!,
+            this.DocumentView?.().ContentDiv!,
             NumCast(this.layoutDoc._width),
             NumCast(this.layoutDoc._height),
             this._props.PanelWidth(),
@@ -1741,7 +1741,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0;
 
     incrementalRender = action(() => {
-        if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath())) {
+        if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.docViewPath)) {
             const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
             const loadIncrement = 5;
             for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
@@ -1755,7 +1755,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     // which will be a DocumentView.  In this sitation, this freeform views acts as an annotation overlay for
     // the underlying DocumentView and will pan and scoll with the underlying Documen tView.
     @computed get underlayViews() {
-        return this._props.children ? [toJS(this._props.children)] : [];
+        return this._props.children ? this._props.children : [];
     }
 
     @computed get placeholder() {
@@ -1798,7 +1798,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 brushedView={this.brushedView}
                 isAnnotationOverlay={this.isAnnotationOverlay}
                 transform={this.PanZoomCenterXf}
-                transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this._props.DocumentView?.()?.Document._viewTransition, 'string', null))}
+                transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))}
                 viewDefDivClick={this._props.viewDefDivClick}>
                 {this.underlayViews}
                 {this.contentViews}
@@ -1896,6 +1896,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         <DocumentView
                             {...this._props}
                             Document={this._lightboxDoc}
+                            containerViewPath={this.docViewPathFunc}
                             TemplateDataDocument={undefined}
                             PanelWidth={this.lightboxPanelWidth}
                             PanelHeight={this.lightboxPanelHeight}
@@ -1942,15 +1943,15 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
         DocumentManager.Instance.showDocument(dv.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
             if (!focused) {
                 const selfFfview = !dv.Document.isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
-                let containers = dv.props.docViewPath();
+                let containers = dv.containerViewPath?.() ?? [];
                 let parFfview = dv.CollectionFreeFormView;
                 for (var cont of containers) {
                     parFfview = parFfview ?? cont.CollectionFreeFormView;
                 }
-                while (parFfview?.Document.isGroup) parFfview = parFfview.props.DocumentView?.().CollectionFreeFormView;
+                while (parFfview?.Document.isGroup) parFfview = parFfview.DocumentView?.().CollectionFreeFormView;
                 const ffview = selfFfview && selfFfview.layoutDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
                 ffview?.zoomSmoothlyAboutPt(ffview.screenToFreeformContentsXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
-                Doc.linkFollowHighlight(dv?.props.Document, false);
+                Doc.linkFollowHighlight(dv?.Document, false);
             }
         });
 }
@@ -1968,13 +1969,13 @@ ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
 });
 ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
     SelectionManager.Views.forEach(view =>
-        view.props.pinToPres(view.Document, {
+        view._props.pinToPres(view.Document, {
             currentFrame: Cast(view.Document.currentFrame, 'number', null),
             pinData: {
                 poslayoutview: pinContent,
                 dataview: pinContent,
             },
-            pinViewport: MarqueeView.CurViewBounds(view.Document, view.props.PanelWidth(), view.props.PanelHeight()),
+            pinViewport: MarqueeView.CurViewBounds(view.Document, view._props.PanelWidth(), view._props.PanelHeight()),
         })
     );
 });
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index d105b04f7..54ade031b 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -147,8 +147,8 @@ export class CollectionLinearView extends CollectionSubView() {
         switch (doc.layout) {
             case '<LinkingUI>':          return this.getLinkUI();
             case '<CurrentlyPlayingUI>': return this.getCurrentlyPlayingUI();
-            case '<UndoStack>':          return <UndoStack />;
-            case '<Branching>':          return Doc.UserDoc().isBranchingMode  ? <BranchingTrailManager /> : null;
+            case '<UndoStack>':          return <UndoStack key={doc[Id]}/>;
+            case '<Branching>':          return Doc.UserDoc().isBranchingMode  ? <BranchingTrailManager key={doc[Id]} /> : null;
         }
 
         const nested = doc._type_collection === CollectionViewType.Linear;
@@ -189,7 +189,7 @@ export class CollectionLinearView extends CollectionSubView() {
                     dontRegisterView={BoolCast(this.Document.childDontRegisterViews)}
                     focus={emptyFunction}
                     styleProvider={this._props.styleProvider}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={this.docViewPathFunc}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     childFilters={this._props.childFilters}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 563084af8..dd1305465 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -127,7 +127,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
     private get totalRatioAllocation(): number | undefined {
         const layoutInfoLen = this.resolvedLayoutInformation.widthSpecifiers.length;
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
-            return this._props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this._props.Document._xMargin);
+            return this._props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._xMargin);
         }
     }
 
@@ -259,14 +259,14 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 Document={childLayout}
                 TemplateDataDocument={childLayout.resolvedDataDoc as Doc}
                 styleProvider={this._props.styleProvider}
-                docViewPath={this._props.docViewPath}
+                containerViewPath={this.docViewPathFunc}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
                 renderDepth={this._props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
                 rootSelected={this.rootSelected}
-                dragAction={(this._props.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
+                dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 suppressSetHeight={true}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index bf0d39197..48c4173d6 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -122,7 +122,7 @@ export class CollectionMultirowView extends CollectionSubView() {
     private get totalRatioAllocation(): number | undefined {
         const layoutInfoLen = this.resolvedLayoutInformation.heightSpecifiers.length;
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
-            return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this._props.Document._yMargin);
+            return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.Document._yMargin);
         }
     }
 
@@ -254,14 +254,14 @@ export class CollectionMultirowView extends CollectionSubView() {
                 Document={layout}
                 TemplateDataDocument={layout.resolvedDataDoc as Doc}
                 styleProvider={this._props.styleProvider}
-                docViewPath={this._props.docViewPath}
+                containerViewPath={this.docViewPathFunc}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
                 renderDepth={this._props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
                 rootSelected={this.rootSelected}
-                dropAction={StrCast(this.Document.childDragAction) as dropActionType}
+                dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={dxf}
@@ -270,7 +270,6 @@ export class CollectionMultirowView extends CollectionSubView() {
                 hideResizeHandles={layout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
                 hideDecorationTitle={this._props.childHideDecorationTitle}
                 fitContentsToBox={this._props.fitContentsToBox}
-                dragAction={this._props.childDragAction}
                 focus={this._props.focus}
                 childFilters={this.childDocFilters}
                 childFiltersByRanges={this.childDocRangeFilters}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 492aed0ea..09352d7a4 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -16,7 +16,7 @@ import { undoable, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
@@ -907,7 +907,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 childFiltersByRanges={this.childDocRangeFilters}
                                 searchFilterDocs={this.searchFilterDocs}
                                 styleProvider={DefaultStyleProvider}
-                                docViewPath={returnEmptyDoclist}
+                                containerViewPath={returnEmptyDoclist}
                                 moveDocument={this._props.moveDocument}
                                 addDocument={this.addRow}
                                 removeDocument={this._props.removeDocument}
@@ -962,7 +962,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
 
     tableWidthFunc = () => this._props.schema.tableWidth;
     screenToLocalXf = () => this._props.schema.ScreenToLocalBoxXf().translate(0, -this._props.rowHeight() - this._props.index * this._props.rowHeight());
-    noOpacityStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
+    noOpacityStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => {
         if (property === StyleProp.Opacity) return 1;
         return DefaultStyleProvider(doc, props, property);
     };
@@ -971,6 +971,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
             <DocumentView
                 key={this._props.doc[Id]}
                 {...this._props.schema._props}
+                containerViewPath={this._props.schema.docViewPathFunc}
                 LayoutTemplate={this._props.schema._props.childLayoutTemplate}
                 LayoutTemplateString={SchemaRowBox.LayoutString(this._props.schema._props.fieldKey, this._props.index)}
                 Document={this._props.doc}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 5a3be826b..8e1fba699 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -12,7 +12,7 @@ import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoable } from '../../../util/UndoManager';
-import { ViewBoxBaseComponent } from '../../DocComponent';
+import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { OpenWhere } from '../../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
@@ -24,7 +24,7 @@ interface SchemaRowBoxProps {
     rowIndex: number;
 }
 @observer
-export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRowBoxProps>() {
+export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRowBoxProps & ViewBoxBaseProps>() {
     public static LayoutString(fieldKey: string, rowIndex: number) {
         return FieldView.LayoutString(SchemaRowBox, fieldKey).replace('fieldKey', `rowIndex={${rowIndex}} fieldKey`);
     }
@@ -38,11 +38,11 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
     bounds = () => this._ref?.getBoundingClientRect();
 
     @computed get schemaView() {
-        return this._props.DocumentView?.()._props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView;
+        return this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionSchemaView;
     }
 
     @computed get schemaDoc() {
-        return this._props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
+        return this.DocumentView?.().containerViewPath?.().lastElement()?.Document;
     }
 
     @computed get rowIndex() {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 85269028b..422c4155d 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -77,7 +77,6 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
             childFiltersByRanges: returnEmptyFilter,
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
-            docViewPath: returnEmptyDoclist,
             isSelected: returnFalse,
             setHeight: returnFalse,
             select: emptyFunction,
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index e57ef4871..3084a7972 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -43,14 +43,14 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
         if (checkResult) {
             const selView = selectedViews.lastElement();
             const fieldKey = selView.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
-            const layoutFrameNumber = Cast(selView._props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+            const layoutFrameNumber = Cast(selView.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent';
         }
         selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent');
         selectedViews.forEach(dv => {
             const fieldKey = dv.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
-            const layoutFrameNumber = Cast(dv._props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+            const layoutFrameNumber = Cast(dv.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(dv.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             if (contentFrameNumber !== undefined) {
                 const obj: { [key: string]: Opt<string> } = {};
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 85e97f95f..dc4aee1ca 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -136,6 +136,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
 
     deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this._props.linkDoc))));
     @observable _hover = false;
+    docView = () => this.props.docView;
     render() {
         const destinationIcon = Doc.toIcon(this._props.destinationDoc) as any as IconProp;
 
@@ -179,7 +180,8 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                                 this._props.linkDoc &&
                                 this._props.clearLinkEditor &&
                                 LinkInfo.SetLinkInfo({
-                                    docProps: this._props.docView._props,
+                                    DocumentView: this.docView,
+                                    styleProvider: this._props.docView._props.styleProvider,
                                     linkSrc: this._props.sourceDoc,
                                     linkDoc: this._props.linkDoc,
                                     showHeader: false,
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 7e344dd7a..31841c596 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -69,7 +69,6 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
                         isSelected={returnTrue}
                         isContentActive={returnTrue}
                         select={returnTrue}
-                        setHeight={returnFalse}
                         addDocument={undefined}
                         addDocTab={returnTrue}
                         pinToPres={emptyFunction}
@@ -81,7 +80,6 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
                         PanelHeight={this.getPHeight}
                         renderDepth={0}
                         focus={emptyFunction}
-                        docViewPath={returnEmptyDoclist}
                         whenChildContentsActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
                         childFilters={returnEmptyFilter}
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index 3d159c3e3..6760a0b1e 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -302,7 +302,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
                         styleProvider={DefaultStyleProvider}
                         ScreenToLocalTransform={this.newLightboxScreenToLocal}
                         renderDepth={0}
-                        docViewPath={returnEmptyDoclist}
+                        containerViewPath={returnEmptyDoclist}
                         childFilters={this.docFilters}
                         childFiltersByRanges={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 908cd5dc0..1729af191 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -211,7 +211,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // removes from currently playing display
     @action
     removeCurrentlyPlaying = () => {
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
             const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
             index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -221,7 +221,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // adds doc to currently playing display
     @action
     addCurrentlyPlaying = () => {
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         if (!CollectionStackedTimeline.CurrentlyPlaying) {
             CollectionStackedTimeline.CurrentlyPlaying = [];
         }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index ad5aabc21..83cabf355 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -15,7 +15,7 @@ import { DocComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import './CollectionFreeFormDocumentView.scss';
-import { DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from './DocumentView';
+import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
 import { FieldViewProps } from './FieldView';
 
 export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps {
@@ -149,7 +149,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return this._props.CollectionFreeFormView;
     }
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
         if (doc === this.layoutDoc) {
             switch (property) {
                 case StyleProp.Opacity:         return this._props.w_Opacity(); // only change the opacity for this specific document, not its children
@@ -229,7 +229,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     @action public float = () => {
         const topDoc = this.Document;
-        const containerDocView = this._props.docViewPath().lastElement();
+        const containerDocView = this._props.containerViewPath?.().lastElement();
         const screenXf = containerDocView?.screenToContentsTransform();
         if (screenXf) {
             SelectionManager.DeselectAll();
@@ -252,7 +252,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     };
 
     nudge = (x: number, y: number) => {
-        const [locX, locY] = this.ScreenToLocalBoxXf().transformDirection(x, y);
+        const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y);
         this._props.Document.x = this._props.w_X() + locX;
         this._props.Document.y = this._props.w_Y() + locY;
     };
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 5e7e568b0..b171db17c 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -11,7 +11,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import './ComparisonBox.scss';
-import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from './DocumentView';
+import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
 
@@ -148,7 +148,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
             e => this.clearDoc(which)
         );
     };
-    docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    docStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
         if (property === StyleProp.PointerEvents) return 'none';
         return this._props.styleProvider?.(doc, props, property);
     };
@@ -178,6 +178,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                         {...this._props}
                         Document={targetDoc}
                         TemplateDataDocument={undefined}
+                        containerViewPath={this.docViewPathFunc}
                         moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={returnZero}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 5a55ca764..5ceec98cb 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
-import { ObservableMap, action, computed, observable, runInAction } from 'mobx';
+import { ObservableMap, action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
@@ -14,10 +14,9 @@ import { TraceMobx } from '../../../../fields/util';
 import { Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
 import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { SidebarAnnos } from '../../SidebarAnnos';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
 import { DocFocusOptions, DocumentView } from '../DocumentView';
@@ -37,22 +36,28 @@ export enum DataVizView {
 }
 
 @observer
-export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+export class DataVizBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
-    private _ffref = React.createRef<CollectionFreeFormView>();
     private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
     crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
-    @observable schemaDataVizChildren: any = undefined;
+    @observable _schemaDataVizChildren: any = undefined;
     @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+
+    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+        super(props);
+        makeObservable(this);
+        this._props.setContentView?.(this);
+    }
+
     @computed get annotationLayer() {
         TraceMobx();
         return <div className="dataVizBox-annotationLayer" style={{ height: this._props.PanelHeight(), width: this._props.PanelWidth() }} ref={this._annotationLayer} />;
     }
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -211,12 +216,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         .ScreenToLocalTransform()
                         .scale(this._props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
-                    const fullWidth = this._props.width;
-                    const mapWidth = fullWidth! - this.sidebarWidth();
+                    const fullWidth = NumCast(this.layoutDoc._width);
+                    const mapWidth = fullWidth - this.sidebarWidth();
                     if (this.sidebarWidth() + localDelta[0] > 0) {
                         this.layoutDoc._layout_showSidebar = true;
-                        this.layoutDoc._width = fullWidth! + localDelta[0];
-                        this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth! + localDelta[0])).toString() + '%';
+                        this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
+                        this.layoutDoc._width = fullWidth + localDelta[0];
                     } else {
                         this.layoutDoc._layout_showSidebar = false;
                         this.layoutDoc._width = mapWidth;
@@ -269,12 +274,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             axes: this.axes,
             //width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
             height: (this._props.PanelHeight() / scale - 32) /* height of 'change view' button */ * 0.9,
-            width: (this._props.PanelWidth() / scale) * 0.9,
+            width: ((this._props.PanelWidth() - this.sidebarWidth()) / scale) * 0.9,
             margin: { top: 10, right: 25, bottom: 75, left: 45 },
         };
         if (!this.records.length) return 'no data/visualization';
         switch (this.dataVizView) {
-            case DataVizView.TABLE:     return <TableBox  {...sharedProps} docView={this._props.DocumentView} selectAxes={this.selectAxes} />;
+            case DataVizView.TABLE:     return <TableBox  {...sharedProps} docView={this.DocumentView} selectAxes={this.selectAxes} />;
             case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} vizBox={this} />;
             case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
             case DataVizView.PIECHART:  return <PieChart  {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} 
@@ -285,7 +290,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         if ((this.Document._freeform_scale || 1) !== 1) return;
-        if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             this._props.select(false);
             MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
             this._marqueeing = [e.clientX, e.clientY];
@@ -345,7 +350,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     render() {
         if (this.layoutDoc && this.layoutDoc.dataViz_asSchema) {
-            this.schemaDataVizChildren = DocListCast(DocCast(this.layoutDoc.dataViz_asSchema)[Doc.LayoutFieldKey(DocCast(this.layoutDoc.dataViz_asSchema))]).length;
+            this._schemaDataVizChildren = DocListCast(DocCast(this.layoutDoc.dataViz_asSchema)[Doc.LayoutFieldKey(DocCast(this.layoutDoc.dataViz_asSchema))]).length;
             this.updateSchemaViz();
         }
 
@@ -366,7 +371,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 }}
                 onWheel={e => e.stopPropagation()}
                 ref={this._mainCont}>
-                <div className={'datatype-button'}>
+                <div className="datatype-button">
                     <Toggle text={'  TABLE  '} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz === DataVizView.TABLE} />
                     <Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz === DataVizView.LINECHART} />
                     <Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz === DataVizView.HISTOGRAM} />
@@ -414,7 +419,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 </div>
                 {this.sidebarHandle}
                 {this.annotationLayer}
-                {!this._mainCont.current || !this._annotationLayer.current ? null : (
+                {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
                     <MarqueeAnnotator
                         ref={this._marqueeref}
                         Document={this.Document}
@@ -422,7 +427,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         scrollTop={0}
                         annotationLayerScrollTop={NumCast(this.Document._layout_scrollTop)}
                         scaling={returnOne}
-                        docView={this._props.DocumentView!}
+                        docView={this.DocumentView}
                         addDocument={this.sidebarAddDocument}
                         finishMarquee={this.finishMarquee}
                         savedAnnotations={this.savedAnnotations}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 4cc7fe4c8..0d76959af 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -116,11 +116,12 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
 
 @observer
 export class DocumentContentsView extends ObservableReactComponent<
-    DocumentViewProps &
-        FieldViewProps & {
-            setHeight?: (height: number) => void;
-            layout_fieldKey: string;
-        }
+    FieldViewProps & {
+        layout_fieldKey: string;
+        LayoutTemplateString?: string;
+        onClick?: () => ScriptField;
+        LayoutTemplate?: () => Opt<Doc>;
+    }
 > {
     constructor(props: any) {
         super(props);
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index dfd610581..25f56e69a 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -25,7 +25,7 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
     }
 
     static get DocViews() {
-        return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.IsLightboxDocView(v._props.docViewPath())) : DocumentManager.Instance.DocumentViews;
+        return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.IsLightboxDocView(v.docViewPath)) : DocumentManager.Instance.DocumentViews;
     }
     render() {
         const view = this._props.view;
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index c549a146a..f5949a451 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -152,8 +152,8 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
                 DocumentLinksButton.AnnotationUri = undefined;
                 //!this._props.StartLink
             } else if (startLink !== endLink) {
-                endLink = endLinkView?.docView?._componentView?.getAnchor?.(true, pinProps) || endLink;
-                startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink;
+                endLink = endLinkView?.ComponentView?.getAnchor?.(true, pinProps) || endLink;
+                startLink = DocumentLinksButton.StartLinkView?.ComponentView?.getAnchor?.(true) || startLink;
                 const linkDoc = DocUtils.MakeLink(startLink, endLink, { link_relationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined });
 
                 LinkManager.currentLink = linkDoc;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6cc61ec62..5345ef0d8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -109,7 +109,7 @@ export interface DocFocusOptions {
     easeFunc?: 'linear' | 'ease'; // transition method for scrolling
 }
 export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => Opt<number>;
-export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => any;
+export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => any;
 export interface DocComponentView {
     fieldKey?: string;
     annotationKey?: string;
@@ -143,20 +143,31 @@ export interface DocComponentView {
     snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
     search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
 }
-// These props are passed to both FieldViews and DocumentViews
+/**
+ * props that DocumentViews, DocumentInternalViews and FieldViews all can use
+ * */
 export interface DocumentViewSharedProps {
-    renderDepth: number;
     Document: Doc;
     TemplateDataDocument?: Doc;
+    renderDepth: number;
     scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
-    DocumentView?: () => DocumentView;
+    treeViewDoc?: Doc;
+    xPadding?: number;
+    yPadding?: number;
+    dontRegisterView?: boolean;
+    dropAction?: dropActionType;
+    dragAction?: dropActionType;
+    forceAutoHeight?: boolean;
+    ignoreAutoHeight?: boolean;
+    disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
     CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
     isGroupActive?: () => string | undefined; // is this document part of a group that is active
     setContentView?: (view: DocComponentView) => any;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    docViewPath: () => DocumentView[];
+    isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
+    isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
     childFilters: () => string[];
     childFiltersByRanges: () => string[];
     styleProvider: Opt<StyleProviderFunc>;
@@ -178,29 +189,12 @@ export interface DocumentViewSharedProps {
     waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
     defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
     pointerEvents?: () => Opt<string>;
-    treeViewDoc?: Doc;
-    xPadding?: number;
-    yPadding?: number;
-    dontRegisterView?: boolean;
-    childHideDecorationTitle?: boolean;
-    childHideResizeHandles?: boolean;
-    childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
-    dropAction?: dropActionType;
-    dragAction?: dropActionType;
-    dragWhenActive?: boolean;
-    dontHideOnDrag?: boolean;
-    hideLinkButton?: boolean;
-    hideCaptions?: boolean;
-    ignoreAutoHeight?: boolean;
-    forceAutoHeight?: boolean;
-    suppressSetHeight?: boolean;
-    disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
-    onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
 }
 
-// these props are specific to DocuentViews
+/**
+ * props that are used by DocumentViews and DocumentInternalViews but not by the contents of those views (FieldViews).
+ */
 export interface DocumentViewProps extends DocumentViewSharedProps {
-    // properties specific to DocumentViews but not to FieldView
     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
     hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
@@ -209,15 +203,22 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     hideOpenButton?: boolean;
     hideDeleteButton?: boolean;
     hideLinkAnchors?: boolean;
+    hideLinkButton?: boolean;
+    hideCaptions?: boolean;
     contentPointerEvents?: 'none' | 'all' | undefined; // pointer events allowed for content of a document view.  eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
     LayoutTemplateString?: string;
     dontCenter?: 'x' | 'y' | 'xy';
-    isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
-    isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
+    childHideDecorationTitle?: boolean;
+    childHideResizeHandles?: boolean;
+    childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
+    dragWhenActive?: boolean;
+    dontHideOnDrag?: boolean;
+    suppressSetHeight?: boolean;
+    onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
     NativeWidth?: () => number;
     NativeHeight?: () => number;
-    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps
     LayoutTemplate?: () => Opt<Doc>;
+    containerViewPath?: () => DocumentView[];
     contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
     onClick?: () => ScriptField;
     onDoubleClick?: () => ScriptField;
@@ -229,12 +230,18 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     dragEnding?: () => void;
 }
 
-// these props are only available in DocumentViewIntenral
-export interface DocumentViewInternalProps extends DocumentViewProps {
-    isSelected: () => boolean;
+/**
+ * props used by DocInternalViews and FieldViews but not DocumentViews
+ * these props correspond to things that the DocumentView creates and thus doesn't need to receive as a prop
+ */
+export interface DocumentViewInternalSharedProps {
+    DocumentView?: () => DocumentView;
     select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
-    DocumentView: () => DocumentView;
-    viewPath: () => DocumentView[];
+    isSelected: () => boolean;
+    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
+}
+export interface DocumentViewInternalProps extends DocumentViewProps, DocumentViewInternalSharedProps {
+    docViewPath: () => DocumentView[];
 }
 
 @observer
@@ -265,17 +272,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return this._animateScaleTime ?? 100;
     }
     public get displayName() {
-        return 'DocumentViewInternal(' + this._props.Document.title + ')';
+        return 'DocumentViewInternal(' + this.Document.title + ')';
     } // this makes mobx trace() statements more descriptive
 
+    public get DocumentView() {
+        return this._props.DocumentView!;
+    }
+
     public get ContentDiv() {
         return this._mainCont.current;
     }
     public get LayoutFieldKey() {
         return Doc.LayoutFieldKey(this.layoutDoc);
     }
+    @computed get styleProps(): FieldViewProps {
+        return { ...this._props, fieldKey: '' };
+    }
     @computed get layout_showTitle() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.ShowTitle) as Opt<string>;
+        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.ShowTitle) as Opt<string>;
     }
     @computed get NativeDimScaling() {
         return this._props.NativeDimScaling?.() || 1;
@@ -284,32 +298,32 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
     }
     @computed get opacity() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Opacity);
+        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.Opacity);
     }
     @computed get boxShadow() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow);
+        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BoxShadow);
     }
     @computed get borderRounding() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
+        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BorderRounding);
     }
     @computed get widgetDecorations() {
         TraceMobx();
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Decorations);
+        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.Decorations);
     }
     @computed get backgroundBoxColor() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor + ':box');
+        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BackgroundColor + ':box');
     }
     @computed get docContents() {
-        return this._props.styleProvider?.(this.Document, this._props, StyleProp.DocContents);
+        return this._props.styleProvider?.(this.Document, this.styleProps, StyleProp.DocContents);
     }
     @computed get headerMargin() {
-        return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) || 0;
+        return this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.HeaderMargin) || 0;
     }
     @computed get layout_showCaption() {
-        return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.ShowCaption) || 0;
+        return this._props?.hideCaptions ? undefined : this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.ShowCaption) || 0;
     }
     @computed get titleHeight() {
-        return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.TitleHeight) || 0;
+        return this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.TitleHeight) || 0;
     }
     @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined;
     @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
@@ -363,7 +377,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             { fireImmediately: true }
         );
         this._disposers.pointerevents = reaction(
-            () => this._props.styleProvider?.(this.Document, this._props, StyleProp.PointerEvents),
+            () => this._props.styleProvider?.(this.Document, this.styleProps, StyleProp.PointerEvents),
             pointerevents => (this._pointerEvents = pointerevents),
             { fireImmediately: true }
         );
@@ -378,32 +392,32 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     setupHandlers() {
         this.cleanupHandlers(false);
         if (this._mainCont.current) {
-            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this._props.Document, this.preDropFunc);
+            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.Document, this.preDropFunc);
         }
     }
 
     cleanupHandlers(unbrush: boolean) {
         this._dropDisposer?.();
-        unbrush && Doc.UnBrushDoc(this._props.Document);
+        unbrush && Doc.UnBrushDoc(this.Document);
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
     startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
         if (this._mainCont.current) {
-            const views = SelectionManager.Views.filter(dv => dv.docView?._mainCont.current);
-            const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this._props.DocumentView()];
+            const views = SelectionManager.Views.filter(dv => dv.ContentDiv);
+            const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this.DocumentView()];
             const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document));
-            const screenXf = this.props.DocumentView().screenToViewTransform();
+            const screenXf = this.DocumentView().screenToViewTransform();
             const [left, top] = screenXf.inverse().transformPoint(0, 0);
             dragData.offset = screenXf.transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.treeViewDoc = this._props.treeViewDoc;
             dragData.removeDocument = this._props.removeDocument;
             dragData.moveDocument = this._props.moveDocument;
-            dragData.draggedViews = [this._props.DocumentView()];
+            dragData.draggedViews = [this.DocumentView()];
             dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
             DragManager.StartDocumentDrag(
-                selected.map(dv => dv.docView!._mainCont.current!),
+                selected.map(dv => dv.ContentDiv!),
                 dragData,
                 x,
                 y,
@@ -443,7 +457,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     const func = () => this.onDoubleClickHandler.script.run( {
                             this: this.Document,
                             scriptContext: this._props.scriptContext,
-                            documentView: this._props.DocumentView(),
+                            documentView: this.DocumentView(),
                             clientX, clientY,  altKey,  shiftKey,  ctrlKey,
                             value: undefined,
                         }, console.log  );
@@ -451,7 +465,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 } else if (!Doc.IsSystem(this.Document) && (defaultDblclick === undefined || defaultDblclick === 'default')) {
                     UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.Document, OpenWhere.lightbox), 'double tap');
                     SelectionManager.DeselectAll();
-                    Doc.UnBrushDoc(this._props.Document);
+                    Doc.UnBrushDoc(this.Document);
                 } else {
                     this._singleClickFunc?.();
                 }
@@ -474,7 +488,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                                 this: this.Document,
                                 _readOnly_: false,
                                 scriptContext: this._props.scriptContext,
-                                documentView: this._props.DocumentView(),
+                                documentView: this.DocumentView(),
                                 clientX,
                                 clientY,
                                 shiftKey,
@@ -498,7 +512,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 const sendToBack = e.altKey;
                 this._singleClickFunc =
                     // prettier-ignore
-                    clickFunc ?? (() => (sendToBack ? this._props.DocumentView()._props.bringToFront(this.Document, true) : 
+                    clickFunc ?? (() => (sendToBack ? this.DocumentView()._props.bringToFront(this.Document, true) : 
                                                       this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? 
                                         this._props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
                 const waitFordblclick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
@@ -522,7 +536,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 this._props.select(false);
             }
         }, 1000);
-        if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._props.DocumentView();
+        if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.DocumentView();
 
         this._downX = e.clientX;
         this._downY = e.clientY;
@@ -600,7 +614,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined;
     };
 
-    @undoBatch deleteClicked = () => this._props.removeDocument?.(this._props.Document);
+    @undoBatch deleteClicked = () => this._props.removeDocument?.(this.Document);
     @undoBatch setToggleDetail = () =>
         (this.Document.onClick = ScriptField.MakeScript(
             `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
@@ -612,7 +626,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @undoBatch
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
-        if (this._props.Document === Doc.ActiveDashboard) {
+        if (this.Document === Doc.ActiveDashboard) {
             e.stopPropagation();
             e.preventDefault();
             alert(
@@ -634,7 +648,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
                     if (de.complete.linkDocument) {
                         de.complete.linkDocument.layout_isSvg = true;
-                        this._props.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
+                        this.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
                     }
                 }
                 e.stopPropagation();
@@ -646,16 +660,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @undoBatch
     makeIntoPortal = () => {
-        const portalLink = this.allLinks.find(d => d.link_anchor_1 === this._props.Document && d.link_relationship === 'portal to:portal from');
+        const portalLink = this.allLinks.find(d => d.link_anchor_1 === this.Document && d.link_relationship === 'portal to:portal from');
         if (!portalLink) {
             DocUtils.MakeLink(
-                this._props.Document,
+                this.Document,
                 Docs.Create.FreeformDocument([], {
                     _width: NumCast(this.layoutDoc._width) + 10,
                     _height: Math.max(NumCast(this.layoutDoc._height), NumCast(this.layoutDoc._width) + 10),
                     _isLightbox: true,
                     _layout_fitWidth: true,
-                    title: StrCast(this._props.Document.title) + ' [Portal]',
+                    title: StrCast(this.Document.title) + ' [Portal]',
                 }),
                 { link_relationship: 'portal to:portal from' }
             );
@@ -686,7 +700,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (e && this.layoutDoc._layout_hideContextMenu && Doc.noviceMode) {
             e.preventDefault();
             e.stopPropagation();
-            //!this._props.isSelected(true) && SelectionManager.SelectView(this._props.DocumentView(), false);
+            //!this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false);
         }
         // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
         if (e) {
@@ -708,7 +722,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         if (e && !(e.nativeEvent as any).dash) {
             const onDisplay = () => {
-                if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this._props.isSelected() && SelectionManager.SelectView(this._props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
+                if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this._props.isSelected() && SelectionManager.SelectView(this.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
                 setTimeout(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY));
             };
             if (navigator.userAgent.includes('Macintosh')) {
@@ -719,14 +733,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             return;
         }
 
-        const customScripts = Cast(this._props.Document.contextMenuScripts, listSpec(ScriptField), []);
+        const customScripts = Cast(this.Document.contextMenuScripts, listSpec(ScriptField), []);
         StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
             cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' })
         );
         this._props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.Document, scriptContext: this._props.scriptContext }), icon: item.icon as IconProp }));
 
-        if (!this._props.Document.isFolder) {
-            const templateDoc = Cast(this._props.Document[StrCast(this._props.Document.layout_fieldKey)], Doc, null);
+        if (!this.Document.isFolder) {
+            const templateDoc = Cast(this.Document[StrCast(this.Document.layout_fieldKey)], Doc, null);
             const appearance = cm.findByDescription('Appearance...');
             const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
 
@@ -764,7 +778,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
 
                         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._props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
+                        !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) {
                         onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
@@ -786,15 +800,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const moreItems = more && 'subitems' in more ? more.subitems : [];
             if (!Doc.IsSystem(this.Document)) {
                 if (!Doc.noviceMode) {
-                    moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this._props.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' });
+                    moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' });
                     moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => (this.Document._chromeHidden = !this.Document._chromeHidden), icon: 'project-diagram' });
 
-                    if (Cast(Doc.GetProto(this._props.Document).data, listSpec(Doc))) {
-                        moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this._props.Document }).then(console.log), icon: 'caret-square-right' });
-                        moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this._props.Document), icon: 'caret-square-right' });
-                        moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this._props.Document), icon: 'caret-square-right' });
+                    if (Cast(Doc.GetProto(this.Document).data, listSpec(Doc))) {
+                        moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.Document }).then(console.log), icon: 'caret-square-right' });
+                        moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this.Document), icon: 'caret-square-right' });
+                        moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this.Document), icon: 'caret-square-right' });
                     }
-                    moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this._props.Document)), icon: 'fingerprint' });
+                    moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' });
                 }
             }
 
@@ -802,14 +816,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         const constantItems: ContextMenuProps[] = [];
         if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) {
-            constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this._props.Document) });
-            (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._props.DocumentView()), icon: 'users' });
-            if (this._props.removeDocument && Doc.ActiveDashboard !== this._props.Document) {
+            constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.Document) });
+            (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.DocumentView()), icon: 'users' });
+            if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) {
                 // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
                 constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
             }
         }
-        constantItems.push({ description: 'Show Metadata', event: () => this._props.addDocTab(this._props.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
+        constantItems.push({ description: 'Show Metadata', event: () => this._props.addDocTab(this.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
         cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' });
 
         const help = cm.findByDescription('Help...');
@@ -820,7 +834,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         let documentationDescription: string | undefined = undefined;
         let documentationLink: string | undefined = undefined;
-        switch (this._props.Document.type) {
+        switch (this.Document.type) {
             case DocumentType.COL:
                 documentationDescription = 'See collection documentation';
                 documentationLink = 'https://brown-dash.github.io/Dash-Documentation/views/';
@@ -874,7 +888,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
     rootSelected = () => this._rootSelected;
     panelHeight = () => this._props.PanelHeight() - this.headerMargin;
-    screenToLocalContent = () => this.ScreenToLocalBoxXf().translate(0, -this.headerMargin);
+    screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
     setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
@@ -912,7 +926,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     {...this._props}
                     fieldKey=""
                     pointerEvents={this.contentPointerEvents}
-                    docViewPath={this._props.viewPath}
                     setContentView={this.setContentView}
                     childFilters={this.childFilters}
                     PanelHeight={this.panelHeight}
@@ -931,7 +944,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     anchorPanelWidth = () => this._props.PanelWidth() || 1;
     anchorPanelHeight = () => this._props.PanelHeight() || 1;
-    anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    anchorStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
         // prettier-ignore
         switch (property.split(':')[0]) {
             case StyleProp.ShowTitle:     return '';
@@ -976,7 +989,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     {...this._props}
                     isContentActive={returnFalse}
                     Document={link}
-                    docViewPath={this._props.viewPath}
+                    containerViewPath={this._props.docViewPath}
                     PanelWidth={this.anchorPanelWidth}
                     PanelHeight={this.anchorPanelHeight}
                     dontRegisterView={false}
@@ -1066,7 +1079,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     };
 
-    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
+    captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
     @observable _changingTitleField = false;
     @observable _dropDownInnerWidth = 0;
     fieldsDropdown = (inputOptions: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
@@ -1266,10 +1279,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     }
     @computed get highlighting() {
-        return this._props.styleProvider?.(this.Document, this._props, StyleProp.Highlighting);
+        return this._props.styleProvider?.(this.Document, { ...this._props, fieldKey: '' }, StyleProp.Highlighting);
     }
     @computed get borderPath() {
-        return this._props.styleProvider?.(this.Document, this._props, StyleProp.BorderPath);
+        return this._props.styleProvider?.(this.Document, { ...this._props, fieldKey: '' }, StyleProp.BorderPath);
     }
     render() {
         TraceMobx();
@@ -1332,14 +1345,13 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     @computed public static get exploreMode() {
         return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
     }
-    @observable public docView: DocumentViewInternal | undefined | null = undefined;
-    @observable public textHtmlOverlay: Opt<string> = undefined;
-    @observable public textHtmlOverlayTime: Opt<number> = undefined;
+    @observable private _docViewInternal: DocumentViewInternal | undefined | null = undefined;
+    @observable private _htmlOverlayText: Opt<string> = undefined;
     @observable private _isHovering = false;
+    private _htmlOverlayEffect: Opt<Doc>;
 
-    public htmlOverlayEffect: Opt<Doc>;
     public get displayName() {
-        return 'DocumentView(' + this._props.Document?.title + ')';
+        return 'DocumentView(' + this.Document?.title + ')';
     } // this makes mobx trace() statements more descriptive
     public ContentRef = React.createRef<HTMLDivElement>();
     public ViewTimer: NodeJS.Timeout | undefined; // timer for res
@@ -1349,10 +1361,26 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         this.ViewTimer && clearTimeout(this.ViewTimer);
         this.layoutDoc._viewTransition = undefined;
     };
-    public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
-
-    public showContextMenu = (pageX: number, pageY: number) => this.docView?.onContextMenu(undefined, pageX, pageY);
-
+    playAnnotation = () => this._docViewInternal?.playAnnotation();
+    noOnClick = () => this._docViewInternal?.noOnClick();
+    makeIntoPortal = () => this._docViewInternal?.makeIntoPortal();
+    setToggleDetail = () => this._docViewInternal?.setToggleDetail();
+    onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY);
+    cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents();
+    public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource);
+
+    public showContextMenu = (pageX: number, pageY: number) => this._docViewInternal?.onContextMenu(undefined, pageX, pageY);
+
+    public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => {
+        this._htmlOverlayText = text;
+        this._htmlOverlayEffect = effect;
+    });
+    public setAnimateScaling = action((scale: number, time?: number) => {
+        if (this._docViewInternal) {
+            this._docViewInternal._animateScalingTo = scale;
+            this._docViewInternal._animateScaleTime = time;
+        }
+    });
     public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
         this.AnimEffectTimer && clearTimeout(this.AnimEffectTimer);
         this.Document[Animation] = presEffect;
@@ -1391,10 +1419,11 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         DocServer.GetRefField(docId).then(docx =>
             LightboxView.Instance.SetLightboxDoc(
                 (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection
-                Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, updateContentsScript: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId)
+                Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, onViewMounted: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId)
             )
         );
     }
+    toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle);
 
     get Document() {
         return this._props.Document;
@@ -1403,19 +1432,19 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         return this._props.renderDepth === 0;
     }
     get dataDoc() {
-        return this.docView?.dataDoc ?? this.Document;
+        return this._docViewInternal?.dataDoc ?? this.Document;
     }
     get ContentDiv() {
-        return this.docView?.ContentDiv;
+        return this._docViewInternal?.ContentDiv;
     }
     get ComponentView() {
-        return this.docView?._componentView;
+        return this._docViewInternal?._componentView;
     }
     get allLinks() {
-        return (this.docView?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
+        return (this._docViewInternal?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
     }
     get LayoutFieldKey() {
-        return this.docView?.LayoutFieldKey || 'layout';
+        return this._docViewInternal?.LayoutFieldKey || 'layout';
     }
     @computed get layout_fitWidth() {
         return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
@@ -1439,8 +1468,17 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     @computed get linkCountView() {
         return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
     }
+    /**
+     * path of DocumentViews terminating in the DocumentView that contains this DocumentView
+     */
+    @computed get containerViewPath() {
+        return this._props.containerViewPath;
+    }
+    /**
+     * path of DocumentViews terminating in this DocumentView
+     */
     @computed get docViewPath(): DocumentView[] {
-        return this._props.docViewPath ? [...this._props.docViewPath(), this] : [this];
+        return this.containerViewPath ? [...this.containerViewPath(), this] : [this];
     }
     @computed get layoutDoc() {
         return Doc.Layout(this.Document, this._props.LayoutTemplate?.());
@@ -1502,19 +1540,19 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         return this._props.CollectionFreeFormDocumentView?.();
     }
 
-    public toggleNativeDimensions = () => this.docView && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this._props.PanelWidth(), this._props.PanelHeight());
+    public toggleNativeDimensions = () => this._docViewInternal && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this._docViewInternal.NativeDimScaling, this._props.PanelWidth(), this._props.PanelHeight());
     public getBounds = () => {
-        if (!this.docView?.ContentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this._props.Document, Doc.UserDoc())) {
+        if (!this._docViewInternal?.ContentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
             return undefined;
         }
-        if (this.docView._componentView?.screenBounds?.()) {
-            return this.docView._componentView.screenBounds();
+        if (this._docViewInternal._componentView?.screenBounds?.()) {
+            return this._docViewInternal._componentView.screenBounds();
         }
-        const xf = this.docView.ScreenToLocalBoxXf().scale(this.nativeScaling).inverse();
+        const xf = this._docViewInternal._props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
 
-        if (this.docView._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
-            const docuBox = this.docView.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
+        if (this._docViewInternal._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+            const docuBox = this._docViewInternal.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
             if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
         }
         return { left, top, right, bottom };
@@ -1522,11 +1560,11 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
 
     public iconify(finished?: () => void, animateTime?: number) {
         this.ComponentView?.updateIcon?.();
-        const animTime = this.docView?._animateScaleTime;
-        runInAction(() => this.docView && animateTime !== undefined && (this.docView._animateScaleTime = animateTime));
+        const animTime = this._docViewInternal?._animateScaleTime;
+        runInAction(() => this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime));
         const finalFinished = action(() => {
             finished?.();
-            this.docView && (this.docView._animateScaleTime = animTime);
+            this._docViewInternal && (this._docViewInternal._animateScaleTime = animTime);
         });
         const layout_fieldKey = Cast(this.Document.layout_fieldKey, 'string', null);
         if (layout_fieldKey !== 'layout_icon') {
@@ -1541,12 +1579,12 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     }
     @undoBatch
     setCustomView = (custom: boolean, layout: string): void => {
-        Doc.setNativeView(this._props.Document);
-        custom && DocUtils.makeCustomViewClicked(this._props.Document, Docs.Create.StackingDocument, layout, undefined);
+        Doc.setNativeView(this.Document);
+        custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined);
     };
 
     switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
-        runInAction(() => this.docView && (this.docView._animateScalingTo = 0.1)); // shrink doc
+        runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc
         setTimeout(
             action(() => {
                 if (useExistingLayout && custom && this.Document['layout_' + view]) {
@@ -1554,16 +1592,16 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                 } else {
                     this.setCustomView(custom, view);
                 }
-                this.docView && (this.docView._animateScalingTo = 1); // expand it
+                this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // expand it
                 setTimeout(
                     action(() => {
-                        this.docView && (this.docView._animateScalingTo = 0);
+                        this._docViewInternal && (this._docViewInternal._animateScalingTo = 0);
                         finished?.();
                     }),
-                    this.docView ? Math.max(0, this.docView.animateScaleTime - 10) : 0
+                    this._docViewInternal ? Math.max(0, this._docViewInternal.animateScaleTime - 10) : 0
                 );
             }),
-            this.docView ? Math.max(0, this.docView?.animateScaleTime - 10) : 0
+            this._docViewInternal ? Math.max(0, this._docViewInternal?.animateScaleTime - 10) : 0
         );
     };
 
@@ -1606,29 +1644,21 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
 
     componentDidMount() {
         runInAction(() => this.Document[DocViews].add(this));
-        this._disposers.updateContentsScript = reaction(() => ScriptCast(this.Document.updateContentsScript)?.script?.run({ this: this.Document }).result, emptyFunction);
-        this._disposers.height = reaction(
-            // increase max auto height if document has been resized to be greater than current max
-            () => NumCast(this.layoutDoc._height),
-            action(height => {
-                const docMax = NumCast(this.layoutDoc.layout_maxAutoHeight);
-                if (docMax && docMax < height) this.layoutDoc.layout_maxAutoHeight = height;
-            })
-        );
-        !BoolCast(this._props.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(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);
     }
 
     componentWillUnmount() {
-        this.Document[DocViews].delete(this);
+        runInAction(() => this.Document[DocViews].delete(this));
         Object.values(this._disposers).forEach(disposer => disposer?.());
-        !BoolCast(this._props.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
+        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
     }
     // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed.
     // unfortunately, CSS can't transition animate any properties for something that is display 'none'.
     // so we need to first activate the div, then, after a render timeout, start the opacity transition.
     @observable enableHtmlOverlayTransitions: boolean = false;
     @computed get htmlOverlay() {
-        const effect = StrCast(this.htmlOverlayEffect?.presentation_effect, StrCast(this.htmlOverlayEffect?.followLinkAnimEffect));
+        const effect = StrCast(this._htmlOverlayEffect?.presentation_effect, StrCast(this._htmlOverlayEffect?.followLinkAnimEffect));
         return (
             <div
                 className="documentView-htmlOverlay"
@@ -1638,13 +1668,13 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                         setTimeout(action(() => (this.enableHtmlOverlayTransitions = val)));
                     }
                 }}
-                style={{ display: !this.textHtmlOverlay ? 'none' : undefined }}>
+                style={{ display: !this._htmlOverlayText ? 'none' : undefined }}>
                 <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this.enableHtmlOverlayTransitions ? 0.9 : 0 }}>
                     {DocumentViewInternal.AnimationEffect(
                         <div className="webBox-textHighlight">
-                            <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this.textHtmlOverlay)} />
+                            <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
                         </div>,
-                        { ...(this.htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc,
+                        { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc,
                         this.Document
                     )}
                 </div>
@@ -1663,7 +1693,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
 
         return (
             <div className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
-                {!this._props.Document || !this._props.PanelWidth() ? null : (
+                {!this.Document || !this._props.PanelWidth() ? null : (
                     <div
                         className="contentFittingDocumentView-previewDoc"
                         ref={this.ContentRef}
@@ -1676,7 +1706,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                         <DocumentViewInternal
                             {...this._props}
                             DocumentView={this.selfView}
-                            viewPath={this.docViewPathFunc}
+                            docViewPath={this.docViewPathFunc}
                             PanelWidth={this.PanelWidth}
                             PanelHeight={this.PanelHeight}
                             NativeWidth={this.NativeWidth}
@@ -1687,7 +1717,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                             layout_fitWidth={this.layout_fitWidthFunc}
                             ScreenToLocalTransform={this.screenToContentsTransform}
                             focus={this._props.focus || emptyFunction}
-                            ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))}
+                            ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))}
                         />
                         {this.htmlOverlay}
                         {this.infoUI}
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index ff92c701f..ebad257ac 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -27,7 +27,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     componentDidMount() {
         this._props.setContentView?.(this);
-        if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()))) {
+        if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.docViewPath))) {
             this._props.select(false);
 
             this._ref.current!.mathField.focus();
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 008f10f26..b456807d7 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -6,23 +6,18 @@ import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { ScriptField } from '../../../fields/ScriptField';
 import { WebField } from '../../../fields/URLField';
-import { DocumentViewSharedProps } from './DocumentView';
+import { DocumentView, DocumentViewInternalSharedProps, DocumentViewSharedProps } from './DocumentView';
 
 //
 // these properties get assigned through the render() method of the DocumentView when it creates this node.
 // However, that only happens because the properties are "defined" in the markup for the field view.
 // See the LayoutString method on each field view :   ImageBox, FormattedTextBox, etc.
 //
-export interface FieldViewProps extends DocumentViewSharedProps {
+export interface FieldViewProps extends DocumentViewSharedProps, DocumentViewInternalSharedProps {
     // FieldView specific props that are not part of DocumentView props
     fieldKey: string;
 
-    select: (isCtrlPressed: boolean) => void;
-    isContentActive: (outsideReaction?: boolean) => boolean | undefined;
-    isDocumentActive?: () => boolean | undefined;
-    isSelected: () => boolean;
     setHeight?: (height: number) => void;
-    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps
     onBrowseClick?: () => ScriptField | undefined;
     onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
     pointerEvents?: () => Opt<string>;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index a5853499f..4483d12ce 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -158,7 +158,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     @undoBatch
     setNativeSize = action(() => {
-        const scaling = (this._props.DocumentView?.().screenToViewTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
+        const scaling = (this.DocumentView?.().screenToViewTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
         const nscale = NumCast(this._props.PanelWidth()) / scaling;
         const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
         this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
@@ -368,7 +368,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
     screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -444,7 +444,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     {this.content}
                 </CollectionFreeFormView>
                 {this.annotationLayer}
-                {!this._mainCont.current || !this._annotationLayer.current ? null : (
+                {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
                     <MarqueeAnnotator
                         Document={this.Document}
                         ref={this._marqueeref}
@@ -452,7 +452,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         annotationLayerScrollTop={0}
                         scaling={returnOne}
                         annotationLayerScaling={this._props.NativeDimScaling}
-                        docView={this._props.DocumentView!}
+                        docView={this.DocumentView}
                         addDocument={this.addDocument}
                         finishMarquee={this.finishMarquee}
                         savedAnnotations={this.savedAnnotations}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 7bc9d3f85..b2aab2422 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -61,33 +61,11 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
     };
 
     render() {
-        const props: FieldViewProps = {
-            Document: this._props.doc,
-            childFilters: returnEmptyFilter,
-            childFiltersByRanges: returnEmptyFilter,
-            searchFilterDocs: returnEmptyDoclist,
-            styleProvider: DefaultStyleProvider,
-            docViewPath: returnEmptyDoclist,
-            fieldKey: this._props.keyName,
-            isSelected: returnFalse,
-            setHeight: returnFalse,
-            select: emptyFunction,
-            bringToFront: emptyFunction,
-            renderDepth: 1,
-            isContentActive: returnFalse,
-            whenChildContentsActiveChanged: emptyFunction,
-            ScreenToLocalTransform: Transform.Identity,
-            focus: emptyFunction,
-            PanelWidth: this._props.PanelWidth,
-            PanelHeight: this._props.PanelHeight,
-            addDocTab: returnFalse,
-            pinToPres: returnZero,
-        };
         // let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
         let protoCount = 0;
-        let doc: Doc | undefined = props.Document;
+        let doc = this._props.doc;
         while (doc) {
-            if (Object.keys(doc).includes(props.fieldKey)) {
+            if (Object.keys(doc).includes(this._props.keyName)) {
                 break;
             }
             protoCount++;
@@ -106,17 +84,17 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
                             style={hover}
                             className="keyValuePair-td-key-delete"
                             onClick={undoBatch(() => {
-                                if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
-                                    delete props.Document[props.fieldKey];
-                                } else delete DocCast(props.Document.proto)?.[props.fieldKey];
+                                if (Object.keys(this._props.doc).indexOf(this._props.keyName) !== -1) {
+                                    delete this._props.doc[this._props.keyName];
+                                } else delete DocCast(this._props.doc.proto)?.[this._props.keyName];
                             })}>
                             X
                         </button>
                         <input className="keyValuePair-td-key-check" type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} />
-                        <Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === props.fieldKey)?.[1].description ?? ''}>
-                            <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (props.fieldKey.match(/_/g)?.length || 0), color: keyStyle }}>
+                        <Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === this._props.keyName)?.[1].description ?? ''}>
+                            <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (this._props.keyName.match(/_/g)?.length || 0), color: keyStyle }}>
                                 {'('.repeat(parenCount)}
-                                {props.fieldKey}
+                                {this._props.keyName}
                                 {')'.repeat(parenCount)}
                             </div>
                         </Tooltip>
@@ -124,7 +102,32 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
                 </td>
                 <td className="keyValuePair-td-value" style={{ width: `${100 - this._props.keyWidth}%` }} onContextMenu={this.onContextMenu}>
                     <div className="keyValuePair-td-value-container">
-                        <EditableView contents={undefined} fieldContents={props} GetValue={() => Field.toKeyValueString(props.Document, props.fieldKey)} SetValue={(value: string) => KeyValueBox.SetField(props.Document, props.fieldKey, value)} />
+                        <EditableView
+                            contents={undefined}
+                            fieldContents={{
+                                Document: this._props.doc,
+                                childFilters: returnEmptyFilter,
+                                childFiltersByRanges: returnEmptyFilter,
+                                searchFilterDocs: returnEmptyDoclist,
+                                styleProvider: DefaultStyleProvider,
+                                fieldKey: this._props.keyName,
+                                isSelected: returnFalse,
+                                setHeight: returnFalse,
+                                select: emptyFunction,
+                                bringToFront: emptyFunction,
+                                renderDepth: 1,
+                                isContentActive: returnFalse,
+                                whenChildContentsActiveChanged: emptyFunction,
+                                ScreenToLocalTransform: Transform.Identity,
+                                focus: emptyFunction,
+                                PanelWidth: this._props.PanelWidth,
+                                PanelHeight: this._props.PanelHeight,
+                                addDocTab: returnFalse,
+                                pinToPres: returnZero,
+                            }}
+                            GetValue={() => Field.toKeyValueString(this._props.doc, this._props.keyName)}
+                            SetValue={(value: string) => KeyValueBox.SetField(this._props.doc, this._props.keyName, value)}
+                        />
                     </div>
                 </td>
             </tr>
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index b86ba72a0..864c1955b 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -7,13 +7,13 @@ import { TraceMobx } from '../../../fields/util';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
 import { SelectionManager } from '../../util/SelectionManager';
-import { ViewBoxBaseComponent } from '../DocComponent';
+import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LinkAnchorBox.scss';
 import { LinkInfo } from './LinkDocPreview';
 const { default: { MEDIUM_GRAY }, } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
-export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
+export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps & ViewBoxBaseProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LinkAnchorBox, fieldKey);
     }
@@ -33,12 +33,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get linkSource() {
-        return this._props.docViewPath()[this._props.docViewPath().length - 2].Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource);
+        return this.containerViewPath?.().lastElement().Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource);
     }
 
     onPointerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
-            if (doubleTap) LinkFollower.FollowLink(this.Document, this.linkSource, false);
+        const linkSource = this.linkSource;
+        linkSource && setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
+            if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false);
             else this._props.select(false);
         });
     };
@@ -86,7 +87,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 className={`linkAnchorBox-cont${small ? '-small' : ''}`}
                 onPointerEnter={e =>
                     LinkInfo.SetLinkInfo({
-                        docProps: this._props,
+                        DocumentView: this.DocumentView,
+                        styleProvider: this._props.styleProvider,
                         linkSrc: this.linkSource,
                         linkDoc: this.Document,
                         showHeader: true,
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 7f1d41547..4221f464d 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -29,12 +29,12 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get anchor1() {
         const anchor1 = DocCast(this.dataDoc.link_anchor_1);
         const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1;
-        return DocumentManager.Instance.getDocumentView(anchor_1, this._props.docViewPath()[this._props.docViewPath().length - 2]); // this._props.docViewPath().lastElement());
+        return DocumentManager.Instance.getDocumentView(anchor_1, this.containerViewPath?.().lastElement());
     }
     @computed get anchor2() {
         const anchor2 = DocCast(this.dataDoc.link_anchor_2);
         const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2;
-        return DocumentManager.Instance.getDocumentView(anchor_2, this._props.docViewPath()[this._props.docViewPath().length - 2]); // this._props.docViewPath().lastElement());
+        return DocumentManager.Instance.getDocumentView(anchor_2, this.containerViewPath?.().lastElement());
     }
     screenBounds = () => {
         if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) {
@@ -66,7 +66,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     const a = (this.anchor1 ?? this.anchor2)!;
                     const b = (this.anchor2 ?? this.anchor1)!;
 
-                    const parxf = this._props.docViewPath()[this._props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
+                    const parxf = this.containerViewPath?.().lastElement().ComponentView as CollectionFreeFormView;
                     const this_xf = parxf?.screenToFreeformContentsXf ?? Transform.Identity; //this.ScreenToLocalTransform();
                     const a_invXf = a.screenToViewTransform().inverse();
                     const b_invXf = b.screenToViewTransform().inverse();
@@ -161,12 +161,11 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return (
             <div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
                 <ComparisonBox
-                    {...this._props}
+                    {...this._props} //
                     fieldKey="link_anchor"
                     setHeight={emptyFunction}
                     dontRegisterView={true}
                     renderDepth={this._props.renderDepth + 1}
-                    isContentActive={this._props.isContentActive}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     moveDocument={returnFalse}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index ea23ecbea..ed4878d9d 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -18,8 +18,9 @@ import { SearchUtil } from '../../util/SearchUtil';
 import { SettingsManager } from '../../util/SettingsManager';
 import { Transform } from '../../util/Transform';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
+import { DocumentView, DocumentViewSharedProps, OpenWhere, StyleProviderFunc } from './DocumentView';
 import './LinkDocPreview.scss';
+import { FieldViewProps } from './FieldView';
 
 export class LinkInfo {
     private static _instance: Opt<LinkInfo>;
@@ -43,7 +44,8 @@ export class LinkInfo {
 interface LinkDocPreviewProps {
     linkDoc?: Doc;
     linkSrc?: Doc;
-    docProps: DocumentViewSharedProps;
+    DocumentView?: () => DocumentView;
+    styleProvider?: StyleProviderFunc;
     location: number[];
     hrefs?: string[];
     showHeader?: boolean;
@@ -152,7 +154,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
             action(() => {
                 LinkManager.currentLink = this._linkDoc;
                 LinkManager.currentLinkAnchor = this._linkSrc;
-                this._props.docProps.DocumentView?.().select(false);
+                this._props.DocumentView?.().select(false);
                 if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
                     SettingsManager.Instance.propertiesWidth = 250;
                 }
@@ -269,8 +271,8 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                             }}
                             Document={this._targetDoc!}
                             moveDocument={returnFalse}
-                            styleProvider={this._props.docProps?.styleProvider}
-                            docViewPath={returnEmptyDoclist}
+                            styleProvider={this._props.styleProvider}
+                            containerViewPath={returnEmptyDoclist}
                             ScreenToLocalTransform={Transform.Identity}
                             isDocumentActive={returnFalse}
                             isContentActive={returnFalse}
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 3575b21e4..56fb157da 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -379,11 +379,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         });
 
         const targetCreator = (annotationOn: Doc | undefined) => {
-            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
             FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: e => {
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index 34e237007..fc5b4dd18 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -18,10 +18,10 @@ export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     get mapBoxView() {
-        return this._props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as MapBox;
+        return this.DocumentView?.()?.containerViewPath?.().lastElement()?.ComponentView as MapBox;
     }
     get mapBox() {
-        return this._props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
+        return this.DocumentView?.().containerViewPath?.().lastElement()?.Document;
     }
 
     render() {
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index ea8496c99..e079f7457 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -228,11 +228,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         });
 
         const targetCreator = (annotationOn: Doc | undefined) => {
-            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
             FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: e => {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 959d5d88d..71c1b6a4c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -102,7 +102,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         Doc.GetProto(region).followLinkToggle = true;
         this.addDocument(region);
 
-        const docViewContent = this._props.docViewPath().lastElement().ContentDiv!;
+        const docViewContent = this.DocumentView?.().ContentDiv!;
         const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
         newDiv.style.width = NumCast(this.layoutDoc._width).toString();
         newDiv.style.height = NumCast(this.layoutDoc._height).toString();
@@ -163,7 +163,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     updateIcon = () => {
         // currently we render pdf icons as text labels
-        const docViewContent = this._props.docViewPath().lastElement().ContentDiv!;
+        const docViewContent = this.DocumentView?.().ContentDiv!;
         const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime();
         this._pdfViewer?._mainCont.current &&
             CollectionFreeFormView.UpdateIcon(
@@ -304,7 +304,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     setPdfViewer = (pdfViewer: PDFViewer) => {
         this._pdfViewer = pdfViewer;
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         if (this._initialScrollTarget && docView) {
             this.focus(this._initialScrollTarget, { instant: true });
             this._initialScrollTarget = undefined;
@@ -525,7 +525,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     removeDocument={this.removeDocument}
                 />
             ) : (
-                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this._props.DocumentView?.()!, false), true)}>
+                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
                     <ComponentTag
                         {...this._props}
                         setContentView={emptyFunction} // override setContentView to do nothing
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index fb42286af..1913ab032 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -21,7 +21,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { StyleProp } from '../StyleProvider';
@@ -44,7 +44,7 @@ import './VideoBox.scss';
  */
 
 @observer
-export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(VideoBox, fieldKey);
     }
@@ -608,7 +608,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // removes from currently playing display
     @action
     removeCurrentlyPlaying = () => {
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
             const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
             index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -617,7 +617,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     // adds doc to currently playing display
     @action
     addCurrentlyPlaying = () => {
-        const docView = this._props.DocumentView?.();
+        const docView = this.DocumentView?.();
         if (!CollectionStackedTimeline.CurrentlyPlaying) {
             CollectionStackedTimeline.CurrentlyPlaying = [];
         }
@@ -749,7 +749,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     // starts marquee selection
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -941,7 +941,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 style={{
                     pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
                     borderRadius,
-                    overflow: this._props.docViewPath?.().slice(-1)[0].layout_fitWidth ? 'auto' : undefined,
+                    overflow: this.DocumentView?.().layout_fitWidth ? 'auto' : undefined,
                 }}>
                 <div className="videoBox-viewer" onPointerDown={this.marqueeDown}>
                     <div
@@ -978,7 +978,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         </CollectionFreeFormView>
                     </div>
                     {this.annotationLayer}
-                    {!this._mainCont.current || !this._annotationLayer.current ? null : (
+                    {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
                         <MarqueeAnnotator
                             ref={this._marqueeref}
                             Document={this.Document}
@@ -986,7 +986,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                             annotationLayerScrollTop={0}
                             scaling={returnOne}
                             annotationLayerScaling={this._props.NativeDimScaling}
-                            docView={this._props.DocumentView!}
+                            docView={this.DocumentView}
                             containerOffset={this.marqueeOffset}
                             addDocument={this.addDocWithTimecode}
                             finishMarquee={this.finishMarquee}
@@ -1004,7 +1004,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
 
     @computed get UIButtons() {
-        const bounds = this._props.docViewPath().lastElement().getBounds();
+        const bounds = this.DocumentView?.().getBounds();
         const width = (bounds?.right || 0) - (bounds?.left || 0);
         const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
         return (
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c3be2b390..86709e4d7 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -33,7 +33,7 @@ import { Annotation } from '../pdf/Annotation';
 import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { StyleProp } from '../StyleProvider';
-import { DocComponentView, DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from './DocumentView';
+import { DocComponentView, DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { LinkInfo } from './LinkDocPreview';
 import { PinProps, PresBox } from './trails';
@@ -250,7 +250,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
         if (this._mainCont.current && selRange) {
-            if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToContentsTransform().RotateDeg)}deg)`;
+            if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this.DocumentView!().screenToContentsTransform().RotateDeg)}deg)`;
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
@@ -274,7 +274,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         // clear selection
         this._textAnnotationCreator = undefined;
-        if (sel.empty) sel.empty(); // Chrome
+        if (sel.empty)
+            sel.empty(); // Chrome
         else if (sel.removeAllRanges) sel.removeAllRanges(); // Firefox
         return this._savedAnnotations;
     };
@@ -297,8 +298,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     getView = (doc: Doc, options: DocFocusOptions) => {
-        if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this._props.DocumentView?.()));
-        if (this.Document.layout_fieldKey === 'layout_icon') this._props.DocumentView?.().iconify();
+        if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this.DocumentView?.()));
+        if (this.Document.layout_fieldKey === 'layout_icon') this.DocumentView?.().iconify();
         const webUrl = WebCast(doc.config_data)?.url;
         if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href);
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false);
@@ -343,7 +344,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     iframeUp = (e: PointerEvent) => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
         this._textAnnotationCreator = undefined;
-        this._props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
+        this.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
         if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
             const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
             const scale = (this._props.NativeDimScaling?.() || 1) * mainContBounds.scale;
@@ -364,7 +365,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         e.stopPropagation();
         const sel = window.getSelection();
         this._textAnnotationCreator = undefined;
-        if (sel?.empty) sel.empty(); // Chrome
+        if (sel?.empty)
+            sel.empty(); // Chrome
         else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
         // bcz: NEED TO unrotate e.clientX and e.clientY
         const word = getWordAtPoint(e.target, e.clientX, e.clientY);
@@ -556,7 +558,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             clearStyleSheetRules(WebBox.webStyleSheet);
             this._scrollTimer = undefined;
             const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
-            if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()))) {
+            if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.docViewPath))) {
                 this.layoutDoc.thumb = undefined;
                 this.layoutDoc.thumbScrollTop = undefined;
                 this.layoutDoc.thumbNativeWidth = undefined;
@@ -723,10 +725,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     onMarqueeDown = (e: React.PointerEvent) => {
         const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
         this._textAnnotationCreator = undefined;
-        if (sel?.empty) sel.empty(); // Chrome
+        if (sel?.empty)
+            sel.empty(); // Chrome
         else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
         this.marqueeing = [e.clientX, e.clientY];
-        if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -750,7 +753,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.marqueeing = undefined;
         this._textAnnotationCreator = undefined;
         const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
-        if (sel?.empty) sel.empty(); // Chrome
+        if (sel?.empty)
+            sel.empty(); // Chrome
         else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
         this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.Document);
         if (x !== undefined && y !== undefined) {
@@ -762,7 +766,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 setTimeout(() => {
                     // if menu comes up right away, the down event can still be active causing a menu item to be selected
                     this.specificContextMenu(undefined as any);
-                    this._props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
+                    this.DocumentView?.().onContextMenu(undefined, x, y);
                 });
             }
         }
@@ -908,7 +912,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     });
     @action
     onZoomWheel = (e: React.WheelEvent) => {
-        if (this._props.isContentActive(true)) {
+        if (this._props.isContentActive()) {
             e.stopPropagation();
         }
     };
@@ -1065,7 +1069,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
     opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [Utils.OpaqueBackgroundFilter])];
-    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc)) return 'none';
         }
@@ -1099,7 +1103,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     onContextMenu={this.specificContextMenu}>
                     {this.webpage}
                 </div>
-                {!this._mainCont.current || !this._annotationLayer.current ? null : (
+                {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
                     <div style={{ position: 'absolute', height: '100%', width: '100%', pointerEvents: this.marqueeing ? 'all' : 'none' }}>
                         <MarqueeAnnotator
                             ref={this._marqueeref}
@@ -1109,7 +1113,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             annotationLayerScrollTop={0}
                             scaling={this._props.NativeDimScaling}
                             addDocument={this.addDocumentWrapper}
-                            docView={this._props.DocumentView!}
+                            docView={this.DocumentView}
                             finishMarquee={this.finishMarquee}
                             savedAnnotations={this.savedAnnotationsCreator}
                             selectionText={this.selectionText}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 6a2cd359c..cc58c2071 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -6,7 +6,7 @@ import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { NumCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnFalse, Utils } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { Transform } from '../../../util/Transform';
@@ -207,7 +207,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
                     isDocumentActive={returnFalse}
                     isContentActive={this.isContentActive}
                     styleProvider={this._textBox._props.styleProvider}
-                    docViewPath={this._textBox._props.docViewPath}
+                    containerViewPath={this._textBox.docViewPathFunc ?? returnEmptyDoclist}
                     ScreenToLocalTransform={this.getDocTransform}
                     addDocTab={this._textBox._props.addDocTab}
                     pinToPres={returnFalse}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 555b752f0..3f2ab0ec5 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -148,7 +148,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     }
 
     createPivotForField = (e: React.MouseEvent) => {
-        let container = this._props.tbox._props.DocumentView?.()._props.docViewPath().lastElement();
+        let container = this._props.tbox.containerViewPath?.().lastElement();
         if (container) {
             const embedding = Doc.MakeEmbedding(container.Document);
             embedding._type_collection = CollectionViewType.Time;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ae26f170b..6a6cec094 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -42,7 +42,7 @@ import { CollectionStackingView } from '../../collections/CollectionStackingView
 import { CollectionTreeView } from '../../collections/CollectionTreeView';
 import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
@@ -71,7 +71,7 @@ import { SummaryView } from './SummaryView';
 
 export interface FormattedTextBoxProps {}
 @observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & FormattedTextBoxProps>() {
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
@@ -123,7 +123,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     }
 
     @computed get noSidebar() {
-        return this._props.docViewPath().lastElement()?._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar;
+        return this.DocumentView?.()._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar;
     }
     @computed get layout_sidebarWidthPercent() {
         return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
@@ -310,7 +310,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 return target;
             };
 
-            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this._props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
+            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.DocumentView?.()!, () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
         });
         const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
         this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
@@ -1410,7 +1410,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             (this._editorView as any).TextView = this;
         }
 
-        const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()));
+        const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.docViewPath));
         if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
             const selLoadChar = FormattedTextBox.SelectOnLoadChar;
             FormattedTextBox.SelectOnLoad = undefined;
@@ -1531,7 +1531,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
         if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
         if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
-        else if (this._props.isContentActive(true)) {
+        else if (this._props.isContentActive()) {
             const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
             let xpos = pcords?.pos || 0;
             while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
@@ -1764,7 +1764,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 return toNum(height) + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
             };
             const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
-            const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.layout_maxAutoHeight, proseHeight), proseHeight);
+            const scrollHeight = this.ProseRef && proseHeight;
             if (this._props.setHeight && scrollHeight && !this._props.dontRegisterView) {
                 // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
                 const setScrollHeight = () => (this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
@@ -1852,7 +1852,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     setHeight={this.setSidebarHeight}
                 />
             ) : (
-                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this._props.DocumentView?.()!, false), true)}>
+                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
                     <ComponentTag
                         {...this._props}
                         ref={this._sidebarTagRef as any}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index be8736525..ce17af6ca 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -134,7 +134,8 @@ export class FormattedTextBoxComment {
             //nbef &&
             naft &&
                 LinkInfo.SetLinkInfo({
-                    docProps: textBox.props,
+                    DocumentView: textBox.DocumentView,
+                    styleProvider: textBox._props.styleProvider,
                     linkSrc: textBox.Document,
                     linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined,
                     location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 6fa64a765..645ac08e1 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -294,13 +294,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         listItemDoc.presentation_effect = this.activeItem.presBulletEffect;
                         listItemDoc.presentation_transition = 500;
                         targetView?.setAnimEffect(listItemDoc, 500);
-                        if (targetView?.docView && this.activeItem.presBulletExpand) {
-                            targetView.docView._animateScalingTo = 1.2;
-                            targetView.docView._animateScaleTime = 400;
-                            Doc.AddUnHighlightWatcher(() => {
-                                targetView.docView!._animateScaleTime = undefined;
-                                targetView!.docView!._animateScalingTo = 0;
-                            });
+                        if (targetView && this.activeItem.presBulletExpand) {
+                            targetView.setAnimateScaling(1.2, 400);
+                            Doc.AddUnHighlightWatcher(() => targetView?.setAnimateScaling(0, undefined));
                         }
                         listItemDoc.opacity = undefined;
                         this.activeItem.presentation_indexed = presIndexed + 1;
@@ -2622,7 +2618,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 ScreenToLocalTransform={this.getTransform}
                                 AddToMap={this.AddToMap}
                                 RemFromMap={this.RemFromMap}
-                                hierarchyIndex={emptyPath}
+                                hierarchyIndex={emptyPath as any as number[]}
                             />
                         ) : null}
                     </div>
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 4945d66c8..8cf01b9de 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -19,7 +19,7 @@ import { TreeView } from '../../collections/TreeView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
-import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
 import { StyleProp } from '../../StyleProvider';
 import { PresBox } from './PresBox';
@@ -50,7 +50,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     // the presentation view that renders this slide
     @computed get presBoxView() {
-        return this._props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as PresBox;
+        return this.containerViewPath?.().lastElement()?.ComponentView as PresBox;
     }
 
     // the presentation view document that renders this slide
@@ -97,7 +97,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     presExpandDocumentClick = () => (this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton);
     embedHeight = () => this.collapsedHeight + this.expandViewHeight;
     embedWidth = () => this._props.PanelWidth() / 2;
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         return property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property);
     };
     /**
@@ -116,7 +116,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     hideLinkButton={true}
                     ScreenToLocalTransform={Transform.Identity}
                     renderDepth={this._props.renderDepth + 1}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     childFilters={this._props.childFilters}
                     childFiltersByRanges={this._props.childFiltersByRanges}
                     searchFilterDocs={this._props.searchFilterDocs}
@@ -196,7 +196,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const dragArray = this.presBoxView?._dragArray ?? [];
         const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []);
         if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.slideDoc);
-        dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this._props.DocumentView?.()?._props.treeViewDoc;
+        dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this.DocumentView?.()?._props.treeViewDoc;
         dragData.moveDocument = this._props.moveDocument;
         const dragItem: HTMLElement[] = [];
         const classesToRestore = new Map<HTMLElement, string>();
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 4a7e35c16..89e5944f2 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -17,7 +17,7 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
-import { DocFocusOptions, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { LinkInfo } from '../nodes/LinkDocPreview';
 import { ObservableReactComponent } from '../ObservableReactComponent';
@@ -30,7 +30,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
 
 //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
 // The workerSrc property shall be specified.
-Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.0.269/build/pdf.worker.mjs';
+Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.0.379/build/pdf.worker.mjs';
 
 interface IViewerProps extends FieldViewProps {
     Document: Doc;
@@ -364,10 +364,10 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         this._downX = e.clientX;
         this._downY = e.clientY;
         if ((this._props.Document._freeform_scale || 1) !== 1) return;
-        if ((e.button !== 0 || e.altKey) && this._props.isContentActive(true)) {
+        if ((e.button !== 0 || e.altKey) && this._props.isContentActive()) {
             this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Document);
         }
-        if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![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]);
@@ -465,7 +465,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
     @action
     onZoomWheel = (e: React.WheelEvent) => {
-        if (this._props.isContentActive(true)) {
+        if (this._props.isContentActive()) {
             e.stopPropagation();
             if (e.ctrlKey) {
                 const curScale = Number(this._pdfViewer.currentScaleValue);
@@ -497,7 +497,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     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])];
-    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+    childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             const docProps = testDocProps(props) ? props : undefined;
             if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 575d5849e..188b2eba4 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -102,14 +102,12 @@ export class TopBar extends React.Component {
                     Document={selDoc}
                     fieldKey="data"
                     dropAction="embed"
-                    setHeight={returnFalse}
                     styleProvider={DefaultStyleProvider}
                     bringToFront={emptyFunction}
                     select={emptyFunction}
                     isContentActive={returnTrue}
                     isAnyChildContentActive={returnFalse}
                     isSelected={returnFalse}
-                    docViewPath={returnEmptyDoclist}
                     moveDocument={returnFalse}
                     addDocument={returnFalse}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index a1ce55314..9d68ea731 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -396,7 +396,7 @@ export class MobileInterface extends React.Component {
                     isContentActive={emptyFunction}
                     focus={emptyFunction}
                     styleProvider={this.whitebackground}
-                    docViewPath={returnEmptyDoclist}
+                    containerViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     childFilters={returnEmptyFilter}
diff --git a/src/server/DashStats.ts b/src/server/DashStats.ts
index b6164832f..a9e6af67c 100644
--- a/src/server/DashStats.ts
+++ b/src/server/DashStats.ts
@@ -15,7 +15,8 @@ export namespace DashStats {
     export const SAMPLING_INTERVAL = 1000; // in milliseconds (ms) - Time interval to update the frontend.
     export const RATE_INTERVAL = 10; // in seconds (s) - Used to calculate rate
 
-    const statsCSVFilename = './src/server/stats/userLoginStats.csv';
+    const statsCSVDirectory = './src/server/stats/';
+    const statsCSVFilename = statsCSVDirectory + 'userLoginStats.csv';
     const columns = ['USERNAME', 'ACTION', 'TIME'];
 
     /**
@@ -153,6 +154,7 @@ export namespace DashStats {
                 TIME: currentDate.toISOString(),
             };
 
+            if (!fs.existsSync(statsCSVDirectory)) fs.mkdirSync(statsCSVDirectory);
             let statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' });
             statsFile.write(convertToCSV(toWrite));
             statsFile.end();
-- 
cgit v1.2.3-70-g09d2


From c12659816adf68bb0bb931e97096afa82bfbe769 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 18 Jan 2024 17:46:40 -0500
Subject: fix so that freeformviews don't break when they have annotation layer
 children.

---
 .../CollectionFreeFormPannableContents.tsx                 |  6 +++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx          | 14 +++-----------
 2 files changed, 8 insertions(+), 12 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index ec8416303..69cbae86f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -1,4 +1,4 @@
-import { computed } from 'mobx';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
@@ -18,6 +18,10 @@ export interface CollectionFreeFormPannableContentsProps {
 
 @observer
 export class CollectionFreeFormPannableContents extends React.Component<CollectionFreeFormPannableContentsProps> {
+    constructor(props: CollectionFreeFormPannableContentsProps) {
+        super(props);
+        makeObservable(this);
+    }
     @computed get presPaths() {
         return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.Document) : null;
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a8f22aaae..c0104d0f8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1751,13 +1751,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
     });
 
-    // if a freeform view has any children, then the children will likely consist of a single child
-    // which will be a DocumentView.  In this sitation, this freeform views acts as an annotation overlay for
-    // the underlying DocumentView and will pan and scoll with the underlying Documen tView.
-    @computed get underlayViews() {
-        return this._props.children ? this._props.children : [];
-    }
-
     @computed get placeholder() {
         return (
             <div className="collectionfreeformview-placeholder" style={{ background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) }}>
@@ -1790,7 +1783,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             </div>
         );
     }
-    @computed get pannableContents() {
+    get pannableContents() {
         this.incrementalRender();
         return (
             <CollectionFreeFormPannableContents
@@ -1800,14 +1793,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 transform={this.PanZoomCenterXf}
                 transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))}
                 viewDefDivClick={this._props.viewDefDivClick}>
-                {this.underlayViews}
+                {this.props.children ?? null} {/* most likely case of children is document content that's being annoated: eg., an image */}
                 {this.contentViews}
                 <CollectionFreeFormRemoteCursors {...this._props} key="remoteCursors" />
             </CollectionFreeFormPannableContents>
         );
     }
-    @computed get marqueeView() {
-        TraceMobx();
+    get marqueeView() {
         return (
             <MarqueeView
                 {...this._props}
-- 
cgit v1.2.3-70-g09d2


From 533051fbf278fb7109d898c9299f8e724902c138 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 22 Jan 2024 13:34:34 -0500
Subject: cleaned up viewPath methods and LightboxView's containment api

---
 src/client/util/DocumentManager.ts                           | 10 +++++-----
 src/client/views/DocComponent.tsx                            | 10 ++--------
 src/client/views/LightboxView.tsx                            |  2 +-
 src/client/views/StyleProvider.tsx                           |  3 ++-
 src/client/views/collections/CollectionNoteTakingView.tsx    |  4 ++--
 src/client/views/collections/CollectionStackingView.tsx      |  4 ++--
 src/client/views/collections/TreeView.tsx                    |  4 ++--
 .../collectionFreeForm/CollectionFreeFormLinksView.tsx       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx            |  6 +++---
 .../collections/collectionLinear/CollectionLinearView.tsx    |  2 +-
 .../collectionMulticolumn/CollectionMulticolumnView.tsx      |  2 +-
 .../collectionMulticolumn/CollectionMultirowView.tsx         |  2 +-
 .../collections/collectionSchema/CollectionSchemaView.tsx    |  2 +-
 src/client/views/nodes/ComparisonBox.tsx                     |  2 +-
 src/client/views/nodes/DocumentIcon.tsx                      |  2 +-
 src/client/views/nodes/DocumentView.tsx                      | 12 +++---------
 src/client/views/nodes/EquationBox.tsx                       |  2 +-
 src/client/views/nodes/WebBox.tsx                            |  2 +-
 src/client/views/nodes/formattedText/DashDocView.tsx         |  2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx    |  2 +-
 20 files changed, 33 insertions(+), 44 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 07357a764..e81a99e40 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -30,7 +30,7 @@ export class DocumentManager {
     @observable.shallow public LinkAnchorBoxViews: DocumentView[] = [];
     @observable.shallow public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = [];
     @computed public get DocumentViews() {
-        return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(view.docViewPath)));
+        return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.Contains(view)));
     }
     public AddDocumentView(dv: DocumentView) {
         this._documentViews.add(dv);
@@ -161,7 +161,7 @@ export class DocumentManager {
 
     public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
         const views: DocumentView[] = [];
-        DocumentManager.Instance.DocumentViews.forEach(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.Document, toFind) && views.push(view));
+        DocumentManager.Instance.DocumentViews.forEach(view => LightboxView.Contains(view) && Doc.AreProtosEqual(view.Document, toFind) && views.push(view));
         return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
     };
     public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
@@ -179,8 +179,8 @@ export class DocumentManager {
             toFindIn;
 
         const toReturn: DocumentView[] = [];
-        const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.IsLightboxDocView(view.docViewPath));
-        const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.IsLightboxDocView(view.docViewPath));
+        const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.Contains(view));
+        const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.Contains(view));
 
         // heuristic to return the "best" documents first:
         //   choose a document in the lightbox first
@@ -237,7 +237,7 @@ export class DocumentManager {
     // traverses down through the viewPath of contexts to the view:
     //           focusing on each context
     public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => {
-        const docViewPath = targetDocView.docViewPath.slice();
+        const docViewPath = [...(targetDocView.containerViewPath?.() ?? []), targetDocView];
         let rootContextView = docViewPath.shift();
         await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false })));
         if (options.toggleTarget && (!options.didMove || targetDocView.Document.hidden)) targetDocView.Document.hidden = !targetDocView.Document.hidden;
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index fb8ef7c36..4400b3576 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -73,10 +73,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
             return this._props.DocumentView;
         }
         get docViewPath() {
-            return this.DocumentView?.().docViewPath ?? emptyPath;
-        }
-        get docViewPathFunc() {
-            return this.DocumentView?.().docViewPathFunc ?? returnEmptyDocViewList;
+            return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
         }
         get containerViewPath() {
             return this._props.containerViewPath;
@@ -136,10 +133,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             return this._props.DocumentView;
         }
         get docViewPath() {
-            return this.DocumentView?.().docViewPath ?? emptyPath;
-        }
-        get docViewPathFunc() {
-            return this.DocumentView?.().docViewPathFunc ?? returnEmptyDocViewList;
+            return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
         }
         get containerViewPath() {
             return this.DocumentView?.().containerViewPath ?? returnEmptyDocViewList;
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index aec9e32fc..3b86d24a6 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -34,7 +34,7 @@ const savedKeys = ['freeform_panX', 'freeform_panY', 'freeform_scale', 'layout_s
 type LightboxSavedState = { [key: string]: FieldResult; }; // prettier-ignore
 @observer
 export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
-    public static IsLightboxDocView(path: DocumentView[]) { return (path ?? []).includes(LightboxView.Instance?._docView!); } // prettier-ignore
+    public static Contains(view?:DocumentView) { return view && LightboxView.Instance?._docView&&  (view.containerViewPath?.() ?? []).concat(view).includes(LightboxView.Instance?._docView); } // prettier-ignore
     public static get LightboxDoc() { return LightboxView.Instance?._doc; } // prettier-ignore
     static Instance: LightboxView;
     private _path: {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 572971391..491a72c08 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -291,6 +291,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
                 }
             };
             const filter = () => {
+                const docView = props?.DocumentView?.();
                 const dashView = untracked(() => DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard));
                 const showFilterIcon =
                     StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
@@ -326,7 +327,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
                                 "this view inherits filters from one of its parents"}
                             color={SettingsManager.userColor}
                             background={showFilterIcon}
-                            items={[ ...(dashView ? [dashView]: []), ...(props?.DocumentView?.().docViewPath??[])]
+                            items={[ ...(dashView ? [dashView]: []), ...(docView?.containerViewPath?.()??[]), ...(docView ? [docView]:[])]
                                     .filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length)
                                     .map(dv => ({
                                 text: StrCast(dv?.Document.title),
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 0795104d9..92c8e1256 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -238,7 +238,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 PanelWidth={width}
                 PanelHeight={height}
                 styleProvider={this.styleProvider}
-                containerViewPath={this.docViewPathFunc}
+                containerViewPath={this.docViewPath}
                 layout_fitWidth={this._props.childLayoutFitWidth}
                 isContentActive={emptyFunction}
                 onKey={this.onKeyDown}
@@ -511,7 +511,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                             action((entries: any) => {
                                 if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
                                     const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
-                                    if (!LightboxView.IsLightboxDocView(this.docViewPath)) {
+                                    if (!LightboxView.Contains(this.DocumentView?.())) {
                                         this._props.setHeight?.(height);
                                     }
                                 }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 11ca62b39..0a0218124 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -327,7 +327,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 PanelHeight={panelHeight}
                 pointerEvents={this.DocumentView?.()._props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
                 styleProvider={this.styleProvider}
-                containerViewPath={this.docViewPathFunc}
+                containerViewPath={this.docViewPath}
                 layout_fitWidth={this.childFitWidth}
                 isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
                 onKey={this.onKeyDown}
@@ -540,7 +540,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                             action((entries: any) => {
                                 if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
                                     const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
-                                    if (!LightboxView.IsLightboxDocView(this.docViewPath)) {
+                                    if (!LightboxView.Contains(this.DocumentView?.())) {
                                         this._props.setHeight?.(height);
                                     }
                                 }
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 205d536de..41720d0dd 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -977,7 +977,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 hideResizeHandles={this.treeView.outlineMode}
                 styleProvider={this.titleStyleProvider}
                 onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
-                containerViewPath={this.treeView.docViewPathFunc}
+                containerViewPath={this.treeView.docViewPath}
                 treeViewDoc={this.treeView.Document}
                 addDocument={undefined}
                 addDocTab={this._props.addDocTab}
@@ -1084,7 +1084,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     ScreenToLocalTransform={this.docTransform}
                     renderDepth={this._props.renderDepth + 1}
                     treeViewDoc={this.treeView?.Document}
-                    containerViewPath={this.treeView.docViewPathFunc}
+                    containerViewPath={this.treeView.docViewPath}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 95d521f65..e5b6c366f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -11,7 +11,7 @@ import './CollectionFreeFormLinksView.scss';
 export class CollectionFreeFormLinksView extends React.Component {
     @computed get uniqueConnections() {
         return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews))
-            .filter(c => !LightboxView.LightboxDoc || (LightboxView.IsLightboxDocView(c.a.docViewPath) && LightboxView.IsLightboxDocView(c.b.docViewPath)))
+            .filter(c => !LightboxView.LightboxDoc || (LightboxView.Contains(c.a) && LightboxView.Contains(c.b)))
             .map(c => <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />);
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c0104d0f8..d2d931869 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1244,7 +1244,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 moveDocument={this._props.moveDocument}
                 pinToPres={this._props.pinToPres}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                containerViewPath={this.docViewPathFunc}
+                containerViewPath={this.DocumentView?.().docViewPath}
                 styleProvider={this.clusterStyleProvider}
                 dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
                 bringToFront={this.bringToFront}
@@ -1741,7 +1741,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0;
 
     incrementalRender = action(() => {
-        if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.docViewPath)) {
+        if (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.())) {
             const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
             const loadIncrement = 5;
             for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
@@ -1888,7 +1888,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         <DocumentView
                             {...this._props}
                             Document={this._lightboxDoc}
-                            containerViewPath={this.docViewPathFunc}
+                            containerViewPath={this.DocumentView?.().docViewPath}
                             TemplateDataDocument={undefined}
                             PanelWidth={this.lightboxPanelWidth}
                             PanelHeight={this.lightboxPanelHeight}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 54ade031b..4f4c674ea 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -189,7 +189,7 @@ export class CollectionLinearView extends CollectionSubView() {
                     dontRegisterView={BoolCast(this.Document.childDontRegisterViews)}
                     focus={emptyFunction}
                     styleProvider={this._props.styleProvider}
-                    containerViewPath={this.docViewPathFunc}
+                    containerViewPath={this.docViewPath}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     childFilters={this._props.childFilters}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index dd1305465..1fd514ccb 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -259,7 +259,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 Document={childLayout}
                 TemplateDataDocument={childLayout.resolvedDataDoc as Doc}
                 styleProvider={this._props.styleProvider}
-                containerViewPath={this.docViewPathFunc}
+                containerViewPath={this.docViewPath}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
                 renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 48c4173d6..98e39cd36 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -254,7 +254,7 @@ export class CollectionMultirowView extends CollectionSubView() {
                 Document={layout}
                 TemplateDataDocument={layout.resolvedDataDoc as Doc}
                 styleProvider={this._props.styleProvider}
-                containerViewPath={this.docViewPathFunc}
+                containerViewPath={this.docViewPath}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
                 renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 09352d7a4..e22a666c5 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -971,7 +971,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
             <DocumentView
                 key={this._props.doc[Id]}
                 {...this._props.schema._props}
-                containerViewPath={this._props.schema.docViewPathFunc}
+                containerViewPath={this._props.schema.docViewPath}
                 LayoutTemplate={this._props.schema._props.childLayoutTemplate}
                 LayoutTemplateString={SchemaRowBox.LayoutString(this._props.schema._props.fieldKey, this._props.index)}
                 Document={this._props.doc}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index b171db17c..e707bb836 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -178,7 +178,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                         {...this._props}
                         Document={targetDoc}
                         TemplateDataDocument={undefined}
-                        containerViewPath={this.docViewPathFunc}
+                        containerViewPath={this.docViewPath}
                         moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={returnZero}
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index 25f56e69a..5ce08cdc3 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -25,7 +25,7 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
     }
 
     static get DocViews() {
-        return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.IsLightboxDocView(v.docViewPath)) : DocumentManager.Instance.DocumentViews;
+        return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.Contains(v)) : DocumentManager.Instance.DocumentViews;
     }
     render() {
         const view = this._props.view;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c2b9dcbbd..4e13b0ccb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1469,17 +1469,11 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
     }
     /**
-     * path of DocumentViews terminating in the DocumentView that contains this DocumentView
+     * path of DocumentViews hat contains this DocumentView (does not includes this DocumentView thouhg)
      */
     @computed get containerViewPath() {
         return this._props.containerViewPath;
     }
-    /**
-     * path of DocumentViews terminating in this DocumentView
-     */
-    @computed get docViewPath(): DocumentView[] {
-        return this.containerViewPath ? [...this.containerViewPath(), this] : [this];
-    }
     @computed get layoutDoc() {
         return Doc.Layout(this.Document, this._props.LayoutTemplate?.());
     }
@@ -1607,7 +1601,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
 
     layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
     screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale;
-    docViewPathFunc = () => this.docViewPath;
+    docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]);
     isSelected = () => this.IsSelected;
     select = (extendSelection: boolean, focusSelection?: boolean) => {
         if (this.IsSelected && SelectionManager.Views.length > 1) SelectionManager.DeselectView(this);
@@ -1706,7 +1700,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                         <DocumentViewInternal
                             {...this._props}
                             DocumentView={this.selfView}
-                            docViewPath={this.docViewPathFunc}
+                            docViewPath={this.docViewPath}
                             PanelWidth={this.PanelWidth}
                             PanelHeight={this.PanelHeight}
                             NativeWidth={this.NativeWidth}
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index ebad257ac..cad76ddf7 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -27,7 +27,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     componentDidMount() {
         this._props.setContentView?.(this);
-        if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.docViewPath))) {
+        if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) {
             this._props.select(false);
 
             this._ref.current!.mathField.focus();
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 86709e4d7..b62d32503 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -558,7 +558,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             clearStyleSheetRules(WebBox.webStyleSheet);
             this._scrollTimer = undefined;
             const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
-            if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.docViewPath))) {
+            if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) {
                 this.layoutDoc.thumb = undefined;
                 this.layoutDoc.thumbScrollTop = undefined;
                 this.layoutDoc.thumbNativeWidth = undefined;
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index cc58c2071..61576a498 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -207,7 +207,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
                     isDocumentActive={returnFalse}
                     isContentActive={this.isContentActive}
                     styleProvider={this._textBox._props.styleProvider}
-                    containerViewPath={this._textBox.docViewPathFunc ?? returnEmptyDoclist}
+                    containerViewPath={this._textBox.docViewPath ?? returnEmptyDoclist}
                     ScreenToLocalTransform={this.getDocTransform}
                     addDocTab={this._textBox._props.addDocTab}
                     pinToPres={returnFalse}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 6a6cec094..65898d568 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1410,7 +1410,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
             (this._editorView as any).TextView = this;
         }
 
-        const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.docViewPath));
+        const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()));
         if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
             const selLoadChar = FormattedTextBox.SelectOnLoadChar;
             FormattedTextBox.SelectOnLoad = undefined;
-- 
cgit v1.2.3-70-g09d2


From 953a298b49c2345741fd7989f9b62cadd76eea52 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 22 Jan 2024 20:41:55 -0500
Subject: fixed pointer events for pdfs

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 25 ++++++----------------
 src/client/views/pdf/PDFViewer.scss                |  1 +
 2 files changed, 8 insertions(+), 18 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d2d931869..3eaf1876c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1190,15 +1190,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     @computed get childPointerEvents() {
         const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine);
-        const pointerevents = SnappingManager.IsResizing
+        return SnappingManager.IsResizing
             ? 'none'
             : this._props.childPointerEvents?.() ??
-              (this._props.viewDefDivClick || //
-              (engine === computePassLayout.name && !this._props.isSelected()) ||
-              this.isContentActive() === false
-                  ? 'none'
-                  : this._props.pointerEvents?.());
-        return pointerevents;
+                  (this._props.viewDefDivClick || //
+                  (engine === computePassLayout.name && !this._props.isSelected()) ||
+                  this.isContentActive() === false
+                      ? 'none'
+                      : this._props.pointerEvents?.());
     }
 
     @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined = undefined;
@@ -1476,17 +1475,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 );
 
                 this._disposers.pointerevents = reaction(
-                    () => {
-                        const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine);
-                        return SnappingManager.IsResizing
-                            ? 'none'
-                            : this._props.childPointerEvents?.() ??
-                                  (this._props.viewDefDivClick || //
-                                  (engine === computePassLayout.name && !this._props.isSelected()) ||
-                                  this.isContentActive() === false
-                                      ? 'none'
-                                      : this._props.pointerEvents?.());
-                    },
+                    () => this.childPointerEvents,
                     pointerevents => (this._childPointerEvents = pointerevents as any),
                     { fireImmediately: true }
                 );
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index cfe07f6cb..d3dd9f727 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -52,6 +52,7 @@
         .textLayer {
             pointer-events: all;
             user-select: text;
+            z-index: 0;
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 001127c07f95173d7036db19d07dcfb1135f3caa Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 22 Jan 2024 21:45:28 -0500
Subject: fixed schema rows to render, fix for resizing docs from left side.
 all locking docs in non freeform view.  fix for labelBox with multiple rows
 to keep top rows.  cleaned up docViewPath/containerViewPath &

---
 src/client/util/CurrentUserUtils.ts                |  2 +-
 src/client/views/DocComponent.tsx                  | 20 +-------
 src/client/views/DocumentDecorations.tsx           |  9 ++--
 src/client/views/MainView.tsx                      |  7 +--
 src/client/views/MarqueeAnnotator.tsx              |  6 +--
 src/client/views/StyleProvider.tsx                 | 56 +++++++---------------
 .../views/collections/CollectionNoteTakingView.tsx |  7 ++-
 .../views/collections/CollectionStackingView.tsx   | 15 +++---
 src/client/views/collections/CollectionSubView.tsx |  4 ++
 src/client/views/collections/TabDocView.tsx        |  4 +-
 src/client/views/collections/TreeView.tsx          |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 17 ++++---
 .../collectionLinear/CollectionLinearView.tsx      |  2 +-
 .../CollectionMulticolumnView.tsx                  |  2 +-
 .../CollectionMultirowView.tsx                     |  2 +-
 .../collectionSchema/CollectionSchemaView.scss     |  3 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  2 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  | 18 ++++++-
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 14 ++++--
 src/client/views/nodes/ComparisonBox.tsx           |  7 ++-
 src/client/views/nodes/DocumentContentsView.tsx    | 12 ++---
 src/client/views/nodes/DocumentView.tsx            | 29 +++++------
 src/client/views/nodes/LabelBox.scss               |  5 +-
 src/client/views/nodes/LabelBox.tsx                |  4 +-
 src/client/views/nodes/LinkAnchorBox.tsx           | 11 +++--
 src/client/views/nodes/LinkBox.tsx                 |  6 +--
 src/client/views/nodes/PDFBox.tsx                  |  1 +
 .../views/nodes/formattedText/DashDocView.tsx      |  7 ++-
 .../views/nodes/formattedText/DashFieldView.tsx    |  2 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 | 13 ++---
 src/client/views/topbar/TopBar.tsx                 |  3 +-
 src/fields/Doc.ts                                  |  5 ++
 33 files changed, 143 insertions(+), 158 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 7d9cb36c5..0b15e3add 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -192,7 +192,7 @@ export class CurrentUserUtils {
                 {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", });
         };
         const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({
-            textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 24, layout_borderRounding: "5px",  _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
+            textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px",  _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
         });
         const imageBox = (opts: DocumentOptions, url?:string) => Docs.Create.ImageDocument(url ?? "http://www.cs.brown.edu/~bcz/noImage.png", { "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG,  _width: 360 / 4, _height: 270 / 4,  _layout_showTitle: "title", ...opts });
         const fontBox = (opts:DocumentOptions, data?:string) => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts  });
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 4400b3576..6aba4a042 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,6 +1,6 @@
 import { action, computed, makeObservable, observable } from 'mobx';
 import * as React from 'react';
-import { emptyPath, returnFalse } from '../../Utils';
+import { returnFalse } from '../../Utils';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
@@ -41,7 +41,7 @@ export function DocComponent<P extends DocComponentProps>() {
         }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() {
-            return this._props.Document[DocData] as Doc;
+            return this._props.Document[DocData];
         }
     }
     return Component;
@@ -55,16 +55,12 @@ export interface ViewBoxBaseProps {
     Document: Doc;
     TemplateDataDocument?: Doc;
     DocumentView?: () => DocumentView;
-    containerViewPath?: () => DocumentView[];
     fieldKey: string;
     isSelected: () => boolean;
     isContentActive: () => boolean | undefined;
     ScreenToLocalTransform: () => Transform;
     renderDepth: number;
 }
-function returnEmptyDocViewList() {
-    return [] as DocumentView[];
-}
 export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
         ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
@@ -72,12 +68,6 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
         get DocumentView() {
             return this._props.DocumentView;
         }
-        get docViewPath() {
-            return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
-        }
-        get containerViewPath() {
-            return this._props.containerViewPath;
-        }
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         get Document() {
             return this._props.Document;
@@ -132,12 +122,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
         get DocumentView() {
             return this._props.DocumentView;
         }
-        get docViewPath() {
-            return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
-        }
-        get containerViewPath() {
-            return this.DocumentView?.().containerViewPath ?? returnEmptyDocViewList;
-        }
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         @computed get Document() {
             return this._props.Document;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c96253405..8db972ef0 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -344,10 +344,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
-            e => UndoManager.RunInBatch(
-                () => SelectionManager.Docs.forEach(doc =>
-                        doc._pointerEvents = (doc._lockedPosition = !doc._lockedPosition)? 'none' : undefined ),
-                'toggleBackground' ) // prettier-ignore
+            e => UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => Doc.toggleLockedPosition(doc)), 'toggleBackground')
         );
         e.stopPropagation();
     };
@@ -530,7 +527,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             doc.xPadding = NumCast(doc.xPadding) * scale.x;
             doc.yPadding = NumCast(doc.yPadding) * scale.y;
         } else {
-            const refCent = docView.screenToContentsTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
+            const refCent = docView.screenToViewTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
             const [nwidth, nheight] = [docView.nativeWidth, docView.nativeHeight];
             const [initWidth, initHeight] = [NumCast(doc._width, 1), NumCast(doc._height)];
 
@@ -689,7 +686,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         );
 
         const bounds = this.ClippedBounds;
-        const useLock = bounds.r - bounds.x > 135 && seldocview.CollectionFreeFormDocumentView;
+        const useLock = bounds.r - bounds.x > 135;
         const useRotation = !hideResizers && seldocview.Document.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
         const rotation = SelectionManager.Views.length == 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0;
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cd6aeea41..d700ea020 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -56,13 +56,11 @@ import { CollectionLinearView } from './collections/collectionLinear';
 import { LinkMenu } from './linking/LinkMenu';
 import { AudioBox } from './nodes/AudioBox';
 import { DocButtonState } from './nodes/DocumentLinksButton';
-import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from './nodes/DocumentView';
 import { ImageBox } from './nodes/ImageBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
 import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu';
-import { MapBox } from './nodes/MapBox/MapBox';
-import { RadialMenu } from './nodes/RadialMenu';
 import { TaskCompletionBox } from './nodes/TaskCompletedBox';
 import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@@ -898,6 +896,7 @@ export class MainView extends ObservableReactComponent<{}> {
             <div className="mainView-docButtons" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }} ref={this._docBtnRef}>
                 <CollectionLinearView
                     Document={Doc.MyDockedBtns}
+                    docViewPath={returnEmptyDocViewList}
                     fieldKey="data"
                     dropAction="embed"
                     styleProvider={DefaultStyleProvider}
@@ -906,7 +905,6 @@ export class MainView extends ObservableReactComponent<{}> {
                     isAnyChildContentActive={returnFalse}
                     isContentActive={emptyFunction}
                     isSelected={returnFalse}
-                    containerViewPath={returnEmptyDoclist}
                     moveDocument={this.moveButtonDoc}
                     addDocument={this.addButtonDoc}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
@@ -1021,7 +1019,6 @@ export class MainView extends ObservableReactComponent<{}> {
                 <PreviewCursor />
                 <TaskCompletionBox />
                 <ContextMenu />
-                <RadialMenu />
                 <AnchorMenu />
                 <MapAnchorMenu />
                 <DirectionsAnchorMenu />
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index cfdc648b4..d0516336c 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -66,7 +66,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations();
         if (savedAnnoMap.size === 0) return undefined;
         const savedAnnos = Array.from(savedAnnoMap.values())[0];
-        const doc = this.props.docView().Document;
+        const doc = this.props.Document;
         const scale = (this.props.annotationLayerScaling?.() || 1) * NumCast(doc._freeform_scale, 1);
         if (savedAnnos.length && (savedAnnos[0] as any).marqueeing) {
             const anno = savedAnnos[0];
@@ -227,7 +227,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
                       dragComplete: e => {
                           if (!e.aborted && e.linkDocument) {
                               Doc.GetProto(e.linkDocument).link_relationship = 'cropped image';
-                              Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView().Document.title;
+                              Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.Document.title;
                               Doc.GetProto(e.linkDocument).link_displayLine = false;
                           }
                       },
@@ -256,7 +256,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             // configure and show the annotation/link menu if a the drag region is big enough
             // copy the temporary marquee to allow for multiple selections (not currently available though).
             const copy = document.createElement('div');
-            const scale = (this.props.scaling?.() || 1) * NumCast(this.props.docView().Document._freeform_scale, 1);
+            const scale = (this.props.scaling?.() || 1) * NumCast(this.props.Document._freeform_scale, 1);
             ['border', 'opacity', 'top', 'left', 'width', 'height'].forEach(prop => (copy.style[prop as any] = marqueeStyle[prop as any]));
             copy.className = 'marqueeAnnotator-annotationBox';
             copy.style.top = parseInt(marqueeStyle.top.toString().replace('px', '')) / scale + this.props.scrollTop + 'px';
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 491a72c08..1cb5ff249 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -20,7 +20,6 @@ import { SnappingManager } from '../util/SnappingManager';
 import { undoBatch, UndoManager } from '../util/UndoManager';
 import { TreeSort } from './collections/TreeSort';
 import { Colors } from './global/globalEnums';
-import { DocumentViewProps } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PropertiesView } from './PropertiesView';
@@ -51,21 +50,7 @@ export enum StyleProp {
 }
 
 function toggleLockedPosition(doc: Doc) {
-    UndoManager.RunInBatch(
-        () =>
-            runInAction(() => {
-                doc._lockedPosition = !doc._lockedPosition;
-                doc._pointerEvents = doc._lockedPosition ? 'none' : undefined;
-            }),
-        'toggleBackground'
-    );
-}
-
-export function testDocProps(toBeDetermined: any): toBeDetermined is DocumentViewProps {
-    return toBeDetermined?.isContentActive ? toBeDetermined : undefined;
-}
-export function testFieldProps(toBeDetermined: any): toBeDetermined is FieldViewProps {
-    return toBeDetermined?.isContentActive ? toBeDetermined : undefined;
+    UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
 }
 
 export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
@@ -82,19 +67,17 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
 //
 export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any {
     const remoteDocHeader = 'author;author_date;noMargin';
-    const docProps = testDocProps(props) ? props : undefined;
-    const fieldProps = testFieldProps(props) ? props : undefined;
     const isCaption = property.includes(':caption');
     const isAnchor = property.includes(':anchor');
     const isNonTransparent = property.includes(':nonTransparent');
     const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent');
     const isContent = property.includes(':content');
     const isAnnotated = property.includes(':annotated');
-    const isInk = () => doc?._layout_isSvg && !docProps?.LayoutTemplateString;
+    const isInk = () => doc?._layout_isSvg && !props?.LayoutTemplateString;
     const isOpen = property.includes(':open');
     const isEmpty = property.includes(':empty');
     const boxBackground = property.includes(':box');
-    const fieldKey = fieldProps?.fieldKey ? fieldProps.fieldKey + '_' : isCaption ? 'caption_' : '';
+    const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : '';
     const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
     const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight);
     const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor + ':nonTransparent' + (isNonTransparentLevel + 1));
@@ -137,20 +120,19 @@ 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 docProps?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
+        case StyleProp.Opacity:    return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
         case StyleProp.FontSize:   return StrCast(doc?.[fieldKey + 'fontSize'],   StrCast(doc?._text_fontSize,   StrCast(Doc.UserDoc().fontSize)));
         case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)));
         case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight)));
         case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent'));
         case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
-        case StyleProp.TitleHeight:
-            return (props?.DocumentView?.().screenToViewTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30)
+        case StyleProp.TitleHeight:return (props?.ScreenToLocalTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30);
         case StyleProp.ShowTitle:
             return (
                 (doc &&
-                    !docProps?.LayoutTemplateString &&
+                    !props?.LayoutTemplateString &&
                     !doc.presentation_targetDoc &&
-                    !docProps?.LayoutTemplateString?.includes(KeyValueBox.name) &&
+                    !props?.LayoutTemplateString?.includes(KeyValueBox.name) &&
                     props?.layout_showTitle?.() !== '' &&
                     StrCast(
                         doc._layout_showTitle,
@@ -169,7 +151,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
             if (doc?.type === DocumentType.FONTICON) return SettingsManager.userColor;
             const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
             if (docColor) return docColor;
-            const parView = props?.DocumentView?.();
             const backColor = backgroundCol();
             return backColor ? lightOrDark(backColor) : undefined;
         case StyleProp.BorderRounding:
@@ -247,7 +228,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
                         doc?.layout_boxShadow,
                         doc?._type_collection === CollectionViewType.Pile
                             ? '4px 4px 10px 2px'
-                            : lockedPosition() || doc?.isGroup || docProps?.LayoutTemplateString
+                            : lockedPosition() || doc?.isGroup || props?.LayoutTemplateString
                             ? undefined // groups have no drop shadow -- they're supposed to be "invisible".  LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
                             : `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
                     );
@@ -257,10 +238,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
                 default:
                     return doc.z
                         ? `#9c9396  ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
-                        : props?.DocumentView?.().containerViewPath?.().lastElement()?.Document._freeform_useClusters
-                        ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                        : props?.containerViewPath?.().lastElement()?.Document._freeform_useClusters
+                        ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : NumCast(doc.group, -1) !== -1
-                        ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                        ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : lockedPosition()
                         ? undefined // if it's a background & has a cluster color, make the shadow spread really big
                         : StrCast(doc.layout_fieldKey).includes('_inline')  // if doc is an inline document in a text box
@@ -271,8 +252,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
             }
         }
         case StyleProp.PointerEvents:
-            if (StrCast(doc?.pointerEvents) && !docProps?.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 (docProps?.DocumentView?.()._props.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
+            if (StrCast(doc?.pointerEvents) && !props?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
+            if (props?.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
             if (SnappingManager.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
             if (props?.pointerEvents?.() === 'none') return 'none';
             if (opacity() === 0) return 'none';
@@ -281,22 +262,17 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
             if (props?.isDocumentActive?.() && !props.treeViewDoc) return isInk() ? 'visiblePainted' : 'all';
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations:
-            const lock = () => {
-                if (props?.DocumentView?.().containerViewPath?.().lastElement()?.Document?._type_collection === CollectionViewType.Freeform) {
-                    return doc?.pointerEvents !== 'none' ? null : (
+            const lock = () => doc?.pointerEvents !== 'none' ? null : (
                         <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
                             <FontAwesomeIcon icon='lock' size="lg" />
                         </div>
                     );
-                }
-            };
             const filter = () => {
-                const docView = props?.DocumentView?.();
                 const dashView = untracked(() => DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard));
                 const showFilterIcon =
                     StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
                         ? 'green' // #18c718bd' //'hasFilter'
-                        : docProps?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragDocsFilter).length || docProps?.childFiltersByRanges().length
+                        : props?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragDocsFilter).length || props?.childFiltersByRanges().length
                         ? 'orange' //'inheritsFilter'
                         : undefined;
                 return !showFilterIcon ? null : (
@@ -327,7 +303,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
                                 "this view inherits filters from one of its parents"}
                             color={SettingsManager.userColor}
                             background={showFilterIcon}
-                            items={[ ...(dashView ? [dashView]: []), ...(docView?.containerViewPath?.()??[]), ...(docView ? [docView]:[])]
+                            items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[])]
                                     .filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length)
                                     .map(dv => ({
                                 text: StrCast(dv?.Document.title),
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 92c8e1256..363db8850 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -238,7 +238,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 PanelWidth={width}
                 PanelHeight={height}
                 styleProvider={this.styleProvider}
-                containerViewPath={this.docViewPath}
+                containerViewPath={this.childContainerViewPath}
                 layout_fitWidth={this._props.childLayoutFitWidth}
                 isContentActive={emptyFunction}
                 onKey={this.onKeyDown}
@@ -407,10 +407,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
 
     @undoBatch
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
-        const docView = fieldProps.DocumentView?.();
-        if (docView && (e.ctrlKey || docView.Document._createDocOnCR) && ['Enter'].includes(e.key)) {
+        if ((e.ctrlKey || fieldProps.Document._createDocOnCR) && ['Enter'].includes(e.key)) {
             e.stopPropagation?.();
-            const newDoc = Doc.MakeCopy(docView.Document, true);
+            const newDoc = Doc.MakeCopy(fieldProps.Document, true);
             Doc.GetProto(newDoc).text = undefined;
             FormattedTextBox.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0a0218124..9384b7088 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -278,16 +278,15 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     };
     @undoBatch
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
-        const docView = fieldProps.DocumentView?.();
-        if (docView && ['Enter'].includes(e.key) && e.ctrlKey) {
+        if (['Enter'].includes(e.key) && e.ctrlKey) {
             e.stopPropagation?.();
             const below = !e.altKey && e.key !== 'Tab';
-            const layout_fieldKey = StrCast(docView.LayoutFieldKey);
-            const newDoc = Doc.MakeCopy(docView.Document, true);
-            const dataField = docView.Document[Doc.LayoutFieldKey(newDoc)];
+            const layout_fieldKey = StrCast(fieldProps.fieldKey);
+            const newDoc = Doc.MakeCopy(fieldProps.Document, true);
+            const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
             newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
-            if (layout_fieldKey !== 'layout' && docView.Document[layout_fieldKey] instanceof Doc) {
-                newDoc[layout_fieldKey] = docView.Document[layout_fieldKey];
+            if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
+                newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
             }
             Doc.GetProto(newDoc).text = undefined;
             FormattedTextBox.SetSelectOnLoad(newDoc);
@@ -327,7 +326,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 PanelHeight={panelHeight}
                 pointerEvents={this.DocumentView?.()._props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
                 styleProvider={this.styleProvider}
-                containerViewPath={this.docViewPath}
+                containerViewPath={this.childContainerViewPath}
                 layout_fitWidth={this.childFitWidth}
                 isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
                 onKey={this.onKeyDown}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 2e0a75281..d7c42a975 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -25,6 +25,7 @@ import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../DocComponent';
 import { LoadingBox } from '../nodes/LoadingBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { CollectionView, CollectionViewProps } from './CollectionView';
+import { returnEmptyDocViewList } from '../nodes/DocumentView';
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     isAnyChildContentActive: () => boolean;
@@ -69,6 +70,9 @@ export function CollectionSubView<X>(moreProps?: X) {
                   : Doc.GetProto(this._props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
         }
 
+        get childContainerViewPath() {
+            return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
+        }
         // this returns whether either the collection is selected, or the template that it is part of is selected
         rootSelected = () => this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
 
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 2441c67e6..f00e42360 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -29,7 +29,7 @@ import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
 import { Colors } from '../global/globalEnums';
-import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { DashFieldView } from '../nodes/formattedText/DashFieldView';
 import { PinProps, PresBox, PresMovement } from '../nodes/trails';
@@ -595,7 +595,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
             <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
                 <CollectionFreeFormView
                     Document={this._props.document}
-                    containerViewPath={returnEmptyDoclist}
+                    docViewPath={returnEmptyDocViewList}
                     childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
                     noOverlay={true} // don't render overlay Docs since they won't scale
                     isContentActive={emptyFunction}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 41720d0dd..9f40edee1 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -977,7 +977,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 hideResizeHandles={this.treeView.outlineMode}
                 styleProvider={this.titleStyleProvider}
                 onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
-                containerViewPath={this.treeView.docViewPath}
+                containerViewPath={this.treeView.childContainerViewPath}
                 treeViewDoc={this.treeView.Document}
                 addDocument={undefined}
                 addDocTab={this._props.addDocTab}
@@ -1084,7 +1084,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     ScreenToLocalTransform={this.docTransform}
                     renderDepth={this._props.renderDepth + 1}
                     treeViewDoc={this.treeView?.Document}
-                    containerViewPath={this.treeView.docViewPath}
+                    containerViewPath={this.treeView.childContainerViewPath}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3eaf1876c..c4358747b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1170,18 +1170,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @undoBatch
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
-        const docView = fieldProps.DocumentView?.();
-        if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
+        if ((e.metaKey || e.ctrlKey || e.altKey || fieldProps.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
             e.stopPropagation?.();
             const below = !e.altKey && e.key !== 'Tab';
-            const layout_fieldKey = StrCast(docView.LayoutFieldKey);
-            const newDoc = Doc.MakeCopy(docView.Document, true);
-            const dataField = docView.Document[Doc.LayoutFieldKey(newDoc)];
+            const layout_fieldKey = StrCast(fieldProps.fieldKey);
+            const newDoc = Doc.MakeCopy(fieldProps.Document, true);
+            const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
             newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
-            if (below) newDoc.y = NumCast(docView.Document.y) + NumCast(docView.Document._height) + 10;
-            else newDoc.x = NumCast(docView.Document.x) + NumCast(docView.Document._width) + 10;
-            if (layout_fieldKey !== 'layout' && docView.Document[layout_fieldKey] instanceof Doc) {
-                newDoc[layout_fieldKey] = docView.Document[layout_fieldKey];
+            if (below) newDoc.y = NumCast(fieldProps.Document.y) + NumCast(fieldProps.Document._height) + 10;
+            else newDoc.x = NumCast(fieldProps.Document.x) + NumCast(fieldProps.Document._width) + 10;
+            if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
+                newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
             }
             Doc.GetProto(newDoc).text = undefined;
             FormattedTextBox.SetSelectOnLoad(newDoc);
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 4f4c674ea..72bdb9b6b 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -189,7 +189,7 @@ export class CollectionLinearView extends CollectionSubView() {
                     dontRegisterView={BoolCast(this.Document.childDontRegisterViews)}
                     focus={emptyFunction}
                     styleProvider={this._props.styleProvider}
-                    containerViewPath={this.docViewPath}
+                    containerViewPath={this.childContainerViewPath}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     childFilters={this._props.childFilters}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 1fd514ccb..40b4151fe 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -259,7 +259,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 Document={childLayout}
                 TemplateDataDocument={childLayout.resolvedDataDoc as Doc}
                 styleProvider={this._props.styleProvider}
-                containerViewPath={this.docViewPath}
+                containerViewPath={this.childContainerViewPath}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
                 renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 98e39cd36..04a4042f1 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -254,7 +254,7 @@ export class CollectionMultirowView extends CollectionSubView() {
                 Document={layout}
                 TemplateDataDocument={layout.resolvedDataDoc as Doc}
                 styleProvider={this._props.styleProvider}
-                containerViewPath={this.docViewPath}
+                containerViewPath={this.childContainerViewPath}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
                 renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 02131ae22..29d121974 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -210,8 +210,9 @@
     border: 1px solid $medium-gray;
     overflow-x: hidden;
     overflow-y: auto;
-    padding: 5px;
     display: inline-flex;
+    padding: 0;
+    align-items: center;
 }
 
 .schema-row {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index e22a666c5..227274a53 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -971,7 +971,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
             <DocumentView
                 key={this._props.doc[Id]}
                 {...this._props.schema._props}
-                containerViewPath={this._props.schema.docViewPath}
+                containerViewPath={this._props.schema.childContainerViewPath}
                 LayoutTemplate={this._props.schema._props.childLayoutTemplate}
                 LayoutTemplateString={SchemaRowBox.LayoutString(this._props.schema._props.fieldKey, this._props.index)}
                 Document={this._props.doc}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 8e1fba699..3f94e7fc1 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -3,7 +3,7 @@ import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
-import { CgClose } from 'react-icons/cg';
+import { CgClose, CgLock, CgLockUnlock } from 'react-icons/cg';
 import { FaExternalLinkAlt } from 'react-icons/fa';
 import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
 import { Doc } from '../../../../fields/Doc';
@@ -120,6 +120,22 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
                         width: CollectionSchemaView._rowMenuWidth,
                         pointerEvents: !this._props.isContentActive() ? 'none' : undefined,
                     }}>
+                    <IconButton
+                        tooltip="whether document interations are enabled"
+                        icon={this.Document._lockedPosition ? <CgLockUnlock size="12px" /> : <CgLock size="12px" />}
+                        size={Size.XSMALL}
+                        onPointerDown={e =>
+                            setupMoveUpEvents(
+                                this,
+                                e,
+                                returnFalse,
+                                emptyFunction,
+                                undoable(e => {
+                                    e.stopPropagation();
+                                    Doc.toggleLockedPosition(this.Document);
+                                }, 'Delete Row')
+                            )
+                        }></IconButton>
                     <IconButton
                         tooltip="close"
                         icon={<CgClose size={'16px'} />}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 83cabf355..73709c17e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,6 +12,7 @@ import { DocumentManager } from '../../util/DocumentManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { DocComponent } from '../DocComponent';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import './CollectionFreeFormDocumentView.scss';
@@ -37,7 +38,7 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
     CollectionFreeFormView: CollectionFreeFormView;
 }
 @observer
-export class CollectionFreeFormDocumentViewWrapper extends DocComponent<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>() implements CollectionFreeFormDocumentViewProps {
+export class CollectionFreeFormDocumentViewWrapper extends ObservableReactComponent<CollectionFreeFormDocumentViewWrapperProps> implements CollectionFreeFormDocumentViewProps {
     constructor(props: any) {
         super(props);
         makeObservable(this);
@@ -59,6 +60,9 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent<Collecti
     CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
     RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
 
+    get Document() {
+        return this._props.Document;
+    }
     @computed get WrapperKeys() {
         return Object.keys(this).filter(key => key.startsWith('w_')).map(key => key.replace('w_', ''))
                   .map(key => ({upper:key, lower:key[0].toLowerCase() + key.substring(1)})); // prettier-ignore
@@ -227,7 +231,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         });
     }
 
-    @action public float = () => {
+    float = () => {
         const topDoc = this.Document;
         const containerDocView = this._props.containerViewPath?.().lastElement();
         const screenXf = containerDocView?.screenToContentsTransform();
@@ -253,8 +257,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     nudge = (x: number, y: number) => {
         const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y);
-        this._props.Document.x = this._props.w_X() + locX;
-        this._props.Document.y = this._props.w_Y() + locY;
+        this.Document.x = this._props.w_X() + locX;
+        this.Document.y = this._props.w_Y() + locY;
     };
     screenToLocalTransform = () =>
         this._props
@@ -288,7 +292,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     zIndex: this._props.w_ZIndex?.(),
                     display: this._props.w_Width?.() ? undefined : 'none',
                 }}>
-                {this._props.RenderCutoffProvider(this._props.Document) ? (
+                {this._props.RenderCutoffProvider(this.Document) ? (
                     <div style={{ position: 'absolute', width: this._props.PanelWidth(), height: this._props.PanelHeight(), background: 'lightGreen' }} />
                 ) : (
                     <DocumentView {...passOnProps} CollectionFreeFormDocumentView={this.returnThis} styleProvider={this.styleProvider} ScreenToLocalTransform={this.screenToLocalTransform} isGroupActive={this.isGroupActive} />
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index e707bb836..500eb52ac 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -11,7 +11,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import './ComparisonBox.scss';
-import { DocumentView } from './DocumentView';
+import { DocumentView, returnEmptyDocViewList } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
 
@@ -152,6 +152,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         if (property === StyleProp.PointerEvents) return 'none';
         return this._props.styleProvider?.(doc, props, property);
     };
+    @computed get childContainerViewPath() {
+        return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
+    }
     moveDoc1 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
     moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
     remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
@@ -178,7 +181,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                         {...this._props}
                         Document={targetDoc}
                         TemplateDataDocument={undefined}
-                        containerViewPath={this.docViewPath}
+                        containerViewPath={this.childContainerViewPath}
                         moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={returnZero}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 0d76959af..55859b92b 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -117,8 +117,6 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
 @observer
 export class DocumentContentsView extends ObservableReactComponent<
     FieldViewProps & {
-        layout_fieldKey: string;
-        LayoutTemplateString?: string;
         onClick?: () => ScriptField;
         LayoutTemplate?: () => Opt<Doc>;
     }
@@ -132,8 +130,8 @@ export class DocumentContentsView extends ObservableReactComponent<
         TraceMobx();
         if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
         if (!this.layoutDoc) return '<p>awaiting layout</p>';
-        if (this._props.layout_fieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
-        const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layout_fieldKey ? this._props.layout_fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
+        if (this._props.fieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
+        const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.fieldKey ? this._props.fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
         if (layout === undefined) return this._props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
         if (typeof layout === 'string') return layout;
         return '<p>Loading layout</p>';
@@ -141,12 +139,12 @@ export class DocumentContentsView extends ObservableReactComponent<
 
     get layoutDoc() {
         // bcz: replaced this with below : is it correct?  change was made to accommodate passing fieldKey's from a layout script
-        // const template: Doc = this._props.LayoutTemplate?.() || Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined);
+        // const template: Doc = this._props.LayoutTemplate?.() || Doc.Layout(this._props.Document, this._props.fieldKey ? Cast(this._props.Document[this._props.fieldKey], Doc, null) : undefined);
         const template: Doc =
             this._props.LayoutTemplate?.() ||
             (this._props.LayoutTemplateString && this._props.Document) ||
-            (this._props.layout_fieldKey && StrCast(this._props.Document[this._props.layout_fieldKey]) && this._props.Document) ||
-            Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined);
+            (this._props.fieldKey && StrCast(this._props.Document[this._props.fieldKey]) && this._props.Document) ||
+            Doc.Layout(this._props.Document, this._props.fieldKey ? Cast(this._props.Document[this._props.fieldKey], Doc, null) : undefined);
         return Doc.expandTemplateLayout(template, this._props.Document);
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4e13b0ccb..444c300f3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -89,6 +89,9 @@ export enum OpenWhere {
     addRightKeyvalue = 'add:right:keyValue',
 }
 
+export function returnEmptyDocViewList() {
+    return [] as DocumentView[];
+}
 export interface DocFocusOptions {
     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
@@ -148,6 +151,7 @@ export interface DocComponentView {
  * */
 export interface DocumentViewSharedProps {
     Document: Doc;
+    LayoutTemplateString?: string;
     TemplateDataDocument?: Doc;
     renderDepth: number;
     scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
@@ -161,6 +165,7 @@ export interface DocumentViewSharedProps {
     ignoreAutoHeight?: boolean;
     disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
     CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
+    containerViewPath?: () => DocumentView[];
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
     isGroupActive?: () => string | undefined; // is this document part of a group that is active
     setContentView?: (view: DocComponentView) => any;
@@ -206,7 +211,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     hideLinkButton?: boolean;
     hideCaptions?: boolean;
     contentPointerEvents?: 'none' | 'all' | undefined; // pointer events allowed for content of a document view.  eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
-    LayoutTemplateString?: string;
     dontCenter?: 'x' | 'y' | 'xy';
     childHideDecorationTitle?: boolean;
     childHideResizeHandles?: boolean;
@@ -218,7 +222,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     NativeWidth?: () => number;
     NativeHeight?: () => number;
     LayoutTemplate?: () => Opt<Doc>;
-    containerViewPath?: () => DocumentView[];
     contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
     onClick?: () => ScriptField;
     onDoubleClick?: () => ScriptField;
@@ -235,13 +238,13 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
  * these props correspond to things that the DocumentView creates and thus doesn't need to receive as a prop
  */
 export interface DocumentViewInternalSharedProps {
-    DocumentView: () => DocumentView;
     select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
     isSelected: () => boolean;
+    docViewPath: () => DocumentView[];
     NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
 }
 export interface DocumentViewInternalProps extends DocumentViewProps, DocumentViewInternalSharedProps {
-    docViewPath: () => DocumentView[];
+    docViewPublic: () => DocumentView;
 }
 
 @observer
@@ -276,7 +279,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     } // this makes mobx trace() statements more descriptive
 
     public get DocumentView() {
-        return this._props.DocumentView;
+        return this._props.docViewPublic;
     }
 
     public get ContentDiv() {
@@ -426,15 +429,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     }
 
-    defaultRestoreTargetView = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => {
-        const targetMatch =
-            Doc.AreProtosEqual(anchor, this.Document) || // anchor is this document, so anchor's properties apply to this document
-            (DocCast(anchor)?.layout_unrendered && Doc.AreProtosEqual(DocCast(anchor.annotationOn), this.Document)) // the anchor is an layout_unrendered annotation on this document, so anchor properties apply to this document
-                ? true
-                : false;
-        return targetMatch && PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined;
-    };
-
     // switches text input focus to the title bar of the document (and displays the title bar if it hadn't been)
     setTitleFocus = () => {
         if (!StrCast(this.layoutDoc._layout_showTitle)) this.layoutDoc._layout_showTitle = 'title';
@@ -923,8 +917,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 }}>
                 <DocumentContentsView
                     key={1}
-                    {...this._props}
-                    fieldKey=""
+                    {...this.styleProps}
+                    fieldKey={this.finalLayoutKey}
                     pointerEvents={this.contentPointerEvents}
                     setContentView={this.setContentView}
                     childFilters={this.childFilters}
@@ -935,7 +929,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     rootSelected={this.rootSelected}
                     onClick={this.onClickFunc}
                     setTitleFocus={this.setTitleFocus}
-                    layout_fieldKey={this.finalLayoutKey}
                 />
                 {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
             </div>
@@ -1699,7 +1692,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                         }}>
                         <DocumentViewInternal
                             {...this._props}
-                            DocumentView={this.selfView}
+                            docViewPublic={this.selfView}
                             docViewPath={this.docViewPath}
                             PanelWidth={this.PanelWidth}
                             PanelHeight={this.PanelHeight}
diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss
index 42e158584..0b195713d 100644
--- a/src/client/views/nodes/LabelBox.scss
+++ b/src/client/views/nodes/LabelBox.scss
@@ -18,6 +18,9 @@
     display: inline-block;
     margin: auto;
     text-overflow: ellipsis;
+    > span {
+        max-height: 100%; // make sure top of text is in view, otherwise it would center on middle of large text span
+    }
 }
 
 .labelBox-params {
@@ -29,4 +32,4 @@
     width: 100%;
     background: lightgray;
     border: dimgray solid 1px;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 934bce448..cc7c15a10 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -1,7 +1,7 @@
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, Field } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
@@ -43,7 +43,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
     }
 
     @computed get Title() {
-        return this.dataDoc.title_custom ? StrCast(this.Document.title) : this._props.label ? this._props.label : typeof this.dataDoc[this.fieldKey] === 'string' ? StrCast(this.dataDoc[this.fieldKey]) : StrCast(this.Document.title);
+        return this.dataDoc.title_custom ? StrCast(this.Document.title) : this._props.label ? this._props.label : Field.toString(this.dataDoc[this.fieldKey] as Field);
     }
 
     protected createDropTarget = (ele: HTMLDivElement) => {
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 864c1955b..362a7def1 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -33,15 +33,16 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps & ViewBox
     }
 
     @computed get linkSource() {
-        return this.containerViewPath?.().lastElement().Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource);
+        return this.DocumentView?.().containerViewPath?.().lastElement().Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource);
     }
 
     onPointerDown = (e: React.PointerEvent) => {
         const linkSource = this.linkSource;
-        linkSource && setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
-            if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false);
-            else this._props.select(false);
-        });
+        linkSource &&
+            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
+                if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false);
+                else this._props.select(false);
+            });
     };
     onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
         const cdiv = this._ref?.current?.parentElement;
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 4221f464d..ba34255dc 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -29,12 +29,12 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get anchor1() {
         const anchor1 = DocCast(this.dataDoc.link_anchor_1);
         const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1;
-        return DocumentManager.Instance.getDocumentView(anchor_1, this.containerViewPath?.().lastElement());
+        return DocumentManager.Instance.getDocumentView(anchor_1, this.DocumentView?.().containerViewPath?.().lastElement());
     }
     @computed get anchor2() {
         const anchor2 = DocCast(this.dataDoc.link_anchor_2);
         const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2;
-        return DocumentManager.Instance.getDocumentView(anchor_2, this.containerViewPath?.().lastElement());
+        return DocumentManager.Instance.getDocumentView(anchor_2, this.DocumentView?.().containerViewPath?.().lastElement());
     }
     screenBounds = () => {
         if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) {
@@ -66,7 +66,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     const a = (this.anchor1 ?? this.anchor2)!;
                     const b = (this.anchor2 ?? this.anchor1)!;
 
-                    const parxf = this.containerViewPath?.().lastElement().ComponentView as CollectionFreeFormView;
+                    const parxf = this.DocumentView?.().containerViewPath?.().lastElement().ComponentView as CollectionFreeFormView;
                     const this_xf = parxf?.screenToFreeformContentsXf ?? Transform.Identity; //this.ScreenToLocalTransform();
                     const a_invXf = a.screenToViewTransform().inverse();
                     const b_invXf = b.screenToViewTransform().inverse();
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 71c1b6a4c..7696a45a0 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -581,6 +581,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     }}>
                     <PDFViewer
                         {...this._props}
+                        pdfBox={this}
                         sidebarAddDoc={this.sidebarAddDocument}
                         addDocTab={this.sidebarAddDocTab}
                         layoutDoc={this.layoutDoc}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 61576a498..4ae12505e 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -11,7 +11,7 @@ import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { Transform } from '../../../util/Transform';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { DocFocusOptions, DocumentView } from '../DocumentView';
+import { DocFocusOptions, DocumentView, returnEmptyDocViewList } from '../DocumentView';
 import { FormattedTextBox } from './FormattedTextBox';
 
 var horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
@@ -178,6 +178,9 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
         ele && (ele.style.backgroundColor = 'orange');
     };
 
+    @computed get childContainerViewPath() {
+        return this._textBox.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
+    }
     componentWillUnmount = () => Object.values(this._disposers).forEach(disposer => disposer?.());
     isContentActive = () => this._props.tbox._props.isContentActive() || this._props.tbox.isAnyChildContentActive?.();
 
@@ -207,7 +210,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
                     isDocumentActive={returnFalse}
                     isContentActive={this.isContentActive}
                     styleProvider={this._textBox._props.styleProvider}
-                    containerViewPath={this._textBox.docViewPath ?? returnEmptyDoclist}
+                    containerViewPath={this.childContainerViewPath}
                     ScreenToLocalTransform={this.getDocTransform}
                     addDocTab={this._textBox._props.addDocTab}
                     pinToPres={returnFalse}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 3f2ab0ec5..dc9914637 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -148,7 +148,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     }
 
     createPivotForField = (e: React.MouseEvent) => {
-        let container = this._props.tbox.containerViewPath?.().lastElement();
+        let container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement();
         if (container) {
             const embedding = Doc.MakeEmbedding(container.Document);
             embedding._type_collection = CollectionViewType.Time;
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 8cf01b9de..4bc79176e 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -50,7 +50,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     // the presentation view that renders this slide
     @computed get presBoxView() {
-        return this.containerViewPath?.().lastElement()?.ComponentView as PresBox;
+        return this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as PresBox;
     }
 
     // the presentation view document that renders this slide
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 89e5944f2..01590749e 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -21,11 +21,12 @@ import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { LinkInfo } from '../nodes/LinkDocPreview';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { StyleProp, testDocProps } from '../StyleProvider';
+import { StyleProp } from '../StyleProvider';
 import { AnchorMenu } from './AnchorMenu';
 import { Annotation } from './Annotation';
 import { GPTPopup } from './GPTPopup/GPTPopup';
 import './PDFViewer.scss';
+import { PDFBox } from '../nodes/PDFBox';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
@@ -33,6 +34,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
 Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.0.379/build/pdf.worker.mjs';
 
 interface IViewerProps extends FieldViewProps {
+    pdfBox: PDFBox;
     Document: Doc;
     dataDoc: Doc;
     layoutDoc: Doc;
@@ -313,7 +315,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.DocumentView?.().ComponentView?.getAnchor!(true)!, false);
+                DocUtils.MakeLinkToActiveAudio(() => this._props.pdfBox.getAnchor(true)!, false);
                 this._scrollTimer = undefined;
             }, 200);
         }
@@ -419,7 +421,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     @action
     createTextAnnotation = (sel: Selection, selRange: Range) => {
         if (this._mainCont.current) {
-            this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToContentsTransform().RotateDeg)}deg)`;
+            this._mainCont.current.style.transform = `rotate(${NumCast(this._props.pdfBox.ScreenToLocalBoxXf().RotateDeg)}deg)`;
             const boundingRect = this._mainCont.current.getBoundingClientRect();
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
@@ -499,9 +501,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
-            const docProps = testDocProps(props) ? props : undefined;
             if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
-            const isInk = doc.layout_isSvg && !docProps?.LayoutTemplateString;
+            const isInk = doc.layout_isSvg && !props?.LayoutTemplateString;
             return isInk ? 'visiblePainted' : 'all';
         }
         return this._props.styleProvider?.(doc, props, property);
@@ -589,7 +590,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                             isNativeScaled={true}
                             annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)}
                             addDocument={this.addDocumentWrapper}
-                            docView={this._props.DocumentView!}
+                            docView={this._props.pdfBox.DocumentView!}
                             finishMarquee={this.finishMarquee}
                             savedAnnotations={this.savedAnnotations}
                             selectionText={this.selectionText}
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 188b2eba4..5828313c8 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -22,7 +22,7 @@ import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { CollectionLinearView } from '../collections/collectionLinear';
 import { DashboardView } from '../DashboardView';
 import { Colors } from '../global/globalEnums';
-import { DocumentViewInternal } from '../nodes/DocumentView';
+import { DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
 import { DefaultStyleProvider } from '../StyleProvider';
 import './TopBar.scss';
 
@@ -100,6 +100,7 @@ export class TopBar extends React.Component {
             <div className="collectionMenu-contMenuButtons" style={{ height: '100%' }}>
                 <CollectionLinearView
                     Document={selDoc}
+                    docViewPath={returnEmptyDocViewList}
                     fieldKey="data"
                     dropAction="embed"
                     styleProvider={DefaultStyleProvider}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index d726f7064..9c2350bd0 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1245,6 +1245,11 @@ export namespace Doc {
                   : undefined;
     }
 
+    export function toggleLockedPosition(doc: Doc) {
+        doc._lockedPosition = !doc._lockedPosition;
+        doc._pointerEvents = doc._lockedPosition ? 'none' : undefined;
+    }
+
     export function deiconifyView(doc: Doc) {
         StrCast(doc.layout_fieldKey).split('_')[1] === 'icon' && setNativeView(doc);
     }
-- 
cgit v1.2.3-70-g09d2


From af380979349308077e13fc12a2d09255b7f05f28 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 23 Jan 2024 16:11:42 -0500
Subject: reorganization of DocumentView, DocumentViewInternal and FieldView
 methods and props. fix for selection bug after following a link.  migrating
 to use [DocData] instad of GetProto()

---
 src/client/documents/Documents.ts                  |   15 +-
 src/client/util/CurrentUserUtils.ts                |    2 +-
 src/client/util/DocumentManager.ts                 |   19 +-
 src/client/util/DragManager.ts                     |    7 +-
 src/client/util/DropConverter.ts                   |    9 +-
 src/client/util/HypothesisUtils.ts                 |   18 +-
 src/client/util/LinkFollower.ts                    |    9 +-
 src/client/util/LinkManager.ts                     |   12 +-
 src/client/util/SearchUtil.ts                      |  155 +--
 src/client/util/SettingsManager.tsx                |    5 +-
 src/client/views/DashboardView.tsx                 |   12 +-
 src/client/views/DocComponent.tsx                  |  125 +-
 src/client/views/DocumentDecorations.tsx           |   16 +-
 src/client/views/InkingStroke.tsx                  |   14 +-
 src/client/views/LightboxView.tsx                  |    2 +-
 src/client/views/MainView.tsx                      |   29 +-
 src/client/views/MarqueeAnnotator.tsx              |    9 +-
 src/client/views/PropertiesView.tsx                |    5 +-
 src/client/views/ScriptBox.tsx                     |    5 +-
 src/client/views/SidebarAnnos.tsx                  |    9 +-
 src/client/views/StyleProvider.tsx                 |   12 +-
 src/client/views/TemplateMenu.tsx                  |    6 +-
 .../views/collections/CollectionCalendarView.tsx   |    4 +-
 .../views/collections/CollectionCarousel3DView.tsx |    7 +-
 .../views/collections/CollectionCarouselView.tsx   |    6 +-
 .../views/collections/CollectionDockingView.tsx    |   18 +-
 src/client/views/collections/CollectionMenu.tsx    |   25 +-
 .../views/collections/CollectionNoteTakingView.tsx |   20 +-
 .../collections/CollectionStackedTimeline.tsx      |   27 +-
 .../views/collections/CollectionStackingView.tsx   |   28 +-
 src/client/views/collections/CollectionSubView.tsx |   33 +-
 .../views/collections/CollectionTimeView.tsx       |    7 +-
 .../views/collections/CollectionTreeView.tsx       |    2 +-
 src/client/views/collections/CollectionView.tsx    |    7 +-
 src/client/views/collections/TabDocView.tsx        |   10 +-
 src/client/views/collections/TreeView.tsx          |   19 +-
 .../CollectionFreeFormLinkView.tsx                 |    6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   90 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |    7 +-
 .../collectionGrid/CollectionGridView.tsx          |    6 +-
 .../CollectionMulticolumnView.tsx                  |    4 +-
 .../CollectionMultirowView.tsx                     |    4 +-
 .../collectionMulticolumn/MulticolumnResizer.tsx   |    6 +-
 .../collectionMulticolumn/MultirowResizer.tsx      |    6 +-
 .../collectionSchema/CollectionSchemaView.tsx      |   10 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |   10 +-
 .../collectionSchema/SchemaTableCell.tsx           |    5 +-
 src/client/views/linking/LinkMenu.tsx              |    2 +-
 src/client/views/linking/LinkMenuGroup.tsx         |   12 +-
 src/client/views/newlightbox/NewLightboxView.tsx   |    2 +-
 src/client/views/nodes/AudioBox.tsx                |   15 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   10 +-
 src/client/views/nodes/ComparisonBox.tsx           |   15 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   16 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   29 +-
 src/client/views/nodes/DocumentIcon.tsx            |    2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   40 +-
 src/client/views/nodes/DocumentView.tsx            | 1214 ++++++++------------
 src/client/views/nodes/EquationBox.tsx             |    4 +-
 src/client/views/nodes/FieldView.tsx               |  109 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |    2 +-
 src/client/views/nodes/FunctionPlotBox.tsx         |    2 +-
 src/client/views/nodes/ImageBox.tsx                |   25 +-
 src/client/views/nodes/KeyValueBox.tsx             |    2 +-
 src/client/views/nodes/KeyValuePair.tsx            |    4 +-
 src/client/views/nodes/LabelBox.tsx                |   23 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |   10 +-
 src/client/views/nodes/LinkBox.tsx                 |    7 +-
 src/client/views/nodes/LinkDescriptionPopup.tsx    |   31 +-
 src/client/views/nodes/LinkDocPreview.tsx          |    6 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   16 +-
 src/client/views/nodes/MapBox/MapBox2.tsx          |   12 +-
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx |    5 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |   26 +-
 src/client/views/nodes/PDFBox.tsx                  |   28 +-
 .../views/nodes/RecordingBox/RecordingBox.tsx      |   12 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   13 +-
 src/client/views/nodes/ScriptingBox.tsx            |    8 +-
 src/client/views/nodes/VideoBox.tsx                |   42 +-
 src/client/views/nodes/WebBox.tsx                  |   20 +-
 src/client/views/nodes/calendarBox/CalendarBox.tsx |   10 +-
 .../views/nodes/formattedText/DashDocView.tsx      |   12 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   45 +-
 .../views/nodes/importBox/ImportElementBox.tsx     |    6 +-
 src/client/views/nodes/trails/PresBox.tsx          |   37 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |    2 +-
 src/client/views/pdf/PDFViewer.tsx                 |    9 +-
 src/client/views/search/SearchBox.tsx              |    8 +-
 src/client/views/selectedDoc/SelectedDocView.tsx   |    8 +-
 src/client/views/webcam/DashWebRTCVideo.tsx        |   16 +-
 src/fields/Doc.ts                                  |   52 +-
 91 files changed, 1304 insertions(+), 1527 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 40cffcf45..bcc016391 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -205,6 +205,7 @@ export class DocumentOptions {
     type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection
     _type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection
     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;
     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);
@@ -1454,17 +1455,17 @@ export namespace DocUtils {
                 TaskCompletionBox.popupY = showPopup[1] - 33;
                 TaskCompletionBox.taskCompleted = true;
 
-                LinkDescriptionPopup.popupX = showPopup[0];
-                LinkDescriptionPopup.popupY = showPopup[1];
-                LinkDescriptionPopup.descriptionPopup = true;
+                LinkDescriptionPopup.Instance.popupX = showPopup[0];
+                LinkDescriptionPopup.Instance.popupY = showPopup[1];
+                LinkDescriptionPopup.Instance.display = true;
 
                 const rect = document.body.getBoundingClientRect();
-                if (LinkDescriptionPopup.popupX + 200 > rect.width) {
-                    LinkDescriptionPopup.popupX -= 190;
+                if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
+                    LinkDescriptionPopup.Instance.popupX -= 190;
                     TaskCompletionBox.popupX -= 40;
                 }
-                if (LinkDescriptionPopup.popupY + 100 > rect.height) {
-                    LinkDescriptionPopup.popupY -= 40;
+                if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
+                    LinkDescriptionPopup.Instance.popupY -= 40;
                     TaskCompletionBox.popupY -= 40;
                 }
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 0b15e3add..c4af20b9b 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -275,7 +275,7 @@ export class CurrentUserUtils {
             {key: "Map",        creator: opts => Docs.Create.MapDocument([], opts),         opts: { _width: 800, _height: 600, _layout_fitWidth: true, }},
             {key: "Screengrab", creator: Docs.Create.ScreenshotDocument,                    opts: { _width: 400, _height: 200 }},
             {key: "WebCam",     creator: opts => Docs.Create.WebCamDocument("", opts),      opts: { _width: 400, _height: 200, recording:true, isSystem: true, cloneFieldFilter: new List<string>(["isSystem"]) }},
-            {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
+            {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title_custom: true,  waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts),   opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeView_HideUnrendered: true}},
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index e81a99e40..f730d17fe 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,6 +1,7 @@
+import { Howl } from 'howler';
 import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { AclAdmin, AclEdit, Animation } from '../../fields/DocSymbols';
+import { AclAdmin, AclEdit, Animation, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { listSpec } from '../../fields/Schema';
 import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
@@ -10,13 +11,13 @@ import { CollectionViewType } from '../documents/DocumentTypes';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { TabDocView } from '../views/collections/TabDocView';
 import { LightboxView } from '../views/LightboxView';
-import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
+import { FocusViewOptions } from '../views/nodes/FieldView';
 import { KeyValueBox } from '../views/nodes/KeyValueBox';
 import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
 import { PresBox } from '../views/nodes/trails';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
-import { Howl } from 'howler';
 
 export class DocumentManager {
     private static _instance: DocumentManager;
@@ -135,7 +136,7 @@ export class DocumentManager {
         });
         if (toReturn.length === 0) {
             DocumentManager.Instance.DocumentViews.forEach(view => {
-                if (Doc.GetProto(view.Document)?.[Id] === id) {
+                if (view.Document[DocData]?.[Id] === id) {
                     toReturn.push(view);
                 }
             });
@@ -236,7 +237,7 @@ export class DocumentManager {
     // shows a documentView by:
     // traverses down through the viewPath of contexts to the view:
     //           focusing on each context
-    public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => {
+    public showDocumentView = async (targetDocView: DocumentView, options: FocusViewOptions) => {
         const docViewPath = [...(targetDocView.containerViewPath?.() ?? []), targetDocView];
         let rootContextView = docViewPath.shift();
         await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false })));
@@ -252,7 +253,7 @@ export class DocumentManager {
     //           and finally restoring the targetDoc to the viewSpec specified by the last document which may either be the targetDoc, or a viewSpec that describes the targetDoc configuration
     public showDocument = async (
         targetDoc: Doc, // document to display
-        options: DocFocusOptions, // options for how to navigate to target
+        options: 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.
     ) => {
         Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
@@ -293,7 +294,7 @@ export class DocumentManager {
 
     focusViewsInPath = async (
         docView: DocumentView, //
-        options: DocFocusOptions,
+        options: FocusViewOptions,
         iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean }>
     ) => {
         let contextView: DocumentView | undefined; // view containing context that contains target
@@ -313,7 +314,7 @@ export class DocumentManager {
     };
 
     @action
-    restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: DocFocusOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+    restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: FocusViewOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
         if (viewSpec && docView) {
             //if (docView.ComponentView instanceof FormattedTextBox)
             //viewSpec !== docView.Document &&
@@ -337,7 +338,7 @@ export class DocumentManager {
         }
     }
 }
-export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
+export function DocFocusOrOpen(doc: Doc, options: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
     const func = () => {
         const cv = DocumentManager.Instance.getDocumentView(containingDoc);
         const dv = DocumentManager.Instance.getDocumentView(doc, cv);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 9ede18ed5..70c40c54e 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -13,6 +13,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
 import { SnappingManager } from './SnappingManager';
 import { UndoManager } from './UndoManager';
+import { DocData } from '../../fields/DocSymbols';
 const { default : { contextMenuZindex } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
 
 export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'inSame' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
@@ -222,7 +223,7 @@ export namespace DragManager {
                                   : docDragData.dropAction === 'add'
                                     ? d
                                     : docDragData.dropAction === 'proto'
-                                      ? Doc.GetProto(d)
+                                      ? d[DocData]
                                       : docDragData.dropAction === 'copy'
                                         ? (await Doc.MakeClone(d)).clone
                                         : d
@@ -250,9 +251,9 @@ export namespace DragManager {
     export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
         const finishDrag = (e: DragCompleteEvent) => {
             const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
-            params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
+            params.map(p => Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
             initialize?.(bd);
-            Doc.GetProto(bd)['onClick-paramFieldKeys'] = new List<string>(params);
+            bd[DocData]['onClick-paramFieldKeys'] = new List<string>(params);
             e.docDragData && (e.docDragData.droppedDocuments = [bd]);
             return e;
         };
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 2c371f28e..f62ec8f83 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,4 +1,5 @@
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
 import { ObjectField } from '../../fields/ObjectField';
 import { RichTextField } from '../../fields/RichTextField';
 import { listSpec } from '../../fields/Schema';
@@ -12,7 +13,7 @@ import { DragManager } from './DragManager';
 import { ScriptingGlobals } from './ScriptingGlobals';
 
 export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined, templateField: string = '') {
-    if (templateField) Doc.GetProto(doc).title = templateField; /// the title determines which field is being templated
+    if (templateField) doc[DocData].title = templateField; /// the title determines which field is being templated
     doc.isTemplateDoc = makeTemplate(doc, first, rename);
     return doc;
 }
@@ -33,7 +34,7 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = und
     let any = false;
     docs.forEach(d => {
         if (!StrCast(d.title).startsWith('-')) {
-            any = Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)) || any;
+            any = Doc.MakeMetadataFieldTemplate(d, layoutDoc[DocData]) || any;
         } else if (d.type === DocumentType.COL || d.data instanceof RichTextField) {
             any = makeTemplate(d, false) || any;
         }
@@ -41,12 +42,12 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = und
     if (first) {
         if (!docs.length) {
             // bcz: feels hacky : if the root level document has items, it's not a field template
-            any = Doc.MakeMetadataFieldTemplate(doc, Doc.GetProto(layoutDoc)) || any;
+            any = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData]) || any;
         }
     }
     if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) {
         if (!StrCast(layoutDoc.title).startsWith('-')) {
-            any = Doc.MakeMetadataFieldTemplate(layoutDoc, Doc.GetProto(layoutDoc));
+            any = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData]);
         }
     }
     rename && (doc.title = rename);
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index f46c2d431..c5f307f44 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -30,15 +30,15 @@ export namespace Hypothesis {
         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[] = [];
-        await SearchUtil.Search('web', true).then(
-            action(async (res: SearchUtil.DocSearchResult) => {
-                const docs = res.docs;
-                const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data);
-                filteredDocs.forEach(doc => {
-                    uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history?
-                });
-            })
-        );
+        // await SearchUtil.Search('web', true).then(
+        //     action(async (res: SearchUtil.DocSearchResult) => {
+        //         const docs = res.docs;
+        //         const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data);
+        //         filteredDocs.forEach(doc => {
+        //             uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history?
+        //         });
+        //     })
+        // );
 
         const onScreenResults = results.filter(doc => DocumentManager.Instance.getFirstDocumentView(doc));
         return onScreenResults.length ? onScreenResults[0] : results.length ? results[0] : undefined; // prioritize results that are currently on the screen
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 2df4d1ca8..20261859c 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,16 +1,17 @@
-import { action, observable, runInAction } from 'mobx';
+import { action, runInAction } from 'mobx';
 import { Doc, DocListCast, Field, FieldResult, Opt } from '../../fields/Doc';
 import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
-import { DocFocusOptions, OpenWhere } from '../views/nodes/DocumentView';
+import { OpenWhere } from '../views/nodes/DocumentView';
+import { FocusViewOptions } from '../views/nodes/FieldView';
 import { PresBox } from '../views/nodes/trails';
 import { DocumentManager } from './DocumentManager';
 import { LinkManager } from './LinkManager';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
-import { UndoManager } from './UndoManager';
 import { SnappingManager } from './SnappingManager';
+import { UndoManager } from './UndoManager';
 /*
  * link doc:
  * - link_anchor_1: doc
@@ -72,7 +73,7 @@ export class LinkFollower {
             if (target) {
                 const doFollow = (canToggle?: boolean) => {
                     const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
-                    const options: DocFocusOptions = {
+                    const options: FocusViewOptions = {
                         playAudio: BoolCast(srcAnchor.followLinkAudio),
                         playMedia: BoolCast(srcAnchor.followLinkVideo),
                         toggleTarget,
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index ccb3c6b98..353f28a92 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,7 +1,7 @@
 import { action, makeObservable, observable, observe, runInAction } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
-import { DirectLinks } from '../../fields/DocSymbols';
+import { DirectLinks, DocData } from '../../fields/DocSymbols';
 import { FieldLoader } from '../../fields/FieldLoader';
 import { List } from '../../fields/List';
 import { ProxyField } from '../../fields/Proxy';
@@ -58,8 +58,8 @@ export class LinkManager {
                                 link &&
                                     action(lAnchProtoProtos => {
                                         Doc.AddDocToList(Doc.UserDoc(), 'links', link);
-                                        lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].add(link);
-                                        lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].add(link);
+                                        lAnchs[0] && lAnchs[0][DocData][DirectLinks].add(link);
+                                        lAnchs[1] && lAnchs[1][DocData][DirectLinks].add(link);
                                     })
                             )
                         )
@@ -74,8 +74,8 @@ export class LinkManager {
                         Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) =>
                             Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then(
                                 action(lAnchProtoProtos => {
-                                    link && lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].delete(link);
-                                    link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].delete(link);
+                                    link && lAnchs[0] && lAnchs[0][DocData][DirectLinks].delete(link);
+                                    link && lAnchs[1] && lAnchs[1][DocData][DirectLinks].delete(link);
                                 })
                             )
                         )
@@ -162,7 +162,7 @@ export class LinkManager {
         return this.relatedLinker(anchor);
     } // finds all links that contain the given anchor
     public getAllDirectLinks(anchor?: Doc): Doc[] {
-        return anchor ? Array.from(Doc.GetProto(anchor)[DirectLinks]) : [];
+        return anchor ? Array.from(anchor[DirectLinks]) : [];
     } // finds all links that contain the given anchor
 
     relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index e51770c25..2cc64f415 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -1,10 +1,7 @@
-import * as rp from 'request-promise';
-import { DocServer } from '../DocServer';
 import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
-import { Utils } from '../../Utils';
-import { DocumentType } from '../documents/DocumentTypes';
 import { StrCast } from '../../fields/Types';
+import { DocumentType } from '../documents/DocumentTypes';
 
 export namespace SearchUtil {
     export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
@@ -110,154 +107,4 @@ export namespace SearchUtil {
             depth++;
         }
     }
-    export interface IdSearchResult {
-        ids: string[];
-        lines: string[][];
-        numFound: number;
-        highlighting: HighlightingResult | undefined;
-    }
-
-    export interface DocSearchResult {
-        docs: Doc[];
-        lines: string[][];
-        numFound: number;
-        highlighting: HighlightingResult | undefined;
-    }
-
-    export interface SearchParams {
-        hl?: string;
-        'hl.fl'?: string;
-        start?: number;
-        rows?: number;
-        fq?: string;
-        sort?: string;
-        allowEmbeddings?: boolean;
-        onlyEmbeddings?: boolean;
-        facet?: string;
-        'facet.field'?: string;
-    }
-    export function Search(query: string, returnDocs: true, options?: SearchParams): Promise<DocSearchResult>;
-    export function Search(query: string, returnDocs: false, options?: SearchParams): Promise<IdSearchResult>;
-    export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) {
-        query = query || '*'; //If we just have a filter query, search for * as the query
-        const rpquery = Utils.prepend('/dashsearch');
-        let replacedQuery = query.replace(/type_t:([^ )])/g, (substring, arg) => `{!join from=id to=proto_i}*:* AND ${arg}`);
-        if (options.onlyEmbeddings) {
-            const header = query.match(/_[atnb]?:/) ? replacedQuery : 'DEFAULT:' + replacedQuery;
-            replacedQuery = `{!join from=id to=proto_i}* AND ${header}`;
-        }
-        //console.log("Q: " + replacedQuery + " fq: " + options.fq);
-        const gotten = await rp.get(rpquery, { qs: { ...options, q: replacedQuery } });
-        const result: IdSearchResult = gotten.startsWith('<') ? { ids: [], docs: [], numFound: 0, lines: [] } : JSON.parse(gotten);
-        if (!returnDocs) {
-            return result;
-        }
-
-        const { ids, highlighting } = result;
-
-        const txtresult =
-            query !== '*' &&
-            JSON.parse(
-                await rp.get(Utils.prepend('/textsearch'), {
-                    qs: { ...options, q: query.replace(/^[ \+\?\*\|]*/, '') }, // a leading '+' leads to a server crash since findInFiles doesn't handle regex failures
-                })
-            );
-
-        const fileids = txtresult ? txtresult.ids : [];
-        const newIds: string[] = [];
-        const newLines: string[][] = [];
-        // bcz: we stopped storing fileUpload id's, so this won't find anything
-        // if (fileids) {
-        //     await Promise.all(
-        //         fileids.map(async (tr: string, i: number) => {
-        //             const docQuery = 'fileUpload_t:' + tr.substr(0, 7); //If we just have a filter query, search for * as the query
-        //             const docResult = JSON.parse(await rp.get(Utils.prepend('/dashsearch'), { qs: { ...options, q: docQuery } }));
-        //             newIds.push(...docResult.ids);
-        //             newLines.push(...docResult.ids.map((dr: any) => txtresult.lines[i]));
-        //         })
-        //     );
-        // }
-
-        const theDocs: Doc[] = [];
-        const theLines: string[][] = [];
-        const textDocMap = await DocServer.GetRefFields(newIds);
-        const textDocs = newIds.map((id: string) => textDocMap[id]).map(doc => doc as Doc);
-        for (let i = 0; i < textDocs.length; i++) {
-            const testDoc = textDocs[i];
-            if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) {
-                theDocs.push(Doc.GetProto(testDoc));
-                theLines.push(newLines[i].map(line => line.replace(query, query.toUpperCase())));
-            }
-        }
-
-        const docMap = await DocServer.GetRefFields(ids);
-        const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc);
-        for (let i = 0; i < ids.length; i++) {
-            const testDoc = docs[i];
-            if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowEmbeddings || testDoc.proto === undefined || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) {
-                theDocs.push(testDoc);
-                theLines.push([]);
-            } else {
-                result.numFound--;
-            }
-        }
-
-        return { docs: theDocs, numFound: Math.max(0, result.numFound), highlighting, lines: theLines };
-    }
-
-    export async function GetEmbeddingsOfDocument(doc: Doc): Promise<Doc[]>;
-    export async function GetEmbeddingsOfDocument(doc: Doc, returnDocs: false): Promise<string[]>;
-    export async function GetEmbeddingsOfDocument(doc: Doc, returnDocs = true): Promise<Doc[] | string[]> {
-        const proto = Doc.GetProto(doc);
-        const protoId = proto[Id];
-        if (returnDocs) {
-            return (await Search('', returnDocs, { fq: `proto_i:"${protoId}"`, allowEmbeddings: true })).docs;
-        } else {
-            return (await Search('', returnDocs, { fq: `proto_i:"${protoId}"`, allowEmbeddings: true })).ids;
-        }
-        // return Search(`{!join from=id to=proto_i}id:${protoId}`, true);
-    }
-
-    export async function GetViewsOfDocument(doc: Doc): Promise<Doc[]> {
-        const results = await Search('', true, { fq: `proto_i:"${doc[Id]}"` });
-        return results.docs;
-    }
-
-    export async function GetContextsOfDocument(doc: Doc): Promise<{ contexts: Doc[]; embeddingContexts: Doc[] }> {
-        const docContexts = (await Search('', true, { fq: `data_l:"${doc[Id]}"` })).docs;
-        const embeddings = await GetEmbeddingsOfDocument(doc, false);
-        const embeddingContexts = await Promise.all(embeddings.map(doc => Search('', true, { fq: `data_l:"${doc}"` })));
-        const contexts = { contexts: docContexts, embeddingContexts: [] as Doc[] };
-        embeddingContexts.forEach(result => contexts.embeddingContexts.push(...result.docs));
-        return contexts;
-    }
-
-    export async function GetContextIdsOfDocument(doc: Doc): Promise<{ contexts: string[]; embeddingContexts: string[] }> {
-        const docContexts = (await Search('', false, { fq: `data_l:"${doc[Id]}"` })).ids;
-        const embeddings = await GetEmbeddingsOfDocument(doc, false);
-        const embeddingContexts = await Promise.all(embeddings.map(doc => Search('', false, { fq: `data_l:"${doc}"` })));
-        const contexts = { contexts: docContexts, embeddingContexts: [] as string[] };
-        embeddingContexts.forEach(result => contexts.embeddingContexts.push(...result.ids));
-        return contexts;
-    }
-
-    export async function GetAllDocs() {
-        const query = '*';
-        const response = await rp.get(Utils.prepend('/dashsearch'), {
-            qs: { start: 0, rows: 10000, q: query },
-        });
-        const result: IdSearchResult = JSON.parse(response);
-        const { ids, numFound, highlighting } = result;
-        const docMap = await DocServer.GetRefFields(ids);
-        const docs: Doc[] = [];
-        for (const id of ids) {
-            const field = docMap[id];
-            if (field instanceof Doc) {
-                docs.push(field);
-            }
-        }
-        return docs;
-        // const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc);
-        // return docs as Doc[];
-    }
 }
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 0233c4051..5bf9e5b00 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -5,13 +5,13 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { BsGoogle } from 'react-icons/bs';
 import { FaFillDrip, FaPalette } from 'react-icons/fa';
+import { Utils, addStyleSheet, addStyleSheetRule } from '../../Utils';
 import { Doc, Opt } from '../../fields/Doc';
 import { DashVersion } from '../../fields/DocSymbols';
 import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types';
-import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
-import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 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';
@@ -47,7 +47,6 @@ export class SettingsManager extends React.Component<{}> {
     @observable activeTab = 'Accounts';
 
     @observable public propertiesWidth: number = 0;
-    @observable public headerBarHeight: number = 0;
 
     constructor(props: {}) {
         super(props);
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 523721b84..472d419fc 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaPlus } from 'react-icons/fa';
 import { Doc, DocListCast } from '../../fields/Doc';
-import { AclPrivate, DocAcl } from '../../fields/DocSymbols';
+import { AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
@@ -64,7 +64,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
 
     getDashboards = (whichGroup: DashboardGroup) => {
         if (whichGroup === DashboardGroup.MyDashboards) {
-            return DocListCast(Doc.MyDashboards.data).filter(dashboard => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail);
+            return DocListCast(Doc.MyDashboards.data).filter(dashboard => dashboard[DocData].author === Doc.CurrentUserEmail);
         }
         return DocListCast(Doc.MySharedDocs.data_dashboards).filter(doc => doc.dockingConfig);
     };
@@ -171,7 +171,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
                                               }
                                           />
                                           <div className="info">
-                                              <EditableText type={Type.PRIM} color={color} val={StrCast(dashboard.title)} setVal={val => (Doc.GetProto(dashboard).title = val)} />
+                                              <EditableText type={Type.PRIM} color={color} val={StrCast(dashboard.title)} setVal={val => (dashboard[DocData].title = val)} />
                                               {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? <div>unviewed</div> : <div></div>}
                                               <div
                                                   className="more"
@@ -353,7 +353,7 @@ export class DashboardView extends ObservableReactComponent<{}> {
                 },
             ],
         };
-        if (dashboard.dockingConfig && dashboard.dockingConfig !== Doc.GetProto(dashboard).dockingConfig) dashboard.dockingConfig = JSON.stringify(reset);
+        if (dashboard.dockingConfig && dashboard.dockingConfig !== dashboard[DocData].dockingConfig) dashboard.dockingConfig = JSON.stringify(reset);
         else Doc.SetInPlace(dashboard, 'dockingConfig', JSON.stringify(reset), true);
         return reset;
     };
@@ -391,9 +391,9 @@ export class DashboardView extends ObservableReactComponent<{}> {
         Doc.ActivePresentation = undefined;
     };
 
-    public static SetupDashboardCalendars(dashboardDoc: Doc){
+    public static SetupDashboardCalendars(dashboardDoc: Doc) {
         // this section is creating the button document itself  ===  myTrails = new Button
-        
+
         //  create a a list of calendars (as a CalendarCollectionDocument) and store it on the new dashboard
         const reqdOpts: DocumentOptions = {
             title: 'My Calendars',
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 6aba4a042..0e5a4f013 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -2,22 +2,62 @@ import { action, computed, makeObservable, observable } from 'mobx';
 import * as React from 'react';
 import { returnFalse } from '../../Utils';
 import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
 import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
-import { Transform } from '../util/Transform';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
-import { DocumentView } from './nodes/DocumentView';
+import { FieldViewProps, FocusViewOptions } from './nodes/FieldView';
+import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { PinProps } from './nodes/trails';
+import { RefField } from '../../fields/RefField';
 
 /**
- * DocComponent returns a generic React base class used by Doc views (not the 'Box' views that render the contents of Doc views)
- *    (e.g.,CollectionFreeFormDocumentView, DocumentViewInternal)
- *
+ * Shared interface among all viewBox'es (ie, react classes that render the contents of a Doc)
+ * Many of these methods only make sense for specific viewBox'es, but they should be written to
+ * be as general as possible
+ */
+export interface ViewBoxInterface {
+    fieldKey?: string;
+    annotationKey?: string;
+    updateIcon?: () => void; // updates the icon representation of the document
+    getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
+    restoreView?: (viewSpec: Doc) => boolean;
+    scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: FocusViewOptions) => Opt<number>; // returns the duration of the focus
+    brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms)
+    getView?: (doc: Doc, options: FocusViewOptions) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
+    addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
+    addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
+    select?: (ctrlKey: boolean, shiftKey: boolean) => void;
+    focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt<number>;
+    isAnyChildContentActive?: () => boolean; // is any child content of the document active
+    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)
+    playFrom?: (time: number, endTime?: number) => void;
+    Pause?: () => void; // pause a media document (eg, audio/video)
+    IsPlaying?: () => boolean; // is a media document playing
+    TogglePause?: (keep?: boolean) => void; // toggle media document playing state
+    setFocus?: () => void; // sets input focus to the componentView
+    setData?: (data: Field | Promise<RefField | undefined>) => boolean;
+    componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
+    dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
+    incrementalRendering?: () => void;
+    infoUI?: () => JSX.Element | null;
+    screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
+    ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
+    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;
+}
+/**
+ * DocComponent returns a React base class used by Doc views with accessors for unpacking he Document,layoutDoc, and dataDoc's
+ *  (note: this should not be used for the 'Box' views that render the contents of Doc views)
+ *  Example derived views: CollectionFreeFormDocumentView, DocumentView, DocumentViewInternal)
  * */
 export interface DocComponentProps {
     Document: Doc;
@@ -26,7 +66,7 @@ export interface DocComponentProps {
 }
 export function DocComponent<P extends DocComponentProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
-        constructor(props: any) {
+        constructor(props: P) {
             super(props);
             makeObservable(this);
         }
@@ -41,28 +81,25 @@ export function DocComponent<P extends DocComponentProps>() {
         }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() {
-            return this._props.Document[DocData];
+            return this.Document[DocData];
         }
     }
     return Component;
 }
 
 /**
- *  ViewBoxBaseComponent  -  base class for non-annotatable views that render the interior contents of a DocumentView
- *                           (e.g. InkingStroke, ColorBox)
+ *   base class for non-annotatable views that render the interior contents of a DocumentView.
+ *   this unpacks the Document/layout/data docs as well as the fieldKey being rendered,
+ *   and provides accessors for DocumentView and ScreenToLocalBoxXf
+ *   Example views include: InkingStroke, FontIconBox, EquationBox, etc
  */
-export interface ViewBoxBaseProps {
-    Document: Doc;
-    TemplateDataDocument?: Doc;
-    DocumentView?: () => DocumentView;
-    fieldKey: string;
-    isSelected: () => boolean;
-    isContentActive: () => boolean | undefined;
-    ScreenToLocalTransform: () => Transform;
-    renderDepth: number;
-}
-export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
+export function ViewBoxBaseComponent<P extends FieldViewProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
+        constructor(props: P) {
+            super(props);
+            makeObservable(this);
+        }
+
         ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
 
         get DocumentView() {
@@ -89,36 +126,27 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
 }
 
 /**
- * DocAnnotatableComponent - base class for annotatable views that render the interior contents of a DocumentView
- *                       (e.g., PdfBox, ImageBox)
- * These views should be interactive (respond to pointerEvents) when their conatainer DocumentView is selected
+ * base class for annotatable views that render the interior contents of a DocumentView
+ * This does what ViewBoxBaseComponent does and additionally provides accessor for the
+ * field key where annotations are stored as well as add/move/remove methods for handing
+ * annotations.
+ * This also provides methods to determine when the contents should be interactive
+ * (respond to pointerEvents) such as when the DocumentView container is selected or a
+ * peer child of the container is selected
+ * Example views include: PDFBox, ImageBox, MapBox, etc
  */
-export interface ViewBoxAnnotatableProps {
-    Document: Doc;
-    TemplateDataDocument?: Doc;
-    DocumentView?: () => DocumentView;
-    fieldKey: string;
-    filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
-    isContentActive: () => boolean | undefined;
-    select: (isCtrlPressed: boolean) => void;
-    whenChildContentsActiveChanged: (isActive: boolean) => void;
-    ScreenToLocalTransform: () => Transform;
-    isSelected: () => boolean;
-    renderDepth: number;
-    isAnnotationOverlay?: boolean;
-}
-export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() {
+export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
-        constructor(props: any) {
+        @observable _annotationKeySuffix = () => 'annotations';
+        @observable _isAnyChildContentActive = false;
+
+        constructor(props: P) {
             super(props);
             makeObservable(this);
         }
 
         ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
 
-        @observable _annotationKeySuffix = () => 'annotations';
-        @observable _isAnyChildContentActive = false;
-
         get DocumentView() {
             return this._props.DocumentView;
         }
@@ -139,9 +167,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
         @computed get fieldKey() {
             return this._props.fieldKey;
         }
-
-        isAnyChildContentActive = () => this._isAnyChildContentActive;
-
         @computed public get annotationKey() {
             return this.fieldKey + (this._annotationKeySuffix() ? '_' + this._annotationKeySuffix() : '');
         }
@@ -162,7 +187,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                 toRemove.forEach(doc => {
                     leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
                     Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
-                    Doc.RemoveDocFromList(Doc.GetProto(doc), 'proto_embeddings', doc);
+                    Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', doc);
                     doc.embedContainer = undefined;
                     if (recent) {
                         doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
@@ -210,8 +235,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                 if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) {
                     added.forEach(doc => {
                         doc._dragOnlyWithinContainer = undefined;
-                        if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.Document;
-                        else Doc.GetProto(doc).annotationOn = undefined;
+                        if (annotationKey ?? this._annotationKeySuffix()) doc[DocData].annotationOn = this.Document;
+                        else doc[DocData].annotationOn = undefined;
                         Doc.SetContainer(doc, this.Document);
                         inheritParentAcls(targetDataDoc, doc, true);
                     });
@@ -225,6 +250,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             return true;
         };
 
+        isAnyChildContentActive = () => this._isAnyChildContentActive;
+
         whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
     }
     return Component;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8db972ef0..5ef62b2c5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -8,7 +8,7 @@ import { FaUndo } from 'react-icons/fa';
 import { Utils, emptyFunction, lightOrDark, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit } from '../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols';
 import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
@@ -101,7 +101,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             { x: 0, y: 0, r: 0, b: 0 }
             : SelectionManager.Views
                 .filter(dv => dv._props.renderDepth > 0)
-                .map(dv => dv.getBounds())
+                .map(dv => dv.getBounds)
                 .reduce((bounds, rect) => !rect ? bounds
                             : { x: Math.min(rect.left, bounds.x),
                                 y: Math.min(rect.top, bounds.y),
@@ -203,7 +203,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const containers = new Set<Doc | undefined>();
         SelectionManager.Views.forEach(v => containers.add(DocCast(v.Document.embedContainer)));
         if (containers.size > 1) return false;
-        const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
+        const { left, top } = dragDocView.getBounds || { left: 0, top: 0 };
         const dragData = new DragManager.DocumentDragData(
             SelectionManager.Views.map(dv => dv.Document),
             dragDocView._props.dropAction
@@ -541,17 +541,17 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             }
 
             if (['right', 'left'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeWidth(doc)) {
-                const setData = Doc.NativeWidth(Doc.GetProto(doc)) === doc.nativeWidth;
+                const setData = Doc.NativeWidth(doc[DocData]) === doc.nativeWidth;
                 doc.nativeWidth = scale.x * Doc.NativeWidth(doc);
-                if (setData) Doc.SetNativeWidth(Doc.GetProto(doc), NumCast(doc.nativeWidth));
+                if (setData) Doc.SetNativeWidth(doc[DocData], NumCast(doc.nativeWidth));
                 if (doc.layout_reflowVertical && !NumCast(doc.nativeHeight)) {
                     doc._nativeHeight = (initHeight / initWidth) * nwidth; // initializes the nativeHeight for a PDF
                 }
             }
             if (['bottom', 'top'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeHeight(doc)) {
-                const setData = Doc.NativeHeight(Doc.GetProto(doc)) === doc.nativeHeight;
+                const setData = Doc.NativeHeight(doc[DocData]) === doc.nativeHeight;
                 doc._nativeHeight = scale.y * Doc.NativeHeight(doc);
-                if (setData) Doc.SetNativeHeight(Doc.GetProto(doc), NumCast(doc._nativeHeight));
+                if (setData) Doc.SetNativeHeight(doc[DocData], NumCast(doc._nativeHeight));
             }
 
             doc._width = Math.max(1, NumCast(doc._width) * scale.x);
@@ -601,7 +601,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         this._inkDragDocs
             .map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
             .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
-                Doc.GetProto(doc).data = new InkField(inkPts.map(
+                doc[DocData].data = new InkField(inkPts.map(
                     (ipt) => ({// (new x — oldx) + newWidth * (oldxpoint /oldWidth)
                         X: NumCast(doc.x) - x + (NumCast(doc.width) * ipt.X) / width,
                         Y: NumCast(doc.y) - y + (NumCast(doc.height) * ipt.Y) / height,
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 11b05352d..92644d3c5 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -34,21 +34,19 @@ import { InteractionUtils } from '../util/InteractionUtils';
 import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
 import { ContextMenu } from './ContextMenu';
-import { ViewBoxBaseComponent, ViewBoxBaseProps } from './DocComponent';
+import { ViewBoxBaseComponent, ViewBoxInterface } from './DocComponent';
 import { Colors } from './global/globalEnums';
 import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
 import './InkStroke.scss';
 import { InkStrokeProperties } from './InkStrokeProperties';
 import { InkTangentHandles } from './InkTangentHandles';
-import { DocComponentView } from './nodes/DocumentView';
 import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from './nodes/trails';
 import { StyleProp } from './StyleProvider';
-import { Transform } from '../util/Transform';
 const { default: { INK_MASK_SIZE } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 @observer
-export class InkingStroke extends ViewBoxBaseComponent<ViewBoxBaseProps & FieldViewProps>() {
+export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() implements ViewBoxInterface {
     static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(InkingStroke, fieldStr);
@@ -64,7 +62,7 @@ export class InkingStroke extends ViewBoxBaseComponent<ViewBoxBaseProps & FieldV
     @observable _nearestScrPt?: { X: number; Y: number }; // nearst screen point on the ink stroke ""
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         this._disposers.selfDisper = reaction(
             () => this._props.isSelected(), // react to stroke being deselected by turning off ink handles
             selected => !selected && (InkStrokeProperties.Instance._controlButton = false)
@@ -332,8 +330,8 @@ export class InkingStroke extends ViewBoxBaseComponent<ViewBoxBaseProps & FieldV
         );
     };
 
-    _subContentView: DocComponentView | undefined;
-    setSubContentView = (doc: DocComponentView) => (this._subContentView = doc);
+    _subContentView: ViewBoxInterface | undefined;
+    setSubContentView = (doc: ViewBoxInterface) => (this._subContentView = doc);
     @computed get fillColor() {
         const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
         return isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FillColor) ?? 'transparent';
@@ -462,7 +460,7 @@ export class InkingStroke extends ViewBoxBaseComponent<ViewBoxBaseProps & FieldV
                         <FormattedTextBox
                             {...this._props}
                             setHeight={undefined}
-                            setContentView={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
+                            setContentViewBox={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
                             yPadding={10}
                             xPadding={10}
                             fieldKey="text"
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 3b86d24a6..530fd3121 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -267,7 +267,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                             addDocTab={this.AddDocTab}
                             pinToPres={TabDocView.PinDoc}
                             bringToFront={emptyFunction}
-                            onBrowseClick={DocumentView.exploreMode}
+                            onBrowseClickScript={DocumentView.exploreMode}
                             focus={emptyFunction}
                         />
                     </GestureOverlay>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d700ea020..9061e98d4 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -15,6 +15,7 @@ import { DocServer } from '../DocServer';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
+import { CalendarManager } from '../util/CalendarManager';
 import { CaptureManager } from '../util/CaptureManager';
 import { DocumentManager } from '../util/DocumentManager';
 import { DragManager } from '../util/DragManager';
@@ -31,7 +32,6 @@ import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
 import { ReportManager } from '../util/reportManager/ReportManager';
 import { ComponentDecorations } from './ComponentDecorations';
-import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
 import { ContextMenu } from './ContextMenu';
 import { DashboardView } from './DashboardView';
 import { DictationOverlay } from './DictationOverlay';
@@ -55,11 +55,13 @@ import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOpti
 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 { ImageBox } 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 { TaskCompletionBox } from './nodes/TaskCompletedBox';
 import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
@@ -70,9 +72,8 @@ import { PresBox } from './nodes/trails';
 import { AnchorMenu } from './pdf/AnchorMenu';
 import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
 import { TopBar } from './topbar/TopBar';
+import { DocData } from '../../fields/DocSymbols';
 const { default: { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
-import { DirectionsAnchorMenu } from './nodes/MapBox/DirectionsAnchorMenu';
-import { CalendarManager } from '../util/CalendarManager';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -130,9 +131,19 @@ export class MainView extends ObservableReactComponent<{}> {
     @computed public get mainFreeform(): Opt<Doc> {
         return (docs => (docs?.length > 1 ? docs[1] : undefined))(DocListCast(this.mainContainer!.data));
     }
+    @observable public headerBarHeight: number = 0;
+    headerBarHeightFunc = () => this.headerBarHeight;
 
+    @action
+    toggleTopBar = () => {
+        if (this.headerBarHeight > 0) {
+            this.headerBarHeight = 0;
+        } else {
+            this.headerBarHeight = 60;
+        }
+    };
     headerBarDocWidth = () => this.mainDocViewWidth();
-    headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.Instance?.headerBarHeight ?? 0);
+    headerBarDocHeight = () => (this._hideUI ? 0 : this.headerBarHeight ?? 0);
     topMenuHeight = () => (this._hideUI ? 0 : 35);
     topMenuWidth = returnZero; // value is ignored ...
     leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', '')));
@@ -630,9 +641,11 @@ export class MainView extends ObservableReactComponent<{}> {
         );
     }
     @computed get mainDocView() {
+        const headerBar = this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView;
+        console.log('Header = ' + this._hideUI + ' ' + this.headerBarDocHeight?.() + ' ' + headerBar);
         return (
             <>
-                {this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView}
+                {headerBar}
                 <DocumentView
                     key="main"
                     Document={this.mainContainer!}
@@ -783,7 +796,7 @@ export class MainView extends ObservableReactComponent<{}> {
 
     @action
     selectMenu = (button: Doc) => {
-        const title = StrCast(Doc.GetProto(button).title);
+        const title = StrCast(button[DocData].title);
         const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title;
         this.closeFlyout();
         if (willOpen) {
@@ -998,7 +1011,7 @@ export class MainView extends ObservableReactComponent<{}> {
                 <DocumentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfSidebarDoc} PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} />
                 <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
                 {this._hideUI ? null : <TopBar />}
-                {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
+                <LinkDescriptionPopup />
                 {DocButtonState.Instance.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocButtonState.Instance.LinkEditorDocView = undefined))} docView={DocButtonState.Instance.LinkEditorDocView} /> : null}
                 {LinkInfo.Instance?.LinkInfo ? <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> : null}
 
@@ -1008,7 +1021,7 @@ export class MainView extends ObservableReactComponent<{}> {
                         default:
                         case 'dashboard': return (<>
                                         <div key="dashdiv" style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}>
-                                            <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
+                                            <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} toggleTopBar={this.toggleTopBar} topBarHeight={this.headerBarHeightFunc}/>
                                         </div>
                                         {this.mainDashboardArea}
                                     </> );
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index d0516336c..a4303c3aa 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -118,7 +118,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             })
         );
 
-        const textRegionAnnoProto = Doc.GetProto(textRegionAnno);
+        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);
@@ -226,9 +226,10 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
                   DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                       dragComplete: e => {
                           if (!e.aborted && e.linkDocument) {
-                              Doc.GetProto(e.linkDocument).link_relationship = 'cropped image';
-                              Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.Document.title;
-                              Doc.GetProto(e.linkDocument).link_displayLine = false;
+                              const linkDocData = e.linkDocument[DocData];
+                              linkDocData.link_relationship = 'cropped image';
+                              linkDocData.title = 'crop: ' + this.props.Document.title;
+                              linkDocData.link_displayLine = false;
                           }
                       },
                   });
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 13057ffbf..952c8f0b4 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -37,7 +37,8 @@ import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
 import { PropertiesSection } from './PropertiesSection';
 import './PropertiesView.scss';
 import { DefaultStyleProvider } from './StyleProvider';
-import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
+import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { StyleProviderFuncType } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
 const _global = (window /* browser */ || global) /* node */ as any;
@@ -45,7 +46,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
 interface PropertiesViewProps {
     width: number;
     height: number;
-    styleProvider?: StyleProviderFunc;
+    styleProvider?: StyleProviderFuncType;
     addDocTab: (doc: Doc, where: OpenWhere) => boolean;
 }
 
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 086f40e96..623201ed1 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -11,6 +11,7 @@ import { EditableView } from './EditableView';
 import { DocumentIconContainer } from './nodes/DocumentIcon';
 import { OverlayView } from './OverlayView';
 import './ScriptBox.scss';
+import { DocData } from '../../fields/DocSymbols';
 
 export interface ScriptBoxProps {
     onSave: (text: string, onError: (error: string) => void) => void;
@@ -102,7 +103,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
                 onCancel={overlayDisposer}
                 onSave={(text, onError) => {
                     if (!text) {
-                        Doc.GetProto(doc)[fieldKey] = undefined;
+                        doc[DocData][fieldKey] = undefined;
                     } else {
                         const script = CompileScript(text, {
                             params: { this: Doc.name, ...contextParams },
@@ -125,7 +126,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
                         div.innerHTML = 'button';
                         params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, (button: Doc) => {}, clientX, clientY);
 
-                        Doc.GetProto(doc)[fieldKey] = new ScriptField(script);
+                        doc[DocData][fieldKey] = new ScriptField(script);
                         overlayDisposer();
                     }
                 }}
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 0f4a4260c..1e9272e93 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -18,6 +18,7 @@ import { StyleProp } from './StyleProvider';
 import { CollectionStackingView } from './collections/CollectionStackingView';
 import { FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { DocData } from '../../fields/DocSymbols';
 
 interface ExtraProps {
     fieldKey: string;
@@ -90,7 +91,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
             .map(data => {
                 const key = data.split(':')[0];
                 const val = Field.Copy(this.allMetadata.get(key));
-                Doc.GetProto(target)[key] = val;
+                target[DocData][key] = val;
                 return {
                     type: 'dashField',
                     attrs: { fieldKey: key, docId: '', hideKey: false, editable: true },
@@ -98,7 +99,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                 };
             });
 
-        if (!anchor.text) Doc.GetProto(anchor).text = '-selection-';
+        if (!anchor.text) anchor[DocData].text = '-selection-';
         const textLines: any = [
             {
                 type: 'paragraph',
@@ -133,7 +134,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
         };
         if (taggedContent.length) textLines.push(metadatatext);
         if (textLines.length) {
-            Doc.GetProto(target).text = new RichTextField(
+            target[DocData].text = new RichTextField(
                 JSON.stringify({
                     doc: {
                         type: 'doc',
@@ -234,7 +235,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                 <div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
                     <CollectionStackingView
                         {...this._props}
-                        setContentView={emptyFunction}
+                        setContentViewBox={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
                         ref={this._stackRef}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 1cb5ff249..2aba439c8 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -24,6 +24,7 @@ import { FieldViewProps } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PropertiesView } from './PropertiesView';
 import './StyleProvider.scss';
+import { DocumentViewProps } from './nodes/DocumentView';
 
 export enum StyleProp {
     TreeViewIcon = 'treeView_Icon',
@@ -65,13 +66,12 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
 
 // 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>, property: string): any {
+export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & DocumentViewProps>, property: string): any {
     const remoteDocHeader = 'author;author_date;noMargin';
     const isCaption = property.includes(':caption');
     const isAnchor = property.includes(':anchor');
     const isNonTransparent = property.includes(':nonTransparent');
     const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent');
-    const isContent = property.includes(':content');
     const isAnnotated = property.includes(':annotated');
     const isInk = () => doc?._layout_isSvg && !props?.LayoutTemplateString;
     const isOpen = property.includes(':open');
@@ -125,7 +125,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
         case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)));
         case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight)));
         case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent'));
-        case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
+        case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
         case StyleProp.TitleHeight:return (props?.ScreenToLocalTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30);
         case StyleProp.ShowTitle:
             return (
@@ -244,7 +244,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
                         ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (props?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : lockedPosition()
                         ? undefined // if it's a background & has a cluster color, make the shadow spread really big
-                        : StrCast(doc.layout_fieldKey).includes('_inline')  // if doc is an inline document in a text box
+                        : fieldKey.includes('_inline')  // if doc is an inline document in a text box
                         ? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0vw 0vw 0.1vw')}`
                         : DocCast(doc.embedContainer)?.type=== DocumentType.RTF // if doc is embedded in a text document (but not an inline)
                         ? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
@@ -316,11 +316,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps>,
             };
             const audio = () => {
                 const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped');
-                const audioAnnosCount = (doc: Doc) => StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations']).length;
+                const audioAnnosCount = (doc: Doc) => StrListCast(doc[fieldKey + 'audioAnnotations']).length;
                 if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null;
                 const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' };
                 return (
-                    <Tooltip title={<div>{StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}</div>}>
+                    <Tooltip title={<div>{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}</div>}>
                         <div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.playAnnotation()}>
                             <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon={'file-audio'} size="sm" />
                         </div>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e5154efcb..ba7120b93 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -11,9 +11,10 @@ import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { Transform } from '../util/Transform';
 import { undoBatch } from '../util/UndoManager';
 import { CollectionTreeView } from './collections/CollectionTreeView';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, returnEmptyDocViewList } from './nodes/DocumentView';
 import { DefaultStyleProvider } from './StyleProvider';
 import './TemplateMenu.scss';
+import { DocData } from '../../fields/DocSymbols';
 
 @observer
 class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> {
@@ -79,7 +80,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     };
     componentDidMount() {
         !this._addedKeys && (this._addedKeys = new ObservableSet());
-        [...Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].Document))), ...Array.from(Object.keys(this.props.docViews[0].Document))]
+        [...Array.from(Object.keys(this.props.docViews[0].Document[DocData])), ...Array.from(Object.keys(this.props.docViews[0].Document))]
             .filter(key => key.startsWith('layout_'))
             .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', ''))));
     }
@@ -113,6 +114,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 {templateMenu}
                 <CollectionTreeView
                     Document={Doc.MyTemplates}
+                    docViewPath={returnEmptyDocViewList}
                     styleProvider={DefaultStyleProvider}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index 4c0a917f5..cbcc980a9 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -62,7 +62,7 @@ export class CollectionCalendarView extends CollectionSubView() {
     screenToLocalTransform = () =>
         this._props
             .ScreenToLocalTransform()
-            .translate(Doc.NativeWidth(this._props.Document), 0)
+            .translate(Doc.NativeWidth(this.Document), 0)
             .scale(this._props.NativeDimScaling?.() || 1);
 
     get calendarsKey() {
@@ -74,7 +74,7 @@ export class CollectionCalendarView extends CollectionSubView() {
             <div className="collectionCalendarView">
                 <CollectionStackingView
                     {...this._props}
-                    setContentView={emptyFunction}
+                    setContentViewBox={emptyFunction}
                     ref={this._stackRef}
                     PanelHeight={this.panelHeight}
                     PanelWidth={this._props.PanelWidth}
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 8e072e235..2d2cb4860 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -10,7 +10,8 @@ import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
+import { DocumentView } from '../nodes/DocumentView';
+import { FocusViewOptions } from '../nodes/FieldView';
 import './CollectionCarousel3DView.scss';
 import { CollectionSubView } from './CollectionSubView';
 const { default: { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
@@ -49,7 +50,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
             .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
             .scale(1 / this.centerScale);
 
-    focus = (anchor: Doc, options: DocFocusOptions) => {
+    focus = (anchor: Doc, options: FocusViewOptions) => {
         const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
         if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return;
         options.didMove = true;
@@ -70,7 +71,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
                     layout_fitWidth={undefined}
-                    onDoubleClick={this.onChildDoubleClick}
+                    onDoubleClickScript={this.onChildDoubleClick}
                     renderDepth={this._props.renderDepth + 1}
                     LayoutTemplate={this._props.childLayoutTemplate}
                     LayoutTemplateString={this._props.childLayoutString}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 208fc45b5..79f5baabe 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -66,9 +66,9 @@ export class CollectionCarouselView extends CollectionSubView() {
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
                         layout_fitWidth={undefined}
-                        setContentView={undefined}
-                        onDoubleClick={this.onContentDoubleClick}
-                        onClick={this.onContentClick}
+                        setContentViewBox={undefined}
+                        onDoubleClickScript={this.onContentDoubleClick}
+                        onClickScript={this.onContentClick}
                         isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive}
                         isContentActive={this._props.childContentsActive ?? this._props.isContentActive() === false ? returnFalse : emptyFunction}
                         hideCaptions={!!carouselShowsCaptions} // hide captions if the carousel is configured to show the captions
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1f867fc44..87973fd81 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
 import * as GoldenLayout from '../../../client/goldenLayout';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { AclAdmin, AclEdit } from '../../../fields/DocSymbols';
+import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -277,8 +277,8 @@ export class CollectionDockingView extends CollectionSubView() {
     }
     setupGoldenLayout = async () => {
         if (this._unmounting) return;
-        //const config = StrCast(this._props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this._props.Document)));
-        const config = StrCast(this._props.Document.dockingConfig);
+        //const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document)));
+        const config = StrCast(this.Document.dockingConfig);
         if (config) {
             const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
             const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? [];
@@ -322,7 +322,7 @@ export class CollectionDockingView extends CollectionSubView() {
             );
             new _global.ResizeObserver(this.onResize).observe(this._containerRef.current);
             this._reactionDisposer = reaction(
-                () => StrCast(this._props.Document.dockingConfig),
+                () => StrCast(this.Document.dockingConfig),
                 config => {
                     if (!this._goldenLayout || this._ignoreStateChange !== config) {
                         //  bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one.
@@ -381,7 +381,7 @@ export class CollectionDockingView extends CollectionSubView() {
                   .map(id => DocServer.GetCachedRefField(id))
                   .filter(f => f)
                   .map(f => f as Doc);
-        const changesMade = this._props.Document.dockingConfig !== json;
+        const changesMade = this.Document.dockingConfig !== json;
         if (changesMade) {
             if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) {
                 this.layoutDoc.dockingConfig = json;
@@ -449,7 +449,7 @@ export class CollectionDockingView extends CollectionSubView() {
         if (clone) {
             const cloned = await Doc.MakeClone(doc);
             Array.from(cloned.map.entries()).map(entry => (json = json.replace(entry[0], entry[1][Id])));
-            Doc.GetProto(cloned.clone).dockingConfig = json;
+            cloned.clone[DocData].dockingConfig = json;
             return DashboardView.openDashboard(cloned.clone);
         }
         const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g);
@@ -463,7 +463,7 @@ export class CollectionDockingView extends CollectionSubView() {
             const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true, undefined, true) : Doc.MakeEmbedding(origtab);
             const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeEmbedding(origtabdoc));
             if (newtabdocs.length) {
-                Doc.GetProto(newtab).data = new List<Doc>(newtabdocs);
+                newtab[DocData].data = new List<Doc>(newtabdocs);
                 newtabdocs.forEach(ntab => Doc.SetContainer(ntab, newtab));
             }
             json = json.replace(origtab[Id], newtab[Id]);
@@ -479,7 +479,7 @@ export class CollectionDockingView extends CollectionSubView() {
     stateChanged = () => {
         this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         const json = JSON.stringify(this._goldenLayout.toConfig());
-        const changesMade = this._props.Document.dockingConfig !== json;
+        const changesMade = this.Document.dockingConfig !== json;
         return changesMade;
     };
 
@@ -490,7 +490,7 @@ export class CollectionDockingView extends CollectionSubView() {
             // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed
             if (tab.DashDoc.embedContainer === this.Document) tab.DashDoc.embedContainer = undefined;
             if (!tab.DashDoc.embedContainer) Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
-            Doc.RemoveDocFromList(Doc.GetProto(tab.DashDoc), 'proto_embeddings', tab.DashDoc);
+            Doc.RemoveDocFromList(tab.DashDoc[DocData], 'proto_embeddings', tab.DashDoc);
         }
         if (CollectionDockingView.Instance) {
             const dview = CollectionDockingView.Instance.Document;
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index a54a5ec81..2dad377e0 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -19,14 +19,17 @@ import { undoBatch } from '../../util/UndoManager';
 import { AntimodeMenu } from '../AntimodeMenu';
 import { EditableView } from '../EditableView';
 import { MainView } from '../MainView';
-import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
 import { DefaultStyleProvider } from '../StyleProvider';
 import { CollectionLinearView } from './collectionLinear';
 import './CollectionMenu.scss';
+import { DocData } from '../../../fields/DocSymbols';
 
 interface CollectionMenuProps {
     panelHeight: () => number;
     panelWidth: () => number;
+    toggleTopBar: () => void;
+    topBarHeight: () => number;
 }
 
 @observer
@@ -65,15 +68,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
         }
     };
 
-    @action
-    toggleTopBar = () => {
-        if (SettingsManager.Instance.headerBarHeight > 0) {
-            SettingsManager.Instance.headerBarHeight = 0;
-        } else {
-            SettingsManager.Instance.headerBarHeight = 60;
-        }
-    };
-
     @action
     toggleProperties = () => {
         if (MainView.Instance.propertiesWidth() > 0) {
@@ -95,6 +89,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
             <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this._props.panelHeight() }}>
                 <CollectionLinearView
                     Document={selDoc}
+                    docViewPath={returnEmptyDocViewList}
                     fieldKey="data"
                     dropAction="embed"
                     styleProvider={DefaultStyleProvider}
@@ -123,8 +118,8 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
     }
 
     render() {
-        const headerIcon = SettingsManager.Instance.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down';
-        const headerTitle = SettingsManager.Instance.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar';
+        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';
 
@@ -134,8 +129,8 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     toggleType={ToggleType.BUTTON}
                     type={Type.PRIM}
                     color={SettingsManager.userColor}
-                    onClick={this.toggleTopBar}
-                    toggleStatus={SettingsManager.Instance.headerBarHeight > 0}
+                    onClick={this.props.toggleTopBar}
+                    toggleStatus={this.props.topBarHeight() > 0}
                     icon={<FontAwesomeIcon icon={headerIcon} size="lg" />}
                     tooltip={headerTitle}
                 />
@@ -219,7 +214,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         params: ['target', 'source'],
         title: 'set content',
         script: 'getProto(this.target).data = copyField(this.source);',
-        immediate: undoBatch((source: Doc[]) => (Doc.GetProto(this.target).data = new List<Doc>(source))),
+        immediate: undoBatch((source: Doc[]) => (this.target[DocData].data = new List<Doc>(source))),
         initialize: emptyFunction,
     };
     _onClickCommand = {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 363db8850..e6ce5baab 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, Field, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Copy, Id } from '../../../fields/FieldSymbols';
@@ -18,8 +18,8 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { LightboxView } from '../LightboxView';
-import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
-import { FieldViewProps } from '../nodes/FieldView';
+import { DocumentView } from '../nodes/DocumentView';
+import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
 import './CollectionNoteTakingView.scss';
@@ -51,7 +51,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     }
 
     @computed get chromeHidden() {
-        return BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClick?.() ? true : false;
+        return BoolCast(this.layoutDoc.chromeHidden) || this._props.onBrowseClickScript?.() ? true : false;
     }
     // columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns
     @computed get colHeaderData() {
@@ -189,7 +189,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     };
 
     // let's dive in and get the actual document we want to drag/move around
-    focusDocument = (doc: Doc, options: DocFocusOptions) => {
+    focusDocument = (doc: Doc, options: FocusViewOptions) => {
         Doc.BrushDoc(doc);
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
         if (found) {
@@ -254,9 +254,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 rootSelected={this.rootSelected}
                 layout_showTitle={this._props.childlayout_showTitle}
                 dragAction={StrCast(this.layoutDoc.childDragAction) as dropActionType}
-                onClick={this.onChildClickHandler}
-                onBrowseClick={this._props.onBrowseClick}
-                onDoubleClick={this.onChildDoubleClickHandler}
+                onClickScript={this.onChildClickHandler}
+                onBrowseClickScript={this._props.onBrowseClickScript}
+                onDoubleClickScript={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={noteTakingDocTransform}
                 focus={this.focusDocument}
                 childFilters={this.childDocFilters}
@@ -410,7 +410,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         if ((e.ctrlKey || fieldProps.Document._createDocOnCR) && ['Enter'].includes(e.key)) {
             e.stopPropagation?.();
             const newDoc = Doc.MakeCopy(fieldProps.Document, true);
-            Doc.GetProto(newDoc).text = undefined;
+            newDoc[DocData].text = undefined;
             FormattedTextBox.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
@@ -442,7 +442,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 }
                 return true;
             }
-        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this._props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
+        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
             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/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 1239a038a..1394e62ba 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -3,6 +3,7 @@ import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import { Doc, Opt } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
@@ -23,17 +24,17 @@ import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { CollectionSubView } from '../collections/CollectionSubView';
 import { LightboxView } from '../LightboxView';
 import { AudioWaveform } from '../nodes/audio/AudioWaveform';
-import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
+import { DocumentView, OpenWhere } from '../nodes/DocumentView';
+import { FocusFuncType, FocusViewOptions, StyleProviderFuncType } from '../nodes/FieldView';
 import { LabelBox } from '../nodes/LabelBox';
 import { VideoBox } from '../nodes/VideoBox';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import './CollectionStackedTimeline.scss';
-import { FieldViewProps } from '../nodes/FieldView';
 
 export type CollectionStackedTimelineProps = {
     Play: () => void;
     Pause: () => void;
-    playLink: (linkDoc: Doc, options: DocFocusOptions) => void;
+    playLink: (linkDoc: Doc, options: FocusViewOptions) => void;
     playFrom: (seekTimeInSeconds: number, endTime?: number) => void;
     playing: () => boolean;
     thumbnails?: () => string[];
@@ -162,7 +163,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     makeDocUnfiltered = (doc: Doc) => this.childDocList?.some(item => item === doc);
 
-    getView = async (doc: Doc, options: DocFocusOptions): Promise<Opt<DocumentView>> =>
+    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));
@@ -427,8 +428,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 _isTimelineLabel: true,
                 layout_borderRounding: anchorEndTime === undefined ? '100%' : undefined,
             });
-        Doc.GetProto(anchor)[startTag] = anchorStartTime;
-        Doc.GetProto(anchor)[endTag] = anchorEndTime;
+        anchor[DocData][startTag] = anchorStartTime;
+        anchor[DocData][endTag] = anchorEndTime;
         if (addAsAnnotation) {
             if (Cast(dataDoc[fieldKey], listSpec(Doc), null)) {
                 Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor);
@@ -579,7 +580,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                             const timespan = Math.max(0, Math.min(end - this.clipStart, this.clipEnd)) - Math.max(0, start - this.clipStart);
                             const width = (timespan / this.clipDuration) * this.timelineContentWidth;
                             const height = this._props.PanelHeight() / maxLevel;
-                            return this._props.Document.hideAnchors ? null : (
+                            return this.Document.hideAnchors ? null : (
                                 <div
                                     className={'collectionStackedTimeline-marker-timeline'}
                                     key={d.anchor[Id]}
@@ -691,8 +692,8 @@ interface StackedTimelineAnchorProps {
     width: number;
     height: number;
     toTimeline: (screen_delta: number, width: number) => number;
-    styleProvider?: StyleProviderFunc;
-    playLink: (linkDoc: Doc, options: DocFocusOptions) => void;
+    styleProvider?: StyleProviderFuncType;
+    playLink: (linkDoc: Doc, options: FocusViewOptions) => void;
     setTime: (time: number) => void;
     startTag: string;
     endTag: string;
@@ -701,7 +702,7 @@ interface StackedTimelineAnchorProps {
     isDocumentActive?: () => boolean | undefined;
     ScreenToLocalTransform: () => Transform;
     _timeline: HTMLDivElement | null;
-    focus: DocFocusFunc;
+    focus: FocusFuncType;
     currentTimecode: () => number;
     isSelected: () => boolean;
     stackedTimeline: CollectionStackedTimeline;
@@ -810,7 +811,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
     // renders anchor LabelBox
     renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
         const anchor = observable({ view: undefined as Opt<DocumentView> | null });
-        const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => {
+        const focusFunc = (doc: Doc, options: FocusViewOptions): number | undefined => {
             this._props.playLink(mark, options);
             return undefined;
         };
@@ -842,8 +843,8 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
                     searchFilterDocs={returnEmptyDoclist}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
-                    onClick={script}
-                    onDoubleClick={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
+                    onClickScript={script}
+                    onDoubleClickScript={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
                     ignoreAutoHeight={false}
                     hideResizeHandles={true}
                     bringToFront={emptyFunction}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9384b7088..0da32a9e0 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -24,8 +24,8 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { LightboxView } from '../LightboxView';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
-import { FieldViewProps } from '../nodes/FieldView';
+import { DocumentView } from '../nodes/DocumentView';
+import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
 import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
@@ -203,7 +203,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     componentDidMount() {
         super.componentDidMount?.();
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
 
         // reset section headers when a new filter is inputted
         this._pivotFieldDisposer = reaction(
@@ -249,7 +249,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     };
 
     // let's dive in and get the actual document we want to drag/move around
-    focusDocument = (doc: Doc, options: DocFocusOptions) => {
+    focusDocument = (doc: Doc, options: FocusViewOptions) => {
         Doc.BrushDoc(doc);
 
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
@@ -288,7 +288,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
                 newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
             }
-            Doc.GetProto(newDoc).text = undefined;
+            newDoc[DocData].text = undefined;
             FormattedTextBox.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
@@ -324,13 +324,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 renderDepth={this._props.renderDepth + 1}
                 PanelWidth={panelWidth}
                 PanelHeight={panelHeight}
-                pointerEvents={this.DocumentView?.()._props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
+                pointerEvents={this.DocumentView?.()._props.onClickScript?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
                 styleProvider={this.styleProvider}
                 containerViewPath={this.childContainerViewPath}
                 layout_fitWidth={this.childFitWidth}
                 isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
                 onKey={this.onKeyDown}
-                onBrowseClick={this._props.onBrowseClick}
+                onBrowseClickScript={this._props.onBrowseClickScript}
                 isDocumentActive={this.isContentActive}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
@@ -341,8 +341,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 rootSelected={this.rootSelected}
                 layout_showTitle={this._props.childlayout_showTitle}
                 dragAction={(this.layoutDoc.childDragAction ?? this._props.childDragAction) as dropActionType}
-                onClick={this.onChildClickHandler}
-                onDoubleClick={this.onChildDoubleClickHandler}
+                onClickScript={this.onChildClickHandler}
+                onDoubleClickScript={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={stackedDocTransform}
                 focus={this.focusDocument}
                 childFilters={this.childDocFilters}
@@ -468,7 +468,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 }
                 return true;
             }
-        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this._props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
+        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
             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
@@ -551,7 +551,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 addDocument={this.addDocument}
                 chromeHidden={this.chromeHidden}
                 colHeaderData={this.colHeaderData}
-                Document={this._props.Document}
+                Document={this.Document}
                 TemplateDataDocument={this._props.TemplateDataDocument}
                 renderChildren={this.children}
                 columnWidth={this.columnWidth}
@@ -583,7 +583,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         return (
             <CollectionMasonryViewFieldRow
                 showHandle={first}
-                Document={this._props.Document}
+                Document={this.Document}
                 chromeHidden={this.chromeHidden}
                 pivotField={this.pivotField}
                 unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
@@ -670,7 +670,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                     addDocument={this._props.addDocument}
                     moveDocument={this._props.moveDocument}
                     addDocTab={this._props.addDocTab}
-                    onBrowseClick={this._props.onBrowseClick}
+                    onBrowseClickScript={this._props.onBrowseClickScript}
                     pinToPres={emptyFunction}
                     rootSelected={this.rootSelected}
                     removeDocument={this._props.removeDocument}
@@ -738,7 +738,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         onWheel={e => this.isContentActive() && e.stopPropagation()}>
                         {this.renderedSections}
                         {!this.showAddAGroup ? null : (
-                            <div key={`${this._props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
+                            <div key={`${this.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
                                 <EditableView {...editableViewProps} />
                             </div>
                         )}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d7c42a975..0aa74aaba 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -21,18 +21,17 @@ 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, ViewBoxBaseProps } from '../DocComponent';
+import { ViewBoxBaseComponent } from '../DocComponent';
 import { LoadingBox } from '../nodes/LoadingBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { CollectionView, CollectionViewProps } from './CollectionView';
-import { returnEmptyDocViewList } from '../nodes/DocumentView';
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     isAnyChildContentActive: () => boolean;
 }
 
 export function CollectionSubView<X>(moreProps?: X) {
-    class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps & ViewBoxBaseProps>() {
+    class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps>() {
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected _mainCont?: HTMLDivElement;
@@ -63,20 +62,16 @@ export function CollectionSubView<X>(moreProps?: X) {
         }
 
         get dataDoc() {
-            return this._props.TemplateDataDocument instanceof Doc && this._props.Document.isTemplateForField
-                ? Doc.GetProto(this._props.TemplateDataDocument)
-                : this._props.Document.resolvedDataDoc
-                  ? this._props.Document
-                  : Doc.GetProto(this._props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
+            return this._props.TemplateDataDocument instanceof Doc && this.Document.isTemplateForField ? Doc.GetProto(this._props.TemplateDataDocument) : this.Document.resolvedDataDoc ? this.Document : Doc.GetProto(this.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
         }
 
         get childContainerViewPath() {
-            return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
+            return this.DocumentView?.().docViewPath;
         }
         // this returns whether either the collection is selected, or the template that it is part of is selected
         rootSelected = () => this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
 
-        // The data field for rendering this collection will be on the this._props.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument.
+        // The data field for rendering this collection will be on the this.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument.
         // When a document has a TemplateDataDoc but it's not a template, then it contains its own rendering data, but needs to pass the TemplateDataDoc through
         // to its children which may be templates.
         // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
@@ -97,14 +92,14 @@ export function CollectionSubView<X>(moreProps?: X) {
         @computed get childDocList() {
             return Cast(this.dataField, listSpec(Doc));
         }
-        collectionFilters = () => this._focusFilters ?? StrListCast(this._props.Document._childFilters);
-        collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this._props.Document._childFiltersByRanges, listSpec('string'), []);
+        collectionFilters = () => this._focusFilters ?? StrListCast(this.Document._childFilters);
+        collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.Document._childFiltersByRanges, listSpec('string'), []);
         // child filters apply to the descendants of the documents in this collection
         childDocFilters = () => [...(this._props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
         // unrecursive filters apply to the documents in the collection, but no their children.  See Utils.noRecursionHack
         unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
         childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
-        searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this._props.Document._searchFilterDocs);
+        searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this.Document._searchFilterDocs);
         @computed.struct get childDocs() {
             TraceMobx();
             let rawdocs: (Doc | Promise<Doc>)[] = [];
@@ -127,7 +122,7 @@ export function CollectionSubView<X>(moreProps?: X) {
             const childDocFilters = this.childDocFilters();
             const childFiltersByRanges = this.childDocRangeFilters();
             const searchDocs = this.searchFilterDocs();
-            if (this._props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) {
+            if (this.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) {
                 return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one
             }
 
@@ -136,9 +131,9 @@ export function CollectionSubView<X>(moreProps?: X) {
                 // dragging facets
                 const dragged = this._props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter));
                 if (dragged && SnappingManager.CanEmbed && DragManager.docsBeingDragged.includes(d)) return false;
-                let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this._props.Document).length > 0;
+                let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0;
                 if (notFiltered) {
-                    notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this._props.Document).length > 0;
+                    notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0;
                     const fieldKey = Doc.LayoutFieldKey(d);
                     const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name);
                     const data = d[annos ? fieldKey + '_annotations' : fieldKey];
@@ -171,7 +166,7 @@ export function CollectionSubView<X>(moreProps?: X) {
         @action
         protected async setCursorPosition(position: [number, number]) {
             let ind;
-            const doc = this._props.Document;
+            const doc = this.Document;
             const id = Doc.UserDoc()[Id];
             const email = Doc.CurrentUserEmail;
             const pos = { x: position[0], y: position[1] };
@@ -207,7 +202,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                 const dropAction = this.layoutDoc.dropAction as dropActionType;
                 // if the dropEvent's dragAction is, say 'embed', but we're just dragging within a collection, we may not actually want to make an embedding.
                 // so we check if our collection has a dropAction set on it and if so, we use that instead.
-                if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this._props.Document && this.childDocs.includes(d))) {
+                if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d))) {
                     de.complete.docDragData.dropAction = dropAction;
                 }
                 e.stopPropagation();
@@ -471,7 +466,7 @@ export function CollectionSubView<X>(moreProps?: X) {
             }
             if (generatedDocuments.length) {
                 // Creating a dash document
-                const isFreeformView = this._props.Document._type_collection === CollectionViewType.Freeform;
+                const isFreeformView = this.Document._type_collection === CollectionViewType.Freeform;
                 const set = !isFreeformView
                     ? generatedDocuments
                     : generatedDocuments.length > 1
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 7036ec41c..ee5147428 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -16,7 +16,8 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
-import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
+import { DocumentView } from '../nodes/DocumentView';
+import { FocusViewOptions } from '../nodes/FieldView';
 import { PresBox } from '../nodes/trails';
 import { CollectionSubView } from './CollectionSubView';
 import './CollectionTimeView.scss';
@@ -38,7 +39,7 @@ export class CollectionTimeView extends CollectionSubView() {
     }
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         runInAction(() => {
             this._childClickedScript = ScriptField.MakeScript('openInLightbox(this)', { this: Doc.name });
             this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' });
@@ -68,7 +69,7 @@ export class CollectionTimeView extends CollectionSubView() {
     };
 
     @action
-    scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => {
+    scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: FocusViewOptions) => {
         // if in preview, then override document's fields with view spec
         this._focusFilters = StrListCast(anchor.config_docFilters);
         this._focusRangeFilters = StrListCast(anchor.config_docRangeFilters);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 76b934802..4696d7948 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -447,7 +447,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 {!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? (
                     <CollectionFreeFormView
                         {...this._props}
-                        setContentView={emptyFunction}
+                        setContentViewBox={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
                         pointerEvents={this._props.isContentActive() && SnappingManager.IsDragging ? returnAll : returnNone}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7c9ef9f48..26ff5cc06 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -13,7 +13,7 @@ import { dropActionType } from '../../util/DragManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { OpenWhere } from '../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { CollectionCalendarView } from './CollectionCalendarView';
@@ -33,7 +33,7 @@ import { CollectionLinearView } from './collectionLinear';
 import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
 import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
 import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
-interface CollectionViewProps_ extends FieldViewProps {
+export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> {
     isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
     isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
     layoutEngine?: () => string;
@@ -63,9 +63,8 @@ interface CollectionViewProps_ extends FieldViewProps {
     RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
     hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
 }
-export interface CollectionViewProps extends React.PropsWithChildren<CollectionViewProps_> {}
 @observer
-export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & CollectionViewProps>() {
+export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(CollectionView, fieldStr);
     }
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f00e42360..da15f8dc5 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -29,7 +29,8 @@ import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
 import { Colors } from '../global/globalEnums';
-import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView';
+import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView';
+import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { DashFieldView } from '../nodes/formattedText/DashFieldView';
 import { PinProps, PresBox, PresMovement } from '../nodes/trails';
@@ -37,7 +38,6 @@ import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionView } from './CollectionView';
 import './TabDocView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { FieldViewProps } from '../nodes/FieldView';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface TabDocViewProps {
@@ -118,7 +118,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
             titleEle.onchange = (e: any) => {
                 undoable(() => {
                     titleEle.size = e.currentTarget.value.length + 3;
-                    Doc.GetProto(doc).title = e.currentTarget.value;
+                    doc[DocData].title = e.currentTarget.value;
                 }, 'edit tab title')();
             };
 
@@ -416,7 +416,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         return tab !== undefined;
     };
     @action
-    focusFunc = (doc: Doc, options: DocFocusOptions) => {
+    focusFunc = (doc: Doc, options: FocusViewOptions) => {
         if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
             this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
         }
@@ -452,7 +452,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                     hideTitle={this._props.keyValue}
                     Document={this._document}
                     TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
-                    onBrowseClick={DocumentView.exploreMode}
+                    onBrowseClickScript={DocumentView.exploreMode}
                     waitForDoubleClickToClick={this.waitForDoubleClick}
                     isContentActive={this.isContentActive}
                     isDocumentActive={returnFalse}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 9f40edee1..38a2fe978 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -27,8 +27,8 @@ import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
 import { EditableView } from '../EditableView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
-import { DocumentView, DocumentViewInternal, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
-import { FieldViewProps } from '../nodes/FieldView';
+import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView';
+import { FieldViewProps, StyleProviderFuncType } from '../nodes/FieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
@@ -62,7 +62,7 @@ export interface TreeViewProps {
     ScreenToLocalTransform: () => Transform;
     contextMenuItems?: { script: ScriptField; filter: ScriptField; icon: string; label: string }[];
     dontRegisterView?: boolean;
-    styleProvider?: StyleProviderFunc | undefined;
+    styleProvider?: StyleProviderFuncType | undefined;
     treeViewHideHeaderFields: () => boolean;
     renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle
     onCheckedClick?: () => ScriptField;
@@ -367,8 +367,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             _width: 1000,
             _height: 10,
         });
-        Doc.GetProto(bullet).title = ComputedField.MakeFunction('this.text?.Text');
-        Doc.GetProto(bullet).data = new List<Doc>([]);
+        const bulletData = bullet[DocData];
+        bulletData.title = ComputedField.MakeFunction('this.text?.Text');
+        bulletData.data = new List<Doc>([]);
         DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
 
         return bullet;
@@ -982,8 +983,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 addDocument={undefined}
                 addDocTab={this._props.addDocTab}
                 pinToPres={this.treeView._props.pinToPres}
-                onClick={this.onChildClick}
-                onDoubleClick={this.onChildDoubleClick}
+                onClickScript={this.onChildClick}
+                onDoubleClickScript={this.onChildDoubleClick}
                 dragAction={this._props.dragAction}
                 moveDocument={this.move}
                 removeDocument={this._props.removeDoc}
@@ -1076,7 +1077,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     fitContentsToBox={returnTrue}
                     hideDecorationTitle={this.treeView.outlineMode}
                     hideResizeHandles={this.treeView.outlineMode}
-                    onClick={this.onChildClick}
+                    onClickScript={this.onChildClick}
                     focus={this.refocus}
                     onKey={this.onKeyDown}
                     hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
@@ -1209,7 +1210,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         move: DragManager.MoveFunction,
         dragAction: dropActionType,
         addDocTab: (doc: Doc, where: OpenWhere) => boolean,
-        styleProvider: undefined | StyleProviderFunc,
+        styleProvider: undefined | StyleProviderFuncType,
         screenToLocalXf: () => Transform,
         isContentActive: (outsideReaction?: boolean) => boolean,
         panelWidth: () => number,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 5204633ea..f0a31a8c6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -130,7 +130,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
                 LinkManager.currentLink = this._props.LinkDocs[0];
                 this.toggleProperties();
                 // OverlayView.Instance.addElement(
-                //     <LinkEditor sourceDoc={this._props.A._props.Document} linkDoc={this._props.LinkDocs[0]}
+                //     <LinkEditor sourceDoc={this._props.A.Document} linkDoc={this._props.LinkDocs[0]}
                 //         showLinks={action(() => { })}
                 //     />, { x: 300, y: 300 });
             })
@@ -204,8 +204,8 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
         const atop = this.visibleY(adiv);
         const btop = this.visibleY(bdiv);
         if (!a.width || !b.width) return undefined;
-        const aDocBounds = (A._props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
-        const bDocBounds = (B._props as any).DocumentView?.().getBounds() || { left: 0, right: 0, top: 0, bottom: 0 };
+        const aDocBounds = (A._props as any).DocumentView?.().getBounds || { left: 0, right: 0, top: 0, bottom: 0 };
+        const bDocBounds = (B._props as any).DocumentView?.().getBounds || { left: 0, right: 0, top: 0, bottom: 0 };
         const aleft = this.visibleX(adiv);
         const bleft = this.visibleX(bdiv);
         const aclipped = aleft !== a.left || atop !== a.top;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c4358747b..7203041e0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
 import { Bezier } from 'bezier-js';
 import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, toJS } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
@@ -38,8 +38,8 @@ import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } fr
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
 import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
-import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
-import { FieldViewProps } from '../../nodes/FieldView';
+import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
+import { FocusViewOptions, FieldViewProps } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../../nodes/trails/PresBox';
 import { CreateImage } from '../../nodes/WebBoxRenderer';
@@ -54,7 +54,7 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
 
-export type collectionFreeformViewProps = {
+export interface collectionFreeformViewProps {
     NativeWidth?: () => number;
     NativeHeight?: () => number;
     originTopLeft?: boolean;
@@ -65,12 +65,12 @@ export type collectionFreeformViewProps = {
     noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
     engineProps?: any;
     getScrollHeight?: () => number | undefined;
-};
+}
 
 @observer
 export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() {
     public get displayName() {
-        return 'CollectionFreeFormView(' + this._props.Document.title?.toString() + ')';
+        return 'CollectionFreeFormView(' + this.Document.title?.toString() + ')';
     } // this makes mobx trace() statements more descriptive
 
     constructor(props: any) {
@@ -180,12 +180,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
-        if (timer) clearTimeout(timer);
-        return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true);
+        return DocumentView.SetViewTransition(docs, 'all', duration, timer, undefined, true);
     }
     public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) {
-        if (timer) clearTimeout(timer);
-        const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, undefined, true);
+        const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, timer, undefined, true);
         const timecode = Math.round(time);
         docs.forEach(doc => {
             CollectionFreeFormDocumentView.animFields.forEach(val => {
@@ -223,7 +221,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _keyframeEditing = false;
     @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
     getKeyFrameEditing = () => this._keyframeEditing;
-    onBrowseClickHandler = () => this._props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
+    onBrowseClickHandler = () => this._props.onBrowseClickScript?.() || ScriptCast(this.layoutDoc.onBrowseClick);
     onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
     onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     elementFunc = () => this._layoutElements;
@@ -288,14 +286,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return dispTime === -1 || curTime === -1 || (curTime - dispTime >= -1e-4 && curTime <= endTime);
     }
 
-    groupFocus = (anchor: Doc, options: DocFocusOptions) => {
+    groupFocus = (anchor: Doc, options: FocusViewOptions) => {
         options.docTransform = new Transform(-NumCast(this.layoutDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.layoutDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
         const res = this._props.focus(this.Document, options);
         options.docTransform = undefined;
         return res;
     };
 
-    focus = (anchor: Doc, options: DocFocusOptions) => {
+    focus = (anchor: Doc, options: FocusViewOptions) => {
         if (this._lightboxDoc) return;
         if (anchor === this.Document) {
             if (options.willZoomCentered && options.zoomScale) {
@@ -321,7 +319,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
 
-    getView = async (doc: Doc, options: DocFocusOptions): Promise<Opt<DocumentView>> =>
+    getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> =>
         new Promise<Opt<DocumentView>>(res => {
             if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false);
             const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
@@ -467,7 +465,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this.childLayoutPairs
             .map(pair => pair.layout)
             .reduce((cluster, cd) => {
-                const grouping = this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
+                const grouping = this.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
                 if (grouping !== -1) {
                     const layoutDoc = Doc.Layout(cd);
                     const cx = NumCast(cd.x) - this._clusterDistance / 2;
@@ -484,9 +482,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (cluster !== -1) {
             const ptsParent = e;
             if (ptsParent) {
-                const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
+                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 { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
+                const { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 };
                 const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
                 de.moveDocument = this._props.moveDocument;
                 de.offset = this.screenToFreeformContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
@@ -506,7 +504,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     updateClusters(_freeform_useClusters: boolean) {
-        this._props.Document._freeform_useClusters = _freeform_useClusters;
+        this.Document._freeform_useClusters = _freeform_useClusters;
         this._clusterSets.length = 0;
         this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
     }
@@ -514,7 +512,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @action
     updateClusterDocs(docs: Doc[]) {
         const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
-        if (this._props.Document._freeform_useClusters) {
+        if (this.Document._freeform_useClusters) {
             const docFirst = docs[0];
             docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
             const preferredInd = NumCast(docFirst.layout_cluster);
@@ -557,7 +555,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @action
     updateCluster = (doc: Doc) => {
         const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
-        if (this._props.Document._freeform_useClusters) {
+        if (this.Document._freeform_useClusters) {
             this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
             const preferredInd = NumCast(doc.layout_cluster);
             doc.layout_cluster = -1;
@@ -616,7 +614,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     trySelectCluster = (addToSel: boolean) => {
         if (this._hitCluster !== -1) {
             !addToSel && SelectionManager.DeselectAll();
-            const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
+            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;
         }
@@ -810,7 +808,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this.childDocs
             .map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.()))
             .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
-            .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
+            .map(inkView => ({ inkViewBounds: inkView!.getBounds, inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
             .filter(
                 ({ inkViewBounds }) =>
                     inkViewBounds && // bounding box of eraser segment and ink stroke overlap
@@ -959,8 +957,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         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);
-            this._props.Document[this.scaleFieldKey] = Math.abs(safeScale);
-            this.setPan(-localTransform.TranslateX / safeScale, (this._props.originTopLeft ? undefined : NumCast(this._props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
+            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);
         }
     };
 
@@ -968,7 +966,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     onPointerWheel = (e: React.WheelEvent): void => {
         if (this.Document.isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
         PresBox.Instance?.pauseAutoPres();
-        if (this.layoutDoc._Transform || this._props.Document.treeView_OutlineMode === TreeViewType.outline) return;
+        if (this.layoutDoc._Transform || this.Document.treeView_OutlineMode === TreeViewType.outline) return;
         e.stopPropagation();
         const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
         const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling + 1e-4;
@@ -989,7 +987,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             case freeformScrollMode.Zoom:
                 if ((e.ctrlKey || !scrollable) && this._props.isContentActive()) {
                     this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this._props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
-                    e.preventDefault();
+                    // e.preventDefault();
                 }
                 break;
         }
@@ -1182,13 +1180,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             if (layout_fieldKey !== 'layout' && fieldProps.Document[layout_fieldKey] instanceof Doc) {
                 newDoc[layout_fieldKey] = fieldProps.Document[layout_fieldKey];
             }
-            Doc.GetProto(newDoc).text = undefined;
+            newDoc[DocData].text = undefined;
             FormattedTextBox.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
     };
     @computed get childPointerEvents() {
-        const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine);
+        const engine = this._props.layoutEngine?.() || StrCast(this.Document._layoutEngine);
         return SnappingManager.IsResizing
             ? 'none'
             : this._props.childPointerEvents?.() ??
@@ -1210,6 +1208,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 {...OmitKeys(entry, ['replica', 'pair']).omit}
                 key={childLayout[Id] + (entry.replica || '')}
                 Document={childLayout}
+                containerViewPath={this.DocumentView?.().docViewPath}
+                styleProvider={this.clusterStyleProvider}
                 TemplateDataDocument={childData}
                 dragStarting={this.dragStarting}
                 dragEnding={this.dragEnding}
@@ -1223,10 +1223,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 LayoutTemplateString={childLayout.z ? undefined : this._props.childLayoutString}
                 rootSelected={childData ? this.rootSelected : returnFalse}
                 waitForDoubleClickToClick={this._props.waitForDoubleClickToClick}
-                onClick={this.onChildClickHandler}
+                onClickScript={this.onChildClickHandler}
                 onKey={this.onKeyDown}
-                onDoubleClick={this.onChildDoubleClickHandler}
-                onBrowseClick={this.onBrowseClickHandler}
+                onDoubleClickScript={this.onChildDoubleClickHandler}
+                onBrowseClickScript={this.onBrowseClickHandler}
                 ScreenToLocalTransform={childLayout.z ? this.ScreenToLocalBoxXf : this.ScreenToContentsXf}
                 PanelWidth={childLayout[Width]}
                 PanelHeight={childLayout[Height]}
@@ -1242,8 +1242,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 moveDocument={this._props.moveDocument}
                 pinToPres={this._props.pinToPres}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                containerViewPath={this.DocumentView?.().docViewPath}
-                styleProvider={this.clusterStyleProvider}
                 dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
                 bringToFront={this.bringToFront}
                 layout_showTitle={this._props.childlayout_showTitle}
@@ -1322,7 +1320,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
-        (this._props.viewDefDivClick || ScriptCast(this._props.Document.onViewDefDivClick))?.script.run({ this: this._props.Document, payload });
+        (this._props.viewDefDivClick || ScriptCast(this.Document.onViewDefDivClick))?.script.run({ this: this.Document, payload });
         e.stopPropagation();
     };
 
@@ -1377,7 +1375,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         poolData: Map<string, PoolData>,
         engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[]
     ) {
-        return engine(poolData, this._props.Document, this.childLayoutPairs, [this._props.PanelWidth(), this._props.PanelHeight()], this.viewDefsToJSX, this._props.engineProps);
+        return engine(poolData, this.Document, this.childLayoutPairs, [this._props.PanelWidth(), this._props.PanelHeight()], this.viewDefsToJSX, this._props.engineProps);
     }
 
     doFreeformLayout(poolData: Map<string, PoolData>) {
@@ -1437,7 +1435,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     infoUI = () => (this.Document._hideInfo || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         super.componentDidMount?.();
         setTimeout(
             action(() => {
@@ -1639,15 +1637,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         const appearance = ContextMenu.Instance.findByDescription('Appearance...');
         const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
-        !this._props.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
-        !this._props.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
+        !this.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
+        !this.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
         !Doc.noviceMode &&
             appearanceItems.push({
                 description: 'Toggle auto arrange',
                 event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
                 icon: 'compress-arrows-alt',
             });
-        if (this._props.setContentView === emptyFunction) {
+        if (this._props.setContentViewBox === emptyFunction) {
             !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
             return;
         }
@@ -1656,7 +1654,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
         this._props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
 
-        this._props.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' });
+        this.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' });
 
         !Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null;
         !Doc.noviceMode ? appearanceItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null;
@@ -1680,8 +1678,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @undoBatch
     transcribeStrokes = () => {
-        if (this._props.Document.isGroup && this._props.Document.transcription) {
-            const text = StrCast(this._props.Document.transcription);
+        if (this.Document.isGroup && this.Document.transcription) {
+            const text = StrCast(this.Document.transcription);
             const lines = text.split('\n');
             const height = 30 + 15 * lines.length;
 
@@ -1742,7 +1740,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @computed get placeholder() {
         return (
             <div className="collectionfreeformview-placeholder" style={{ background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) }}>
-                <span className="collectionfreeformview-placeholderSpan">{this._props.Document.annotationOn ? '' : this._props.Document.title?.toString()}</span>
+                <span className="collectionfreeformview-placeholderSpan">{this.Document.annotationOn ? '' : this.Document.title?.toString()}</span>
             </div>
         );
     }
@@ -1882,10 +1880,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                             PanelHeight={this.lightboxPanelHeight}
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
-                            onClick={this.onChildClickHandler}
+                            onClickScript={this.onChildClickHandler}
                             onKey={this.onKeyDown}
-                            onDoubleClick={this.onChildDoubleClickHandler}
-                            onBrowseClick={this.onBrowseClickHandler}
+                            onDoubleClickScript={this.onChildDoubleClickHandler}
+                            onBrowseClickScript={this.onBrowseClickHandler}
                             childFilters={this.childDocFilters}
                             childFiltersByRanges={this.childDocRangeFilters}
                             searchFilterDocs={this.searchFilterDocs}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 39d828302..c2f8232c6 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -352,9 +352,10 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         const newCollection = creator
             ? creator(selected, { title: 'nested stack' })
             : ((doc: Doc) => {
-                  Doc.GetProto(doc).data = new List<Doc>(selected);
-                  Doc.GetProto(doc).isGroup = makeGroup;
-                  Doc.GetProto(doc).title = makeGroup ? 'grouping' : 'nested freeform';
+                  const docData = doc[DocData];
+                  docData.data = new List<Doc>(selected);
+                  docData.isGroup = makeGroup;
+                  docData.title = makeGroup ? 'grouping' : 'nested freeform';
                   doc._freeform_panX = doc._freeform_panY = 0;
                   return doc;
               })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 113ffedb3..f25872c2b 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -192,7 +192,7 @@ export class CollectionGridView extends CollectionSubView() {
                 {...this._props}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
-                setContentView={emptyFunction}
+                setContentViewBox={emptyFunction}
                 Document={layout}
                 TemplateDataDocument={layout.resolvedDataDoc as Doc}
                 isContentActive={this.isChildContentActive}
@@ -200,7 +200,7 @@ export class CollectionGridView extends CollectionSubView() {
                 PanelHeight={height}
                 ScreenToLocalTransform={dxf}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                onClick={this.onChildClickHandler}
+                onClickScript={this.onChildClickHandler}
                 renderDepth={this._props.renderDepth + 1}
                 dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
             />
@@ -340,7 +340,7 @@ export class CollectionGridView extends CollectionSubView() {
      * Handles text document creation on double click.
      */
     onPointerDown = (e: React.PointerEvent) => {
-        if (this._props.isContentActive(true)) {
+        if (this._props.isContentActive()) {
             setupMoveUpEvents(
                 this,
                 e,
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 40b4151fe..1fa106b45 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -267,8 +267,8 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 PanelHeight={height}
                 rootSelected={this.rootSelected}
                 dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
-                onClick={this.onChildClickHandler}
-                onDoubleClick={this.onChildDoubleClickHandler}
+                onClickScript={this.onChildClickHandler}
+                onDoubleClickScript={this.onChildDoubleClickHandler}
                 suppressSetHeight={true}
                 ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 04a4042f1..97a444b8c 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -262,8 +262,8 @@ export class CollectionMultirowView extends CollectionSubView() {
                 PanelHeight={height}
                 rootSelected={this.rootSelected}
                 dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
-                onClick={this.onChildClickHandler}
-                onDoubleClick={this.onChildDoubleClickHandler}
+                onClickScript={this.onChildClickHandler}
+                onDoubleClickScript={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index c38c6dc4e..d580d9c52 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -1,16 +1,16 @@
-import { action, observable } from 'mobx';
+import { action } from 'mobx';
 import { observer } from 'mobx-react';
 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 { StyleProviderFunc } from '../../nodes/DocumentView';
+import { StyleProviderFuncType } from '../../nodes/FieldView';
 import { DimUnit } from './CollectionMulticolumnView';
 
 interface ResizerProps {
     width: number;
-    styleProvider?: StyleProviderFunc;
+    styleProvider?: StyleProviderFuncType;
     isContentActive?: () => boolean | undefined;
     columnUnitLength(): number | undefined;
     toLeft?: Doc;
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index 6f1b3b425..73d08d5ef 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -1,16 +1,16 @@
-import { action, observable } from 'mobx';
+import { action } from 'mobx';
 import { observer } from 'mobx-react';
 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 { StyleProviderFunc } from '../../nodes/DocumentView';
+import { StyleProviderFuncType } from '../../nodes/FieldView';
 import { DimUnit } from './CollectionMultirowView';
 
 interface ResizerProps {
     height: number;
-    styleProvider?: StyleProviderFunc;
+    styleProvider?: StyleProviderFuncType;
     isContentActive?: () => boolean | undefined;
     columnUnitLength(): number | undefined;
     toTop?: Doc;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 227274a53..ec91b25f8 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -16,7 +16,8 @@ import { undoable, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
+import { FocusViewOptions, FieldViewProps } from '../../nodes/FieldView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
@@ -24,7 +25,6 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
-import { FieldViewProps } from '../../nodes/FieldView';
 
 export enum ColumnType {
     Number,
@@ -147,7 +147,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     }
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         document.addEventListener('keydown', this.onKeyDown);
 
         Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1]));
@@ -498,13 +498,13 @@ export class CollectionSchemaView extends CollectionSubView() {
         ContextMenu.Instance.displayMenu(x, y, undefined, true);
     };
 
-    focusDocument = (doc: Doc, options: DocFocusOptions) => {
+    focusDocument = (doc: Doc, options: FocusViewOptions) => {
         Doc.BrushDoc(doc);
         this.scrollToDoc(doc, options);
         return undefined;
     };
 
-    scrollToDoc = (doc: Doc, options: DocFocusOptions) => {
+    scrollToDoc = (doc: Doc, options: FocusViewOptions) => {
         const found = this._tableContentRef && Array.from(this._tableContentRef.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
         if (found) {
             const rect = found.getBoundingClientRect();
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 3f94e7fc1..f2fe0dde7 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -12,7 +12,7 @@ import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoable } from '../../../util/UndoManager';
-import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../../DocComponent';
+import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { OpenWhere } from '../../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
@@ -20,17 +20,17 @@ import { CollectionSchemaView } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
 import { SchemaTableCell } from './SchemaTableCell';
 
-interface SchemaRowBoxProps {
+interface SchemaRowBoxProps extends FieldViewProps {
     rowIndex: number;
 }
 @observer
-export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRowBoxProps & ViewBoxBaseProps>() {
+export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
     public static LayoutString(fieldKey: string, rowIndex: number) {
         return FieldView.LayoutString(SchemaRowBox, fieldKey).replace('fieldKey', `rowIndex={${rowIndex}} fieldKey`);
     }
     private _ref: HTMLDivElement | null = null;
 
-    constructor(props: any) {
+    constructor(props: SchemaRowBoxProps) {
         super(props);
         makeObservable(this);
     }
@@ -50,7 +50,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
     }
 
     componentDidMount(): void {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     select = (ctrlKey: boolean, shiftKey: boolean) => {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 422c4155d..fda5764dd 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -18,7 +18,7 @@ import { EditableView } from '../../EditableView';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DefaultStyleProvider } from '../../StyleProvider';
 import { Colors } from '../../global/globalEnums';
-import { OpenWhere } from '../../nodes/DocumentView';
+import { OpenWhere, returnEmptyDocViewList } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
@@ -49,7 +49,7 @@ export interface SchemaTableCellProps {
 
 @observer
 export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellProps> {
-    constructor(props: any) {
+    constructor(props: SchemaTableCellProps) {
         super(props);
         makeObservable(this);
     }
@@ -75,6 +75,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
         const fieldProps: FieldViewProps = {
             childFilters: returnEmptyFilter,
             childFiltersByRanges: returnEmptyFilter,
+            docViewPath: returnEmptyDocViewList,
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
             isSelected: returnFalse,
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index b7376e901..12b83414c 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -61,7 +61,7 @@ export class LinkMenu extends ObservableReactComponent<Props> {
     render() {
         const sourceDoc = this.props.docView.Document;
         const sourceAnchor = this.props.docView.anchorViewDoc ?? sourceDoc;
-        const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds());
+        const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds);
 
         return (
             <div className="linkMenu" ref={this._linkMenuRef} style={{ ...style, background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 91142b90b..028d3da53 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -47,18 +47,18 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
             const sourceDoc =
                 this.props.docView.anchorViewDoc ??
                 (this.props.docView.Document.type === DocumentType.LINK //
-                    ? this.props.docView.props.LayoutTemplateString?.includes('link_anchor_1')
+                    ? this.props.docView._props.LayoutTemplateString?.includes('link_anchor_1')
                         ? DocCast(linkDoc.link_anchor_1)
                         : DocCast(linkDoc.link_anchor_2)
                     : this.props.sourceDoc);
             const destDoc = !sourceDoc
                 ? undefined
                 : this.props.docView.Document.type === DocumentType.LINK
-                ? 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));
+                  ? 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));
             return !destDoc || !sourceDoc ? null : (
                 <LinkMenuItem
                     key={linkDoc[Id]}
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index 6760a0b1e..ed575c329 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -312,7 +312,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
                         addDocTab={this.addDocTab}
                         pinToPres={TabDocView.PinDoc}
                         bringToFront={emptyFunction}
-                        onBrowseClick={DocumentView.exploreMode}
+                        onBrowseClickScript={DocumentView.exploreMode}
                         focus={emptyFunction}
                     />
                 </GestureOverlay>
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 1729af191..2b71fd156 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -5,6 +5,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DateField } from '../../../fields/DateField';
 import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DateCast, NumCast } from '../../../fields/Types';
 import { AudioField, nullAudio } from '../../../fields/URLField';
@@ -19,8 +20,7 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import './AudioBox.scss';
-import { DocFocusOptions } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
 
 /**
@@ -121,7 +121,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @action
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         if (this.path) {
             this.mediaState = media_state.Paused;
             this.setPlayheadTime(NumCast(this.layoutDoc.clipStart));
@@ -374,9 +374,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             action(() => {
                 const newDoc = DocUtils.GetNewTextDoc('', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
                 const textField = Doc.LayoutFieldKey(newDoc);
-                Doc.GetProto(newDoc)[`${textField}_recordingSource`] = this.dataDoc;
-                Doc.GetProto(newDoc)[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
-                Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
+                const newDocData = newDoc[DocData];
+                newDocData[`${textField}_recordingSource`] = this.dataDoc;
+                newDocData[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
+                newDocData.mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
                 if (Doc.IsInMyOverlay(this.Document)) {
                     newDoc.overlayX = this.Document.x;
                     newDoc.overlayY = NumCast(this.Document.y) + NumCast(this.layoutDoc._height);
@@ -432,7 +433,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     // plays link
-    playLink = (link: Doc, options: DocFocusOptions) => {
+    playLink = (link: Doc, options: FocusViewOptions) => {
         if (link.annotationOn === this.Document) {
             if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
                 this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link));
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 73709c17e..0ae4ed62c 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -38,8 +38,8 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
     CollectionFreeFormView: CollectionFreeFormView;
 }
 @observer
-export class CollectionFreeFormDocumentViewWrapper extends ObservableReactComponent<CollectionFreeFormDocumentViewWrapperProps> implements CollectionFreeFormDocumentViewProps {
-    constructor(props: any) {
+export class CollectionFreeFormDocumentViewWrapper extends ObservableReactComponent<CollectionFreeFormDocumentViewWrapperProps> {
+    constructor(props: CollectionFreeFormDocumentViewWrapperProps) {
         super(props);
         makeObservable(this);
     }
@@ -104,7 +104,7 @@ export class CollectionFreeFormDocumentViewWrapper extends ObservableReactCompon
         );
     }
 }
-export interface CollectionFreeFormDocumentViewProps {
+export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     w_X: () => number;
     w_Y: () => number;
     w_Z: () => number;
@@ -125,8 +125,8 @@ export interface CollectionFreeFormDocumentViewProps {
 }
 
 @observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() {
-    constructor(props: any) {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
+    constructor(props: CollectionFreeFormDocumentViewProps) {
         super(props);
         makeObservable(this);
     }
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 500eb52ac..116dc48a6 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -8,20 +8,20 @@ import { DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { DocUtils, Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import './ComparisonBox.scss';
-import { DocumentView, returnEmptyDocViewList } from './DocumentView';
+import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
 
 @observer
-export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ComparisonBox, fieldKey);
     }
     private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
@@ -35,7 +35,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         return '_' + this._props.fieldKey + '_clipWidth';
     }
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
     protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
         this._disposers[disposerId]?.();
@@ -152,9 +152,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         if (property === StyleProp.PointerEvents) return 'none';
         return this._props.styleProvider?.(doc, props, property);
     };
-    @computed get childContainerViewPath() {
-        return this.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
-    }
     moveDoc1 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
     moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
     remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
@@ -181,7 +178,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                         {...this._props}
                         Document={targetDoc}
                         TemplateDataDocument={undefined}
-                        containerViewPath={this.childContainerViewPath}
+                        containerViewPath={this.DocumentView?.().docViewPath}
                         moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={returnZero}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 5ceec98cb..8365f4770 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -14,13 +14,13 @@ import { TraceMobx } from '../../../../fields/util';
 import { Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
 import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
-import { DocFocusOptions, DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
 import { PinProps } from '../trails';
 import './DataVizBox.scss';
 import { Histogram } from './components/Histogram';
@@ -36,7 +36,7 @@ export enum DataVizView {
 }
 
 @observer
-export class DataVizBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
@@ -46,10 +46,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatablePr
     @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
 
-    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     @computed get annotationLayer() {
@@ -233,7 +233,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatablePr
             () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar')
         );
     };
-    getView = async (doc: Doc, options: DocFocusOptions) => {
+    getView = async (doc: Doc, options: FocusViewOptions) => {
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
             options.didMove = true;
             this.toggleSidebar();
@@ -254,7 +254,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatablePr
     sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeDocument(doc, sidebarKey);
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
     }
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 55859b92b..9538cebb2 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -24,7 +24,6 @@ import { YoutubeBox } from './../../apis/youtube/YoutubeBox';
 import { AudioBox } from './AudioBox';
 import { ComparisonBox } from './ComparisonBox';
 import { DataVizBox } from './DataVizBox/DataVizBox';
-import { DocumentViewProps } from './DocumentView';
 import './DocumentView.scss';
 import { EquationBox } from './EquationBox';
 import { FieldView, FieldViewProps } from './FieldView';
@@ -114,13 +113,11 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
     }
 }
 
+export interface DocumentContentsViewProps extends FieldViewProps {
+    layoutFieldKey: string;
+}
 @observer
-export class DocumentContentsView extends ObservableReactComponent<
-    FieldViewProps & {
-        onClick?: () => ScriptField;
-        LayoutTemplate?: () => Opt<Doc>;
-    }
-> {
+export class DocumentContentsView extends ObservableReactComponent<DocumentContentsViewProps> {
     constructor(props: any) {
         super(props);
         makeObservable(this);
@@ -130,8 +127,8 @@ export class DocumentContentsView extends ObservableReactComponent<
         TraceMobx();
         if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
         if (!this.layoutDoc) return '<p>awaiting layout</p>';
-        if (this._props.fieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
-        const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.fieldKey ? this._props.fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
+        if (this._props.layoutFieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
+        const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
         if (layout === undefined) return this._props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
         if (typeof layout === 'string') return layout;
         return '<p>Loading layout</p>';
@@ -143,26 +140,26 @@ export class DocumentContentsView extends ObservableReactComponent<
         const template: Doc =
             this._props.LayoutTemplate?.() ||
             (this._props.LayoutTemplateString && this._props.Document) ||
-            (this._props.fieldKey && StrCast(this._props.Document[this._props.fieldKey]) && this._props.Document) ||
-            Doc.Layout(this._props.Document, this._props.fieldKey ? Cast(this._props.Document[this._props.fieldKey], Doc, null) : undefined);
+            (this._props.layoutFieldKey && StrCast(this._props.Document[this._props.layoutFieldKey]) && this._props.Document) ||
+            Doc.Layout(this._props.Document, this._props.layoutFieldKey ? Cast(this._props.Document[this._props.layoutFieldKey], Doc, null) : undefined);
         return Doc.expandTemplateLayout(template, this._props.Document);
     }
 
     CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
         const docOnlyProps = [
-            // these are the properties in  DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
+            // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
             'hideResizeHandles',
             'hideTitle',
             'contentPointerEvents',
-            'radialMenu',
             'LayoutTemplateString',
             'LayoutTemplate',
+            'layoutFieldKey',
             'dontCenter',
             'contextMenuItems',
             //'onClick', // don't need to omit this since it will be set
-            'onDoubleClick',
-            'onPointerDown',
-            'onPointerUp',
+            'onDoubleClickScript',
+            'onPointerDownScript',
+            'onPointerUpScript',
         ];
         const templateDataDoc = this._props.TemplateDataDocument ?? (this.layoutDoc !== this._props.Document ? this._props.Document[DocData] : undefined);
         const list: BindingProps & React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLDivElement>, HTMLDivElement> = {
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index 5ce08cdc3..4a22766cc 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -29,7 +29,7 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
     }
     render() {
         const view = this._props.view;
-        const { left, top, right, bottom } = view.getBounds() || { left: 0, top: 0, right: 0, bottom: 0 };
+        const { left, top, right, bottom } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 };
 
         return (
             <div
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index f5949a451..d1805308d 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -17,6 +17,7 @@ import { DocumentView } from './DocumentView';
 import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { TaskCompletionBox } from './TaskCompletedBox';
 import { PinProps } from './trails';
+import { DocData } from '../../../fields/DocSymbols';
 
 interface DocumentLinksButtonProps {
     View: DocumentView;
@@ -158,41 +159,40 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
 
                 LinkManager.currentLink = linkDoc;
 
-                if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
-                    // if linking from a Hypothes.is annotation
-                    Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
-                    Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
-                    Doc.GetProto(linkDoc as Doc).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
-                }
-
                 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;
                     TaskCompletionBox.taskCompleted = true;
 
-                    if (LinkDescriptionPopup.showDescriptions === 'ON' || !LinkDescriptionPopup.showDescriptions) {
-                        LinkDescriptionPopup.popupX = screenX;
-                        LinkDescriptionPopup.popupY = screenY - 100;
-                        LinkDescriptionPopup.descriptionPopup = true;
+                    if (LinkDescriptionPopup.Instance.showDescriptions === 'ON' || !LinkDescriptionPopup.Instance.showDescriptions) {
+                        LinkDescriptionPopup.Instance.popupX = screenX;
+                        LinkDescriptionPopup.Instance.popupY = screenY - 100;
+                        LinkDescriptionPopup.Instance.display = true;
                     }
 
                     const rect = document.body.getBoundingClientRect();
-                    if (LinkDescriptionPopup.popupX + 200 > rect.width) {
-                        LinkDescriptionPopup.popupX -= 190;
+                    if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
+                        LinkDescriptionPopup.Instance.popupX -= 190;
                         TaskCompletionBox.popupX -= 40;
                     }
-                    if (LinkDescriptionPopup.popupY + 100 > rect.height) {
-                        LinkDescriptionPopup.popupY -= 40;
+                    if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
+                        LinkDescriptionPopup.Instance.popupY -= 40;
                         TaskCompletionBox.popupY -= 40;
                     }
 
                     setTimeout(
-                        action(() => {
-                            TaskCompletionBox.taskCompleted = false;
-                        }),
+                        action(() => (TaskCompletionBox.taskCompleted = false)),
                         2500
                     );
                 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 444c300f3..3e5e43b47 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,19 +3,17 @@ import { Dropdown, DropdownType, Type } from 'browndash-components';
 import { Howl } from 'howler';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
 import { Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols';
+import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
-import { RefField } from '../../../fields/RefField';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+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';
@@ -33,34 +31,27 @@ import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
-import { Transform } from '../../util/Transform';
-import { UndoManager, undoBatch } from '../../util/UndoManager';
+import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { DocComponent } from '../DocComponent';
+import { DocComponent, ViewBoxInterface } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
-import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
 import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
 import { DocumentLinksButton } from './DocumentLinksButton';
 import './DocumentView.scss';
-import { FieldViewProps } from './FieldView';
+import { FieldViewProps, FieldViewSharedProps } from './FieldView';
 import { KeyValueBox } from './KeyValueBox';
 import { LinkAnchorBox } from './LinkAnchorBox';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { PresEffect, PresEffectDirection } from './trails';
-import { PinProps, PresBox } from './trails/PresBox';
-
 interface Window {
     MediaRecorder: MediaRecorder;
 }
-
 declare class MediaRecorder {
-    // whatever MediaRecorder has
-    constructor(e: any);
+    constructor(e: any); // whatever MediaRecorder has
 }
 
 export enum OpenWhereMod {
@@ -92,114 +83,7 @@ export enum OpenWhere {
 export function returnEmptyDocViewList() {
     return [] as DocumentView[];
 }
-export interface DocFocusOptions {
-    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
-    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
-    anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc
-    easeFunc?: 'linear' | 'ease'; // transition method for scrolling
-}
-export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => Opt<number>;
-export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => any;
-export interface DocComponentView {
-    fieldKey?: string;
-    annotationKey?: string;
-    updateIcon?: () => void; // updates the icon representation of the document
-    getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
-    restoreView?: (viewSpec: Doc) => boolean;
-    scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
-    brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms)
-    getView?: (doc: Doc, options: DocFocusOptions) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
-    addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
-    addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
-    select?: (ctrlKey: boolean, shiftKey: boolean) => void;
-    focus?: (textAnchor: Doc, options: DocFocusOptions) => Opt<number>;
-    isAnyChildContentActive?: () => boolean; // is any child content of the document active
-    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)
-    playFrom?: (time: number, endTime?: number) => void;
-    Pause?: () => void; // pause a media document (eg, audio/video)
-    IsPlaying?: () => boolean; // is a media document playing
-    TogglePause?: (keep?: boolean) => void; // toggle media document playing state
-    setFocus?: () => void; // sets input focus to the componentView
-    setData?: (data: Field | Promise<RefField | undefined>) => boolean;
-    componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
-    dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
-    incrementalRendering?: () => void;
-    infoUI?: () => JSX.Element | null;
-    screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
-    ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
-    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;
-}
-/**
- * props that DocumentViews, DocumentInternalViews and FieldViews all can use
- * */
-export interface DocumentViewSharedProps {
-    Document: Doc;
-    LayoutTemplateString?: string;
-    TemplateDataDocument?: Doc;
-    renderDepth: number;
-    scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
-    treeViewDoc?: Doc;
-    xPadding?: number;
-    yPadding?: number;
-    dontRegisterView?: boolean;
-    dropAction?: dropActionType;
-    dragAction?: dropActionType;
-    forceAutoHeight?: boolean;
-    ignoreAutoHeight?: boolean;
-    disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
-    CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
-    containerViewPath?: () => DocumentView[];
-    fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
-    isGroupActive?: () => string | undefined; // is this document part of a group that is active
-    setContentView?: (view: DocComponentView) => any;
-    PanelWidth: () => number;
-    PanelHeight: () => number;
-    isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
-    isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
-    childFilters: () => string[];
-    childFiltersByRanges: () => string[];
-    styleProvider: Opt<StyleProviderFunc>;
-    setTitleFocus?: () => void;
-    focus: DocFocusFunc;
-    layout_fitWidth?: (doc: Doc) => boolean | undefined;
-    searchFilterDocs: () => Doc[];
-    layout_showTitle?: () => string;
-    whenChildContentsActiveChanged: (isActive: boolean) => void;
-    rootSelected?: () => boolean; // whether the root of a template has been selected
-    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
-    filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
-    addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
-    removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
-    moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean;
-    pinToPres: (document: Doc, pinProps: PinProps) => void;
-    ScreenToLocalTransform: () => Transform;
-    bringToFront: (doc: Doc, sendToBack?: boolean) => void;
-    waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
-    defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
-    pointerEvents?: () => Opt<string>;
-}
-
-/**
- * props that are used by DocumentViews and DocumentInternalViews but not by the contents of those views (FieldViews).
- */
-export interface DocumentViewProps extends DocumentViewSharedProps {
+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
     hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
@@ -221,35 +105,22 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
     NativeWidth?: () => number;
     NativeHeight?: () => number;
-    LayoutTemplate?: () => Opt<Doc>;
     contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
-    onClick?: () => ScriptField;
-    onDoubleClick?: () => ScriptField;
-    onPointerDown?: () => ScriptField;
-    onPointerUp?: () => ScriptField;
-    onBrowseClick?: () => ScriptField | undefined;
-    onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
     dragStarting?: () => void;
     dragEnding?: () => void;
 }
-
-/**
- * props used by DocInternalViews and FieldViews but not DocumentViews
- * these props correspond to things that the DocumentView creates and thus doesn't need to receive as a prop
- */
-export interface DocumentViewInternalSharedProps {
-    select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
-    isSelected: () => boolean;
-    docViewPath: () => DocumentView[];
-    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
-}
-export interface DocumentViewInternalProps extends DocumentViewProps, DocumentViewInternalSharedProps {
-    docViewPublic: () => DocumentView;
-}
-
 @observer
-export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() {
+export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps>() {
+    // 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
+     */
+    public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
+
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _doubleClickTimeout: NodeJS.Timeout | undefined;
     private _singleClickFunc: undefined | (() => any);
@@ -262,79 +133,51 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _mainCont = React.createRef<HTMLDivElement>();
     private _titleRef = React.createRef<EditableView>();
     private _dropDisposer?: DragManager.DragDropDisposer;
-    constructor(props: any) {
+    constructor(props: FieldViewProps & DocumentViewProps) {
         super(props);
         makeObservable(this);
     }
 
-    @observable _componentView: Opt<DocComponentView> = undefined; // needs to be accessed from DocumentView wrapper class
+    @observable _changingTitleField = false;
+    @observable _titleDropDownInnerWidth = 0; // width of menu dropdown when setting doc title
+    @observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI )
+    @observable _isContentActive: boolean | undefined = undefined;
+    @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined;
+    @observable _componentView: Opt<ViewBoxInterface> = undefined; // needs to be accessed from DocumentView wrapper class
     @observable _animateScaleTime: Opt<number> = undefined; // milliseconds for animating between views.  defaults to 300 if not uset
     @observable _animateScalingTo = 0;
 
-    public get animateScaleTime() {
-        return this._animateScaleTime ?? 100;
-    }
-    public get displayName() {
-        return 'DocumentViewInternal(' + this.Document.title + ')';
-    } // this makes mobx trace() statements more descriptive
-
-    public get DocumentView() {
-        return this._props.docViewPublic;
-    }
+    get _contentDiv()  { return this._mainCont.current; } // prettier-ignore
+    get _docView()     { return this._props.DocumentView?.(); } // prettier-ignore
+
+    animateScaleTime = () => this._animateScaleTime ?? 100;
+    style = (doc: Doc, sprop: StyleProp | string) => this._props.styleProvider?.(doc, this._props, sprop);
+    @computed get layout_showTitle()   { return this.style(this.layoutDoc, StyleProp.ShowTitle) as Opt<string>; } // prettier-ignore
+    @computed get opacity()            { return this.style(this.layoutDoc, StyleProp.Opacity); } // prettier-ignore
+    @computed get boxShadow()          { return this.style(this.layoutDoc, StyleProp.BoxShadow); } // prettier-ignore
+    @computed get borderRounding()     { return this.style(this.layoutDoc, StyleProp.BorderRounding); } // prettier-ignore
+    @computed get widgetDecorations()  { return this.style(this.layoutDoc, StyleProp.Decorations); } // prettier-ignore
+    @computed get backgroundBoxColor() { return this.style(this.layoutDoc, StyleProp.BackgroundColor + ':box'); } // prettier-ignore
+    @computed get headerMargin()       { return this.style(this.layoutDoc, StyleProp.HeaderMargin) ?? 0; } // prettier-ignore
+    @computed get layout_showCaption() { return this.style(this.layoutDoc, StyleProp.ShowCaption) ?? 0;  } // prettier-ignore
+    @computed get titleHeight()        { return this.style(this.layoutDoc, StyleProp.TitleHeight) ?? 0;  } // prettier-ignore
+    @computed get docContents()        { return this.style(this.Document, StyleProp.DocContents); } // prettier-ignore
+    @computed get highlighting()       { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore
+    @computed get borderPath()         { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore
 
-    public get ContentDiv() {
-        return this._mainCont.current;
-    }
-    public get LayoutFieldKey() {
-        return Doc.LayoutFieldKey(this.layoutDoc);
-    }
-    @computed get styleProps(): FieldViewProps {
-        return { ...this._props, fieldKey: '' };
-    }
-    @computed get layout_showTitle() {
-        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.ShowTitle) as Opt<string>;
-    }
-    @computed get NativeDimScaling() {
-        return this._props.NativeDimScaling?.() || 1;
-    }
-    @computed get thumb() {
-        return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
-    }
-    @computed get opacity() {
-        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.Opacity);
-    }
-    @computed get boxShadow() {
-        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BoxShadow);
-    }
-    @computed get borderRounding() {
-        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BorderRounding);
-    }
-    @computed get widgetDecorations() {
-        TraceMobx();
-        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.Decorations);
-    }
-    @computed get backgroundBoxColor() {
-        return this._props.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.BackgroundColor + ':box');
-    }
-    @computed get docContents() {
-        return this._props.styleProvider?.(this.Document, this.styleProps, StyleProp.DocContents);
-    }
-    @computed get headerMargin() {
-        return this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.HeaderMargin) || 0;
-    }
-    @computed get layout_showCaption() {
-        return this._props?.hideCaptions ? undefined : this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.ShowCaption) || 0;
+    @computed get onClickHandler() {
+        return this._props.onClickScript?.() ?? this._props.onBrowseClickScript?.() ?? ScriptCast(this.Document.onClick, ScriptCast(this.layoutDoc.onClick));
     }
-    @computed get titleHeight() {
-        return this._props?.styleProvider?.(this.layoutDoc, this.styleProps, StyleProp.TitleHeight) || 0;
+    @computed get onDoubleClickHandler() {
+        return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick, ScriptCast(this.Document.onDoubleClick));
     }
-    @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined;
-    @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
-        return this._pointerEvents;
+    @computed get onPointerDownHandler() {
+        return this._props.onPointerDownScript?.() ?? ScriptCast(this.layoutDoc.onPointerDown, ScriptCast(this.Document.onPointerDown));
     }
-    @computed get finalLayoutKey() {
-        return StrCast(this.Document.layout_fieldKey, 'layout');
+    @computed get onPointerUpHandler() {
+        return this._props.onPointerUpScript?.() ?? ScriptCast(this.layoutDoc.onPointerUp, ScriptCast(this.Document.onPointerUp));
     }
+
     @computed get disableClickScriptFunc() {
         const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
         // prettier-ignore
@@ -344,23 +187,50 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             (onScriptDisable !== 'never' && (this.rootSelected() || this._componentView?.isAnyChildContentActive?.()))
         );
     }
-    @computed get onClickHandler() {
-        return this._props.onClick?.() ?? this._props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
+    @computed get _rootSelected() {
+        return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
     }
-    @computed get onDoubleClickHandler() {
-        return this._props.onDoubleClick?.() ?? Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick;
+    /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
+    @computed get _contentPointerEvents() {
+        TraceMobx();
+        return this._props.contentPointerEvents ??
+            ((!this.disableClickScriptFunc && //
+                this.onClickHandler &&
+                !this._props.onBrowseClickScript?.() &&
+                this.isContentActive() !== true) ||
+                this.isContentActive() === false)
+            ? 'none'
+            : this._pointerEvents;
     }
-    @computed get onPointerDownHandler() {
-        return this._props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown);
+
+    // We need to use allrelatedLinks to get not just links to the document as a whole, but links to
+    // 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.
+    @computed get directLinks() {
+        TraceMobx();
+        return LinkManager.Instance.getAllRelatedLinks(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)) ||
+                ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.Document)) ||
+                ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.Document))
+        );
     }
-    @computed get onPointerUpHandler() {
-        return this._props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp);
+    @computed get _allLinks() {
+        TraceMobx();
+        return LinkManager.Instance.getAllRelatedLinks(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);
     }
 
     componentWillUnmount() {
         this.cleanupHandlers(true);
     }
-    @observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI )
 
     componentDidMount() {
         runInAction(() => (this._mounted = true));
@@ -380,7 +250,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             { fireImmediately: true }
         );
         this._disposers.pointerevents = reaction(
-            () => this._props.styleProvider?.(this.Document, this.styleProps, StyleProp.PointerEvents),
+            () => this.style(this.Document, StyleProp.PointerEvents),
             pointerevents => (this._pointerEvents = pointerevents),
             { fireImmediately: true }
         );
@@ -406,18 +276,19 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
 
     startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
-        if (this._mainCont.current) {
+        const docView = this._docView;
+        if (this._mainCont.current && docView) {
             const views = SelectionManager.Views.filter(dv => dv.ContentDiv);
-            const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this.DocumentView()];
+            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 = this.DocumentView().screenToViewTransform();
+            const screenXf = docView.screenToViewTransform();
             const [left, top] = screenXf.inverse().transformPoint(0, 0);
             dragData.offset = screenXf.transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.treeViewDoc = this._props.treeViewDoc;
             dragData.removeDocument = this._props.removeDocument;
             dragData.moveDocument = this._props.moveDocument;
-            dragData.draggedViews = [this.DocumentView()];
+            dragData.draggedViews = [docView];
             dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
             DragManager.StartDocumentDrag(
                 selected.map(dv => dv.ContentDiv!),
@@ -434,12 +305,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (!StrCast(this.layoutDoc._layout_showTitle)) this.layoutDoc._layout_showTitle = 'title';
         setTimeout(() => this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined
     };
-
-    public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
-
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
         if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
-        if (!this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+        const documentView = this._docView;
+        if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
             let stopPropagate = true;
             let preventDefault = true;
             !this.layoutDoc._keepZWhenDragged && this._props.bringToFront(this.Document);
@@ -451,7 +320,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     const func = () => this.onDoubleClickHandler.script.run( {
                             this: this.Document,
                             scriptContext: this._props.scriptContext,
-                            documentView: this.DocumentView(),
+                            documentView,
                             clientX, clientY,  altKey,  shiftKey,  ctrlKey,
                             value: undefined,
                         }, console.log  );
@@ -482,7 +351,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                                 this: this.Document,
                                 _readOnly_: false,
                                 scriptContext: this._props.scriptContext,
-                                documentView: this.DocumentView(),
+                                documentView,
                                 clientX,
                                 clientY,
                                 shiftKey,
@@ -506,7 +375,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 const sendToBack = e.altKey;
                 this._singleClickFunc =
                     // prettier-ignore
-                    clickFunc ?? (() => (sendToBack ? this.DocumentView()._props.bringToFront(this.Document, true) : 
+                    clickFunc ?? (() => (sendToBack ? documentView._props.bringToFront(this.Document, true) : 
                                                       this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? 
                                         this._props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
                 const waitFordblclick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
@@ -525,12 +394,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     onPointerDown = (e: React.PointerEvent): void => {
         if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
-        this._longPressSelector = setTimeout(() => {
-            if (DocumentView.LongPress) {
-                this._props.select(false);
-            }
-        }, 1000);
-        if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.DocumentView();
+        this._longPressSelector = setTimeout(() => DocumentView.LongPress && this._props.select(false), 1000);
+        if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._docView;
 
         this._downX = e.clientX;
         this._downY = e.clientY;
@@ -541,7 +406,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             if (
                 // prettier-ignore
                 (this._props.isDocumentActive?.() || this._props.isContentActive?.()) &&
-                !this._props.onBrowseClick?.() &&
+                !this._props.onBrowseClickScript?.() &&
                 !this.Document.ignoreClick &&
                 e.button === 0 &&
                 !Doc.IsInMyOverlay(this.layoutDoc)
@@ -587,38 +452,39 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (DocumentView.LongPress) e.preventDefault();
     };
 
-    @undoBatch
-    toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => {
+    toggleFollowLink = undoable((zoom?: boolean, setTargetToggle?: boolean): void => {
         const hadOnClick = this.Document.onClick;
         this.noOnClick();
         this.Document.onClick = hadOnClick ? undefined : FollowLinkScript();
         this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never';
-    };
-    @undoBatch
-    followLinkOnClick = () => {
+    }, 'toggle follow link');
+
+    followLinkOnClick = undoable(() => {
         this.Document.ignoreClick = false;
         this.Document.onClick = FollowLinkScript();
         this.Document.followLinkToggle = false;
         this.Document.followLinkZoom = false;
         this.Document.followLinkLocation = undefined;
-    };
-    @undoBatch
-    noOnClick = () => {
-        this.Document.ignoreClick = false;
-        this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined;
-    };
-
-    @undoBatch deleteClicked = () => this._props.removeDocument?.(this.Document);
-    @undoBatch setToggleDetail = () =>
-        (this.Document.onClick = ScriptField.MakeScript(
-            `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
-                .replace('layout_', '')
-                .replace(/^layout$/, 'detail')}")`,
-            { documentView: 'any' }
-        ));
+    }, 'follow link on click');
 
-    @undoBatch
-    drop = (e: Event, de: DragManager.DropEvent) => {
+    noOnClick = undoable(() => {
+        this.Document.ignoreClick = false;
+        this.Document.onClick = this.Document[DocData].onClick = undefined;
+    }, 'default on click');
+
+    deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
+    setToggleDetail = undoable(
+        () =>
+            (this.Document.onClick = ScriptField.MakeScript(
+                `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
+                    .replace('layout_', '')
+                    .replace(/^layout$/, 'detail')}")`,
+                { documentView: 'any' }
+            )),
+        'set toggle detail'
+    );
+
+    drop = undoable((e: Event, de: DragManager.DropEvent) => {
         if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
         if (this.Document === Doc.ActiveDashboard) {
             e.stopPropagation();
@@ -642,7 +508,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
                     if (de.complete.linkDocument) {
                         de.complete.linkDocument.layout_isSvg = true;
-                        this.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
+                        this._docView?.CollectionFreeFormView?.addDocument(de.complete.linkDocument);
                     }
                 }
                 e.stopPropagation();
@@ -650,11 +516,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             }
         }
         return false;
-    };
+    }, 'drop doc');
 
-    @undoBatch
-    makeIntoPortal = () => {
-        const portalLink = this.allLinks.find(d => d.link_anchor_1 === this.Document && d.link_relationship === 'portal to:portal from');
+    makeIntoPortal = undoable(() => {
+        const portalLink = this._allLinks.find(d => d.link_anchor_1 === this.Document && d.link_relationship === 'portal to:portal from');
         if (!portalLink) {
             DocUtils.MakeLink(
                 this.Document,
@@ -670,7 +535,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         this.Document.followLinkLocation = OpenWhere.lightbox;
         this.Document.onClick = FollowLinkScript();
-    };
+    }, 'make into portal');
 
     importDocument = () => {
         const input = document.createElement('input');
@@ -716,7 +581,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         if (e && !(e.nativeEvent as any).dash) {
             const onDisplay = () => {
-                if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this._props.isSelected() && SelectionManager.SelectView(this.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
+                if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && this._props.select(false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
                 setTimeout(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY));
             };
             if (navigator.userAgent.includes('Macintosh')) {
@@ -811,7 +676,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const constantItems: ContextMenuProps[] = [];
         if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) {
             constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.Document) });
-            (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.DocumentView()), icon: 'users' });
+            (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._docView), icon: 'users' });
             if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) {
                 // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
                 constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
@@ -877,63 +742,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, undefined);
     };
 
-    @computed get _rootSelected() {
-        return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
-    }
     rootSelected = () => this._rootSelected;
     panelHeight = () => this._props.PanelHeight() - this.headerMargin;
     screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
-    onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
+    onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler;
     setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
-    setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
-    @observable _isContentActive: boolean | undefined = undefined;
-
+    setContentView = action((view: ViewBoxInterface) => (this._componentView = view));
     isContentActive = (): boolean | undefined => this._isContentActive;
     childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
 
-    /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
-    @computed get _contentPointerEvents() {
-        TraceMobx();
-        return this._props.contentPointerEvents ??
-            ((!this.disableClickScriptFunc && //
-                this.onClickHandler &&
-                !this._props.onBrowseClick?.() &&
-                this.isContentActive() !== true) ||
-                this.isContentActive() === false)
-            ? 'none'
-            : this.pointerEvents;
-    }
     contentPointerEvents = () => this._contentPointerEvents;
-    @computed get contents() {
-        TraceMobx();
-        const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
-        const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
-        return (
-            <div
-                className="documentView-contentsView"
-                style={{
-                    pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
-                    height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
-                }}>
-                <DocumentContentsView
-                    key={1}
-                    {...this.styleProps}
-                    fieldKey={this.finalLayoutKey}
-                    pointerEvents={this.contentPointerEvents}
-                    setContentView={this.setContentView}
-                    childFilters={this.childFilters}
-                    PanelHeight={this.panelHeight}
-                    setHeight={this.setHeight}
-                    isContentActive={this.isContentActive}
-                    ScreenToLocalTransform={this.screenToLocalContent}
-                    rootSelected={this.rootSelected}
-                    onClick={this.onClickFunc}
-                    setTitleFocus={this.setTitleFocus}
-                />
-                {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
-            </div>
-        );
-    }
 
     anchorPanelWidth = () => this._props.PanelWidth() || 1;
     anchorPanelHeight = () => this._props.PanelHeight() || 1;
@@ -950,33 +768,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         return this._props.styleProvider?.(doc, props, property);
     };
-    // We need to use allrelatedLinks to get not just links to the document as a whole, but links to
-    // 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.
-    @computed get directLinks() {
-        TraceMobx();
-        return LinkManager.Instance.getAllRelatedLinks(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)) ||
-                ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.Document)) ||
-                ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.Document))
-        );
-    }
-    @computed get allLinks() {
-        TraceMobx();
-        return LinkManager.Instance.getAllRelatedLinks(this.Document);
-    }
-    hideLink = computedFn((link: Doc) => () => (link.link_displayLine = false));
-    @computed get allLinkEndpoints() {
+
+    removeLinkByHiding = (link: Doc) => () => (link.link_displayLine = false);
+    allLinkEndpoints = () => {
         // the small blue dots that mark the endpoints of links
-        TraceMobx();
         if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
-        const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
-        return filtered.map(link => (
+        return this.filteredLinks.map(link => (
             <div className="documentView-anchorCont" key={link[Id]}>
                 <DocumentView
                     {...this._props}
@@ -990,107 +787,54 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     hideCaptions={true}
                     hideLinkAnchors={true}
                     layout_fitWidth={returnTrue}
-                    removeDocument={this.hideLink(link)}
+                    removeDocument={this.removeLinkByHiding(link)}
                     styleProvider={this.anchorStyleProvider}
                     LayoutTemplate={undefined}
                     LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.Document)}`)}
                 />
             </div>
         ));
-    }
-
-    static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
-        let gumStream: any;
-        let recorder: any;
-        navigator.mediaDevices
-            .getUserMedia({
-                audio: true,
-            })
-            .then(function (stream) {
-                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),
-                    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 === undefined) {
-                            dataDoc[field + '_audioAnnotations'] = new List([audioField]);
-                        } else {
-                            audioAnnos.push(audioField);
-                        }
-                    }
-                };
-                //runInAction(() => (dataDoc.audioAnnoState = 'recording'));
-                recorder.start();
-                const stopFunc = () => {
-                    recorder.stop();
-                    DictationManager.Controls.stop(false);
-                    runInAction(() => (dataDoc.audioAnnoState = 'stopped'));
-                    gumStream.getAudioTracks()[0].stop();
-                };
-                if (onRecording) onRecording(stopFunc);
-                else setTimeout(stopFunc, 5000);
-            });
-    }
-    playAnnotation = () => {
-        const self = this;
-        const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped';
-        const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
-        const anno = audioAnnos?.lastElement();
-        if (anno instanceof AudioField) {
-            switch (audioAnnoState) {
-                case 'stopped':
-                    this.dataDoc[AudioPlay] = new Howl({
-                        src: [anno.url.href],
-                        format: ['mp3'],
-                        autoplay: true,
-                        loop: false,
-                        volume: 0.5,
-                        onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')),
-                    });
-                    this.dataDoc.audioAnnoState = 'playing';
-                    break;
-                case 'playing':
-                    this.dataDoc[AudioPlay]?.stop();
-                    this.dataDoc.audioAnnoState = 'stopped';
-                    break;
-            }
-        }
+    viewBoxContents = () => {
+        TraceMobx();
+        const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
+        const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+        return (
+            <div
+                className="documentView-contentsView"
+                style={{
+                    pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
+                    height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
+                }}>
+                <DocumentContentsView
+                    {...this._props}
+                    layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
+                    pointerEvents={this.contentPointerEvents}
+                    setContentViewBox={this.setContentView}
+                    childFilters={this.childFilters}
+                    PanelHeight={this.panelHeight}
+                    setHeight={this.setHeight}
+                    isContentActive={this.isContentActive}
+                    ScreenToLocalTransform={this.screenToLocalContent}
+                    rootSelected={this.rootSelected}
+                    onClickScript={this.onClickFunc}
+                    setTitleFocus={this.setTitleFocus}
+                />
+                {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints()}
+            </div>
+        );
     };
 
     captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
-    @observable _changingTitleField = false;
-    @observable _dropDownInnerWidth = 0;
-    fieldsDropdown = (inputOptions: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
-        const filteredOptions = new Set(inputOptions);
-        const scaling = this.titleHeight / 30; /* height of Dropdown */
-        Object.entries(DocOptions)
-            .filter(opts => opts[1].filterable)
-            .forEach((pair: [string, FInfo]) => filteredOptions.add(pair[0]));
-        filteredOptions.add(StrCast(this.layoutDoc.layout_showTitle));
-        const options = Array.from(filteredOptions)
-            .filter(f => f)
-            .map(facet => ({ val: facet, text: facet }));
+    fieldsDropdown = (reqdFields: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
+        const filteredFields = Object.entries(DocOptions).reduce((set, [field, opts]) => (opts.filterable ? set.add(field) : set), new Set(reqdFields));
         return (
             <div style={{ width: dropdownWidth }}>
                 <div
-                    ref={action((r: any) => r && (this._dropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))}
+                    ref={action((r: any) => r && (this._titleDropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))}
                     onPointerDown={action(e => (this._changingTitleField = true))}
-                    style={{ width: 'max-content', transformOrigin: 'left', transform: `scale(${scaling})` }}>
+                    style={{ width: 'max-content', transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
                     <Dropdown
                         activeChanged={action(isOpen => !isOpen && (this._changingTitleField = false))}
                         selectedVal={placeholder}
@@ -1100,7 +844,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         type={Type.TERT}
                         closeOnSelect={true}
                         dropdownType={DropdownType.SELECT}
-                        items={options}
+                        items={Array.from(filteredFields).map(facet => ({ val: facet, text: facet }))}
                         width={100}
                         fillWidth
                     />
@@ -1108,43 +852,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>
         );
     };
-    @computed get innards() {
-        TraceMobx();
+    /**
+     * displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by
+     * setting layout_showTitle using the format:   field1[;field2[...][:hover]]
+     * from the UI, this is done by clicking the title field and prefixin the format with '#'.  eg.,  #field1[;field2;...][:hover]
+     **/
+    titleView = () => {
         const showTitle = this.layout_showTitle?.split(':')[0];
         const showTitleHover = this.layout_showTitle?.includes(':hover');
-        const captionView = !this.layout_showCaption ? null : (
-            <div
-                className="documentView-captionWrapper"
-                style={{
-                    pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
-                    background: StrCast(this.layoutDoc._backgroundColor, 'rgba(0,0,0,0.2)'),
-                    color: lightOrDark(StrCast(this.layoutDoc._backgroundColor, 'black')),
-                }}>
-                <FormattedTextBox
-                    {...this._props}
-                    yPadding={10}
-                    xPadding={10}
-                    fieldKey={this.layout_showCaption}
-                    styleProvider={this.captionStyleProvider}
-                    dontRegisterView={true}
-                    noSidebar={true}
-                    dontScale={true}
-                    renderDepth={this._props.renderDepth}
-                    isContentActive={this.isContentActive}
-                />
-            </div>
-        );
-        const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.Document;
-        const background = StrCast(
-            this.layoutDoc.layout_headingColor,
-            StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor))
-        );
-        const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._dropDownInnerWidth * this.titleHeight) / 30) : 0;
-        const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
-        // displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by
-        // setting layout_showTitle using the format:   field1[;field2[...][:hover]]
-        // from the UI, this is done by clicking the title field and prefixin the format with '#'.  eg.,  #field1[;field2;...][:hover]
-        const titleView = !showTitle ? null : (
+
+        const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.Document;
+        const background = StrCast(
+            this.layoutDoc.layout_headingColor,
+            StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor))
+        );
+        const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0;
+        const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
+
+        return !showTitle ? null : (
             <div
                 className={`documentView-titleWrapper${showTitleHover ? '-hover' : ''}`}
                 key="title"
@@ -1160,7 +885,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 {!dropdownWidth
                     ? null
                     : this.fieldsDropdown(
-                          [],
+                          [StrCast(this.layoutDoc.layout_showTitle)],
                           dropdownWidth,
                           StrCast(this.layoutDoc.layout_showTitle).split(':')[0],
                           action((field: string | number) => {
@@ -1206,19 +931,36 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 </div>
             </div>
         );
-        return this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
-            this.contents
-        ) : (
-            <div className="documentView-styleWrapper">
-                {titleView}
-                {this.contents}
-                {captionView}
+    };
+
+    captionView = () => {
+        return !this.layout_showCaption ? null : (
+            <div
+                className="documentView-captionWrapper"
+                style={{
+                    pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
+                    background: StrCast(this.layoutDoc._backgroundColor, 'rgba(0,0,0,0.2)'),
+                    color: lightOrDark(StrCast(this.layoutDoc._backgroundColor, 'black')),
+                }}>
+                <FormattedTextBox
+                    {...this._props}
+                    yPadding={10}
+                    xPadding={10}
+                    fieldKey={this.layout_showCaption}
+                    styleProvider={this.captionStyleProvider}
+                    dontRegisterView={true}
+                    noSidebar={true}
+                    dontScale={true}
+                    renderDepth={this._props.renderDepth}
+                    isContentActive={this.isContentActive}
+                />
             </div>
         );
-    }
+    };
 
     renderDoc = (style: object) => {
         TraceMobx();
+        const showTitle = this.layout_showTitle?.split(':')[0];
         return !DocCast(this.Document) || GetEffectiveAcl(this.dataDoc) === AclPrivate
             ? null
             : this.docContents ?? (
@@ -1234,49 +976,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                           fontFamily: StrCast(this.Document._text_fontFamily, 'inherit'),
                           fontSize: Cast(this.Document._text_fontSize, 'string', null),
                           transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
-                          transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
+                          transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime() / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
                       }}>
-                      {this.innards}
+                      {this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
+                          this.viewBoxContents()
+                      ) : (
+                          <div className="documentView-styleWrapper">
+                              {this.titleView()}
+                              {this.viewBoxContents()}
+                              {this.captionView()}
+                          </div>
+                      )}
                       {this.widgetDecorations ?? null}
                   </div>
               );
     };
 
-    /**
-     * returns an entrance animation effect function to wrap a JSX element
-     * @param presEffectDoc presentation effects document that specifies the animation effect parameters
-     * @returns a function that will wrap a JSX animation element wrapping any JSX element
-     */
-    public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) {
-        const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection;
-        const effectProps = {
-            left: dir === PresEffectDirection.Left,
-            right: dir === PresEffectDirection.Right,
-            top: dir === PresEffectDirection.Top,
-            bottom: dir === PresEffectDirection.Bottom,
-            opposite: true,
-            delay: 0,
-            duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)),
-        };
-        //prettier-ignore
-        switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
-            default: 
-            case PresEffect.None:       return renderDoc;
-            case PresEffect.Zoom:       return <Zoom {...effectProps}>{renderDoc}</Zoom>;
-            case PresEffect.Fade:       return <Fade {...effectProps}>{renderDoc}</Fade>;
-            case PresEffect.Flip:       return <Flip {...effectProps}>{renderDoc}</Flip>;
-            case PresEffect.Rotate:     return <Rotate {...effectProps}>{renderDoc}</Rotate>;
-            case PresEffect.Bounce:     return <Bounce {...effectProps}>{renderDoc}</Bounce>;
-            case PresEffect.Roll:       return <Roll {...effectProps}>{renderDoc}</Roll>;
-            case PresEffect.Lightspeed: return <JackInTheBox {...effectProps}>{renderDoc}</JackInTheBox>;
-        }
-    }
-    @computed get highlighting() {
-        return this._props.styleProvider?.(this.Document, { ...this._props, fieldKey: '' }, StyleProp.Highlighting);
-    }
-    @computed get borderPath() {
-        return this._props.styleProvider?.(this.Document, { ...this._props, fieldKey: '' }, StyleProp.BorderPath);
-    }
     render() {
         TraceMobx();
         const highlighting = this.highlighting;
@@ -1304,10 +1019,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 onClick={this.onClick}
                 onPointerEnter={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
                 onPointerOver={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
-                onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)}
+                onPointerLeave={e => !isParentOf(this._contentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)}
                 style={{
                     borderRadius: this.borderRounding,
-                    pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
+                    pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
                 }}>
                 <>
                     {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
@@ -1316,176 +1031,124 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>
         );
     }
-}
 
-@observer
-export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
-    public static ROOT_DIV = 'documentView-effectsWrapper';
-
-    constructor(props: any) {
-        super(props);
-        makeObservable(this);
+    /**
+     * returns an entrance animation effect function to wrap a JSX element
+     * @param presEffectDoc presentation effects document that specifies the animation effect parameters
+     * @returns a function that will wrap a JSX animation element wrapping any JSX element
+     */
+    public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) {
+        const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection;
+        const effectProps = {
+            left: dir === PresEffectDirection.Left,
+            right: dir === PresEffectDirection.Right,
+            top: dir === PresEffectDirection.Top,
+            bottom: dir === PresEffectDirection.Bottom,
+            opposite: true,
+            delay: 0,
+            duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)),
+        };
+        //prettier-ignore
+        switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
+            default: 
+            case PresEffect.None:       return renderDoc;
+            case PresEffect.Zoom:       return <Zoom {...effectProps}>{renderDoc}</Zoom>;
+            case PresEffect.Fade:       return <Fade {...effectProps}>{renderDoc}</Fade>;
+            case PresEffect.Flip:       return <Flip {...effectProps}>{renderDoc}</Flip>;
+            case PresEffect.Rotate:     return <Rotate {...effectProps}>{renderDoc}</Rotate>;
+            case PresEffect.Bounce:     return <Bounce {...effectProps}>{renderDoc}</Bounce>;
+            case PresEffect.Roll:       return <Roll {...effectProps}>{renderDoc}</Roll>;
+            case PresEffect.Lightspeed: return <JackInTheBox {...effectProps}>{renderDoc}</JackInTheBox>;
+        }
     }
+    public static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
+        let gumStream: any;
+        let recorder: any;
+        navigator.mediaDevices
+            .getUserMedia({
+                audio: true,
+            })
+            .then(function (stream) {
+                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),
+                    continuous: { indefinite: false },
+                }).then(results => {
+                    if (results && [DictationManager.Controls.Infringed].includes(results)) {
+                        DictationManager.Controls.stop();
+                    }
+                    onEnd?.();
+                });
 
-    @observable _selected = false;
-    public get IsSelected() {
-        return this._selected;
-    }
-    public set IsSelected(val) {
-        runInAction(() => (this._selected = val));
-    }
-    @observable public static LongPress = false;
-    @computed public static get exploreMode() {
-        return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
+                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 === undefined) {
+                            dataDoc[field + '_audioAnnotations'] = new List([audioField]);
+                        } else {
+                            audioAnnos.push(audioField);
+                        }
+                    }
+                };
+                //runInAction(() => (dataDoc.audioAnnoState = 'recording'));
+                recorder.start();
+                const stopFunc = () => {
+                    recorder.stop();
+                    DictationManager.Controls.stop(false);
+                    runInAction(() => (dataDoc.audioAnnoState = 'stopped'));
+                    gumStream.getAudioTracks()[0].stop();
+                };
+                if (onRecording) onRecording(stopFunc);
+                else setTimeout(stopFunc, 5000);
+            });
     }
-    @observable private _docViewInternal: DocumentViewInternal | undefined | null = undefined;
-    @observable private _htmlOverlayText: Opt<string> = undefined;
-    @observable private _isHovering = false;
-    private _htmlOverlayEffect: Opt<Doc>;
+}
 
-    public get displayName() {
-        return 'DocumentView(' + this.Document?.title + ')';
-    } // this makes mobx trace() statements more descriptive
+@observer
+export class DocumentView extends DocComponent<DocumentViewProps>() {
+    public static ROOT_DIV = 'documentView-effectsWrapper';
+    public get displayName() {  return 'DocumentView(' + this.Document?.title + ')';  } // prettier-ignore
     public ContentRef = React.createRef<HTMLDivElement>();
-    public ViewTimer: NodeJS.Timeout | undefined; // timer for res
-    public AnimEffectTimer: NodeJS.Timeout | undefined; // timer for res
+    private _htmlOverlayEffect: Opt<Doc>;
     private _disposers: { [name: string]: IReactionDisposer } = {};
-    public clearViewTransition = () => {
-        this.ViewTimer && clearTimeout(this.ViewTimer);
-        this.layoutDoc._viewTransition = undefined;
-    };
-    playAnnotation = () => this._docViewInternal?.playAnnotation();
-    noOnClick = () => this._docViewInternal?.noOnClick();
-    makeIntoPortal = () => this._docViewInternal?.makeIntoPortal();
-    setToggleDetail = () => this._docViewInternal?.setToggleDetail();
-    onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY);
-    cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents();
-    public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource);
+    private _viewTimer: NodeJS.Timeout | undefined;
+    private _animEffectTimer: NodeJS.Timeout | undefined;
 
-    public showContextMenu = (pageX: number, pageY: number) => this._docViewInternal?.onContextMenu(undefined, pageX, pageY);
-
-    public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => {
-        this._htmlOverlayText = text;
-        this._htmlOverlayEffect = effect;
-    });
-    public setAnimateScaling = action((scale: number, time?: number) => {
-        if (this._docViewInternal) {
-            this._docViewInternal._animateScalingTo = scale;
-            this._docViewInternal._animateScaleTime = time;
-        }
-    });
-    public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
-        this.AnimEffectTimer && clearTimeout(this.AnimEffectTimer);
-        this.Document[Animation] = presEffect;
-        this.AnimEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs);
-    };
-    public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
-        this.layoutDoc._viewTransition = `${transProp} ${timeInMs}ms`;
-        if (dataTrans) this.Document._dataTransition = `${transProp} ${timeInMs}ms`;
-        this.ViewTimer && clearTimeout(this.ViewTimer);
-        return (this.ViewTimer = setTimeout(() => {
-            this.layoutDoc._viewTransition = undefined;
-            this.Document._dataTransition = 'inherit';
-            afterTrans?.();
-        }, timeInMs + 10));
-    };
-    public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) {
-        docs.forEach(doc => {
-            doc._viewTransition = `${transProp} ${timeInMs}ms`;
-            dataTrans && (doc.dataTransition = `${transProp} ${timeInMs}ms`);
-        });
-        return setTimeout(
-            () =>
-                docs.forEach(doc => {
-                    doc._viewTransition = undefined;
-                    dataTrans && (doc.dataTransition = 'inherit');
-                    afterTrans?.();
-                }),
-            timeInMs + 10
-        );
+    @computed public static get exploreMode() {
+        return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
     }
 
-    // shows a stacking view collection (by default, but the user can change) of all documents linked to the source
-    public static showBackLinks(linkAnchor: Doc) {
-        const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
-        // prettier-ignore
-        DocServer.GetRefField(docId).then(docx =>
-            LightboxView.Instance.SetLightboxDoc(
-                (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection
-                Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, onViewMounted: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId)
-            )
-        );
+    constructor(props: DocumentViewProps) {
+        super(props);
+        makeObservable(this);
     }
-    toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle);
 
-    get Document() {
-        return this._props.Document;
-    }
-    get topMost() {
-        return this._props.renderDepth === 0;
-    }
-    get dataDoc() {
-        return this._docViewInternal?.dataDoc ?? this.Document;
-    }
-    get ContentDiv() {
-        return this._docViewInternal?.ContentDiv;
-    }
-    get ComponentView() {
-        return this._docViewInternal?._componentView;
-    }
-    get allLinks() {
-        return (this._docViewInternal?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
-    }
-    get LayoutFieldKey() {
-        return this._docViewInternal?.LayoutFieldKey || 'layout';
-    }
-    @computed get layout_fitWidth() {
-        return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
-    }
-    @computed get anchorViewDoc() {
-        return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
-    }
-    @computed get hideLinkButton() {
-        return (
-            this._props.hideLinkButton ||
-            this._props.renderDepth === -1 || //
-            (this.IsSelected && this._props.renderDepth) ||
-            !this._isHovering ||
-            (!this.IsSelected && this.layoutDoc.layout_hideLinkButton) ||
-            SnappingManager.IsDragging ||
-            SnappingManager.IsResizing
-        );
-    }
-    hideLinkCount = () => (this.hideLinkButton ? true : false);
+    // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed.
+    // unfortunately, CSS can't transition animate any properties for something that is display 'none'.
+    // so we need to first activate the div, then, after a render timeout, start the opacity transition.
+    @observable private _enableHtmlOverlayTransitions: boolean = false;
+    @observable private _docViewInternal: DocumentViewInternal | undefined | null = undefined;
+    @observable private _htmlOverlayText: Opt<string> = undefined;
+    @observable private _isHovering = false;
+    @observable private _selected = false;
+    @observable public static LongPress = false;
 
-    @computed get linkCountView() {
-        return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
-    }
-    /**
-     * path of DocumentViews hat contains this DocumentView (does not includes this DocumentView thouhg)
-     */
-    @computed get containerViewPath() {
-        return this._props.containerViewPath;
-    }
-    @computed get layoutDoc() {
-        return Doc.Layout(this.Document, this._props.LayoutTemplate?.());
-    }
-    @computed get nativeWidth() {
-        return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
-    }
-    @computed get nativeHeight() {
-        return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
-    }
-    @computed get shouldNotScale() {
+    @computed private get shouldNotScale() {
         return (this.layout_fitWidth && !this.nativeWidth) || this._props.LayoutTemplateString?.includes(KeyValueBox.name) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
     }
-    @computed get effectiveNativeWidth() {
+    @computed private get effectiveNativeWidth() {
         return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width);
     }
-    @computed get effectiveNativeHeight() {
+    @computed private get effectiveNativeHeight() {
         return this.shouldNotScale ? 0 : this.nativeHeight || NumCast(this.layoutDoc.height);
     }
-    @computed get nativeScaling() {
+    @computed private get nativeScaling() {
         if (this.shouldNotScale) return 1;
         const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
         if (this.layout_fitWidth || this._props.PanelHeight() / (this.effectiveNativeHeight || 1) > this._props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
@@ -1493,19 +1156,19 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         }
         return Math.max(minTextScale, this._props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
     }
-    @computed get panelWidth() {
+    @computed private get panelWidth() {
         return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this._props.PanelWidth();
     }
-    @computed get panelHeight() {
+    @computed private get panelHeight() {
         if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.layout_reflowVertical)) {
             return Math.min(this._props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling);
         }
         return this._props.PanelHeight();
     }
-    @computed get Xshift() {
+    @computed private get Xshift() {
         return this.effectiveNativeWidth ? Math.max(0, (this._props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2) : 0;
     }
-    @computed get Yshift() {
+    @computed private get Yshift() {
         return this.effectiveNativeWidth &&
             this.effectiveNativeHeight &&
             Math.abs(this.Xshift) < 0.001 &&
@@ -1513,41 +1176,99 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
             ? Math.max(0, (this._props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2)
             : 0;
     }
-    @computed get centeringX() {
-        return this._props.dontCenter?.includes('x') ? 0 : this.Xshift;
+    @computed private get hideLinkButton() {
+        return (
+            this._props.hideLinkButton ||
+            this._props.renderDepth === -1 || //
+            (this.IsSelected && this._props.renderDepth) ||
+            !this._isHovering ||
+            (!this.IsSelected && this.layoutDoc.layout_hideLinkButton) ||
+            SnappingManager.IsDragging ||
+            SnappingManager.IsResizing
+        );
+    }
+
+    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);
+    }
+
+    componentWillUnmount() {
+        runInAction(() => this.Document[DocViews].delete(this));
+        Object.values(this._disposers).forEach(disposer => disposer?.());
+        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
     }
-    @computed get centeringY() {
-        return this._props.dontCenter?.includes('y') ? 0 : this.Yshift;
+
+    public set IsSelected(val)  { runInAction(() => (this._selected = val)); } // prettier-ignore
+    public get IsSelected()     { return this._selected; } // prettier-ignore
+    public get topMost()        { return this._props.renderDepth === 0; } // prettier-ignore
+    public get ContentDiv()     { return this._docViewInternal?._contentDiv; } // prettier-ignore
+    public get ComponentView()  { return this._docViewInternal?._componentView; } // prettier-ignore
+    public get allLinks()       { return this._docViewInternal?._allLinks ?? []; } // prettier-ignore
+
+    get LayoutFieldKey() {
+        return Doc.LayoutFieldKey(this.Document, this._props.LayoutTemplateString);
     }
 
-    @computed get CollectionFreeFormView() {
-        return this.CollectionFreeFormDocumentView?.CollectionFreeFormView;
+    @computed get layout_fitWidth() {
+        return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
     }
-    @computed get CollectionFreeFormDocumentView() {
-        return this._props.CollectionFreeFormDocumentView?.();
+    @computed get anchorViewDoc() {
+        return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
     }
 
-    public toggleNativeDimensions = () => this._docViewInternal && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this._docViewInternal.NativeDimScaling, this._props.PanelWidth(), this._props.PanelHeight());
-    public getBounds = () => {
-        if (!this._docViewInternal?.ContentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
+    @computed get getBounds() {
+        if (!this._docViewInternal?._contentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
             return undefined;
         }
         if (this._docViewInternal._componentView?.screenBounds?.()) {
             return this._docViewInternal._componentView.screenBounds();
         }
-        const xf = this._docViewInternal._props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
+        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._docViewInternal._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
-            const docuBox = this._docViewInternal.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
+        if (this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+            const docuBox = this._docViewInternal._contentDiv.getElementsByClassName('linkAnchorBox-cont');
             if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
         }
         return { left, top, right, bottom };
+    }
+
+    @computed get nativeWidth() {
+        return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
+    }
+    @computed get nativeHeight() {
+        return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
+    }
+    @computed public get centeringX() { return this._props.dontCenter?.includes('x') ? 0 : this.Xshift; } // prettier-ignore
+    @computed public get centeringY() { return this._props.dontCenter?.includes('y') ? 0 : this.Yshift; } // prettier-ignore
+
+    /**
+     * path of DocumentViews hat contains this DocumentView (does not includes this DocumentView thouhg)
+     */
+    public get containerViewPath() { return this._props.containerViewPath; } // prettier-ignore
+    public get CollectionFreeFormView() { return this.CollectionFreeFormDocumentView?.CollectionFreeFormView; } // prettier-ignore
+    public get CollectionFreeFormDocumentView() { return this._props.CollectionFreeFormDocumentView?.(); } // prettier-ignore
+
+    public clearViewTransition = () => {
+        this._viewTimer && clearTimeout(this._viewTimer);
+        this.layoutDoc._viewTransition = undefined;
     };
+    public noOnClick = () => this._docViewInternal?.noOnClick();
+    public makeIntoPortal = () => this._docViewInternal?.makeIntoPortal();
+    public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle);
+    public setToggleDetail = () => this._docViewInternal?.setToggleDetail();
+    public onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY);
+    public cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents();
+    public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource);
+    public showContextMenu = (pageX: number, pageY: number) => this._docViewInternal?.onContextMenu(undefined, pageX, pageY);
+
+    public toggleNativeDimensions = () => this._docViewInternal && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.NativeDimScaling() ?? 1, this._props.PanelWidth(), this._props.PanelHeight());
 
     public iconify(finished?: () => void, animateTime?: number) {
         this.ComponentView?.updateIcon?.();
-        const animTime = this._docViewInternal?._animateScaleTime;
+        const animTime = this._docViewInternal?.animateScaleTime();
         runInAction(() => this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime));
         const finalFinished = action(() => {
             finished?.();
@@ -1564,13 +1285,58 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
             this._props.bringToFront(this.Document);
         }
     }
-    @undoBatch
-    setCustomView = (custom: boolean, layout: string): void => {
+
+    public playAnnotation = () => {
+        const self = this;
+        const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped';
+        const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
+        const anno = audioAnnos?.lastElement();
+        if (anno instanceof AudioField) {
+            switch (audioAnnoState) {
+                case 'stopped':
+                    this.dataDoc[AudioPlay] = new Howl({
+                        src: [anno.url.href],
+                        format: ['mp3'],
+                        autoplay: true,
+                        loop: false,
+                        volume: 0.5,
+                        onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')),
+                    });
+                    this.dataDoc.audioAnnoState = 'playing';
+                    break;
+                case 'playing':
+                    this.dataDoc[AudioPlay]?.stop();
+                    this.dataDoc.audioAnnoState = 'stopped';
+                    break;
+            }
+        }
+    };
+
+    public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => {
+        this._htmlOverlayText = text;
+        this._htmlOverlayEffect = effect;
+    });
+    public setAnimateScaling = action((scale: number, time?: number) => {
+        if (this._docViewInternal) {
+            this._docViewInternal._animateScalingTo = scale;
+            this._docViewInternal._animateScaleTime = time;
+        }
+    });
+    public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
+        this._animEffectTimer && clearTimeout(this._animEffectTimer);
+        this.Document[Animation] = presEffect;
+        this._animEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs);
+    };
+    public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
+        this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, afterTrans, dataTrans);
+    };
+
+    public setCustomView = undoable((custom: boolean, layout: string): void => {
         Doc.setNativeView(this.Document);
         custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined);
-    };
+    }, 'set custom view');
 
-    switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
+    public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
         runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc
         setTimeout(
             action(() => {
@@ -1585,16 +1351,19 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                         this._docViewInternal && (this._docViewInternal._animateScalingTo = 0);
                         finished?.();
                     }),
-                    this._docViewInternal ? Math.max(0, this._docViewInternal.animateScaleTime - 10) : 0
+                    Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10)
                 );
             }),
-            this._docViewInternal ? Math.max(0, this._docViewInternal?.animateScaleTime - 10) : 0
+            Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10)
         );
     };
+    /**
+     * @returns a hierarchy path through  the nested DocumentViews that display this view.  The last element of the path is this view.
+     */
+    public docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]);
 
     layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
     screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale;
-    docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]);
     isSelected = () => this.IsSelected;
     select = (extendSelection: boolean, focusSelection?: boolean) => {
         if (this.IsSelected && SelectionManager.Views.length > 1) SelectionManager.DeselectView(this);
@@ -1615,6 +1384,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     PanelWidth = () => this.panelWidth;
     PanelHeight = () => this.panelHeight;
     NativeDimScaling = () => this.nativeScaling;
+    hideLinkCount = () => (this.hideLinkButton ? true : false);
     selfView = () => this;
     /**
      * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView)
@@ -1629,34 +1399,19 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
             .translate(-this.centeringX, -this.centeringY)
             .scale(1 / this.nativeScaling);
 
-    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);
-    }
-
-    componentWillUnmount() {
-        runInAction(() => this.Document[DocViews].delete(this));
-        Object.values(this._disposers).forEach(disposer => disposer?.());
-        !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
-    }
-    // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed.
-    // unfortunately, CSS can't transition animate any properties for something that is display 'none'.
-    // so we need to first activate the div, then, after a render timeout, start the opacity transition.
-    @observable enableHtmlOverlayTransitions: boolean = false;
-    @computed get htmlOverlay() {
+    htmlOverlay = () => {
         const effect = StrCast(this._htmlOverlayEffect?.presentation_effect, StrCast(this._htmlOverlayEffect?.followLinkAnimEffect));
         return (
             <div
                 className="documentView-htmlOverlay"
                 ref={r => {
                     const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition
-                    if (r && val !== this.enableHtmlOverlayTransitions) {
-                        setTimeout(action(() => (this.enableHtmlOverlayTransitions = val)));
+                    if (r && val !== this._enableHtmlOverlayTransitions) {
+                        setTimeout(action(() => (this._enableHtmlOverlayTransitions = val)));
                     }
                 }}
                 style={{ display: !this._htmlOverlayText ? 'none' : undefined }}>
-                <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this.enableHtmlOverlayTransitions ? 0.9 : 0 }}>
+                <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this._enableHtmlOverlayTransitions ? 0.9 : 0 }}>
                     {DocumentViewInternal.AnimationEffect(
                         <div className="webBox-textHighlight">
                             <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
@@ -1667,11 +1422,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                 </div>
             </div>
         );
-    }
-
-    @computed get infoUI() {
-        return this.ComponentView?.infoUI?.();
-    }
+    };
 
     render() {
         TraceMobx();
@@ -1685,14 +1436,14 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                         className="contentFittingDocumentView-previewDoc"
                         ref={this.ContentRef}
                         style={{
-                            transition: 'inherit', // this._props.dataTransition,
                             transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
                             width: xshift ?? `${this._props.PanelWidth() - this.Xshift * 2}px`,
                             height: this._props.forceAutoHeight ? undefined : yshift ?? (this.layout_fitWidth ? `${this.panelHeight}px` : `${(this.effectiveNativeHeight / this.effectiveNativeWidth) * this._props.PanelWidth()}px`),
                         }}>
                         <DocumentViewInternal
                             {...this._props}
-                            docViewPublic={this.selfView}
+                            fieldKey={this.LayoutFieldKey}
+                            DocumentView={this.selfView}
                             docViewPath={this.docViewPath}
                             PanelWidth={this.PanelWidth}
                             PanelHeight={this.PanelHeight}
@@ -1706,15 +1457,44 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                             focus={this._props.focus || emptyFunction}
                             ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))}
                         />
-                        {this.htmlOverlay}
-                        {this.infoUI}
+                        {this.htmlOverlay()}
+                        {this.ComponentView?.infoUI?.()}
                     </div>
                 )}
-
-                {this.linkCountView}
+                {/* display link count button */}
+                <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />
             </div>
         );
     }
+
+    public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, timer?: NodeJS.Timeout | undefined, afterTrans?: () => void, dataTrans = false) {
+        docs.forEach(doc => {
+            doc._viewTransition = `${transProp} ${timeInMs}ms`;
+            dataTrans && (doc.dataTransition = `${transProp} ${timeInMs}ms`);
+        });
+        timer && clearTimeout(timer);
+        return setTimeout(
+            () =>
+                docs.forEach(doc => {
+                    doc._viewTransition = undefined;
+                    dataTrans && (doc.dataTransition = 'inherit');
+                    afterTrans?.();
+                }),
+            timeInMs + 10
+        );
+    }
+
+    // shows a stacking view collection (by default, but the user can change) of all documents linked to the source
+    public static showBackLinks(linkAnchor: Doc) {
+        const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
+        // prettier-ignore
+        DocServer.GetRefField(docId).then(docx =>
+            LightboxView.Instance.SetLightboxDoc(
+                (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection
+                Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, onViewMounted: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId)
+            )
+        );
+    }
 }
 
 ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index cad76ddf7..2e03a766a 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -20,13 +20,13 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static SelectOnLoad: string = '';
     _ref: React.RefObject<EquationEditor> = React.createRef();
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) {
             this._props.select(false);
 
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index b456807d7..5bb295565 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,26 +1,108 @@
-import { computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DateField } from '../../../fields/DateField';
-import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc';
+import { Doc, Field, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { ScriptField } from '../../../fields/ScriptField';
 import { WebField } from '../../../fields/URLField';
-import { DocumentView, DocumentViewInternalSharedProps, DocumentViewSharedProps } from './DocumentView';
+import { dropActionType } from '../../util/DragManager';
+import { Transform } from '../../util/Transform';
+import { ViewBoxInterface } from '../DocComponent';
+import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
+import { DocumentView, OpenWhere } from './DocumentView';
+import { PinProps } from './trails';
 
+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
+    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
+    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>;
+export type StyleProviderFuncType = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => any;
 //
 // these properties get assigned through the render() method of the DocumentView when it creates this node.
 // However, that only happens because the properties are "defined" in the markup for the field view.
 // See the LayoutString method on each field view :   ImageBox, FormattedTextBox, etc.
 //
-export interface FieldViewProps extends DocumentViewSharedProps, DocumentViewInternalSharedProps {
-    // FieldView specific props that are not part of DocumentView props
-    fieldKey: string;
-
-    setHeight?: (height: number) => void;
-    onBrowseClick?: () => ScriptField | undefined;
+export interface FieldViewSharedProps {
+    Document: Doc;
+    LayoutTemplateString?: string;
+    LayoutTemplate?: () => Opt<Doc>;
+    TemplateDataDocument?: Doc;
+    renderDepth: number;
+    scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
+    treeViewDoc?: Doc;
+    xPadding?: number;
+    yPadding?: number;
+    dontRegisterView?: boolean;
+    dropAction?: dropActionType;
+    dragAction?: dropActionType;
+    forceAutoHeight?: boolean;
+    ignoreAutoHeight?: boolean;
+    disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
+    CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
+    containerViewPath?: () => DocumentView[];
+    fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
+    isGroupActive?: () => string | undefined; // is this document part of a group that is active
+    setContentViewBox?: (view: ViewBoxInterface) => any; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox
+    PanelWidth: () => number;
+    PanelHeight: () => number;
+    isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
+    isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
+    childFilters: () => string[];
+    childFiltersByRanges: () => string[];
+    styleProvider: Opt<StyleProviderFuncType>;
+    setTitleFocus?: () => void;
+    focus: FocusFuncType;
+    onClickScript?: () => ScriptField;
+    onDoubleClickScript?: () => ScriptField;
+    onPointerDownScript?: () => ScriptField;
+    onPointerUpScript?: () => ScriptField;
+    onBrowseClickScript?: () => ScriptField | undefined;
     onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
+    layout_fitWidth?: (doc: Doc) => boolean | undefined;
+    searchFilterDocs: () => Doc[];
+    layout_showTitle?: () => string;
+    whenChildContentsActiveChanged: (isActive: boolean) => void;
+    rootSelected?: () => boolean; // whether the root of a template has been selected
+    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
+    filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
+    addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
+    removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
+    moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean;
+    pinToPres: (document: Doc, pinProps: PinProps) => void;
+    ScreenToLocalTransform: () => Transform;
+    bringToFront: (doc: Doc, sendToBack?: boolean) => void;
+    waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
+    defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
     pointerEvents?: () => Opt<string>;
+}
+
+/**
+ * FieldView specific props that are not shared with DocumentView props
+ *  */
+export interface FieldViewProps extends FieldViewSharedProps {
+    DocumentView?: () => DocumentView;
+    fieldKey: string;
+    isSelected: () => boolean;
+    select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
+    docViewPath: () => DocumentView[];
+    setHeight?: (height: number) => void;
+    NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
 
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
     // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields
@@ -30,9 +112,9 @@ export interface FieldViewProps extends DocumentViewSharedProps, DocumentViewInt
     color?: string;
     height?: number;
     width?: number;
+    dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field)
     noSidebar?: boolean;
     dontScale?: boolean;
-    dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field)
 }
 
 @observer
@@ -41,13 +123,8 @@ export class FieldView extends React.Component<FieldViewProps> {
         return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={'data'} />"
     }
 
-    @computed
-    get field(): FieldResult {
-        const { Document, fieldKey: fieldKey } = this.props;
-        return Document[fieldKey];
-    }
     render() {
-        const field = this.field;
+        const field = this.props.Document[this.props.fieldKey];
         // prettier-ignore
         if (field instanceof Doc)  return <p> <b>{field.title?.toString()}</b></p>;
         if (field === undefined) return <p>{'<null>'}</p>;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 5a8665aaf..cf07d98be 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -46,7 +46,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         return FieldView.LayoutString(FontIconBox, fieldKey);
     }
 
-    constructor(props: any) {
+    constructor(props: ButtonProps) {
         super(props);
         makeObservable(this);
     }
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index c26579e66..2e7a2120e 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -31,7 +31,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
     }
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         reaction(
             () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange],
             () => this.createGraph()
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 4483d12ce..2a10bd766 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -21,17 +21,17 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../../views/ContextMenu';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { OpenWhere } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import './ImageBox.scss';
 import { PinProps, PresBox } from './trails';
 
 @observer
-export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ImageBox, fieldKey);
     }
@@ -55,10 +55,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _marqueeref = React.createRef<MarqueeAnnotator>();
     @observable _curSuffix = '';
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     protected createDropTarget = (ele: HTMLDivElement) => {
@@ -186,9 +186,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     crop = (region: Doc | undefined, addCrop?: boolean) => {
         if (!region) return;
         const cropping = Doc.MakeCopy(region, true);
-        Doc.GetProto(region).lockedPosition = true;
-        Doc.GetProto(region).title = 'region:' + this.Document.title;
-        Doc.GetProto(region).followLinkToggle = true;
+        const regionData = region[DocData];
+        regionData.lockedPosition = true;
+        regionData.title = 'region:' + this.Document.title;
+        regionData.followLinkToggle = true;
         this.addDocument(region);
         const anchx = NumCast(cropping.x);
         const anchy = NumCast(cropping.y);
@@ -201,7 +202,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
         cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
         cropping.onClick = undefined;
-        const croppingProto = Doc.GetProto(cropping);
+        const croppingProto = cropping[DocData];
         croppingProto.annotationOn = undefined;
         croppingProto.isDataDoc = true;
         croppingProto.backgroundColor = undefined;
@@ -389,7 +390,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this._marqueeref.current?.onTerminateSelection();
         this._props.select(false);
     };
-    focus = (anchor: Doc, options: DocFocusOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
+    focus = (anchor: Doc, options: FocusViewOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
 
     _ffref = React.createRef<CollectionFreeFormView>();
     savedAnnotations = () => this._savedAnnotations;
@@ -421,7 +422,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 <CollectionFreeFormView
                     ref={this._ffref}
                     {...this._props}
-                    setContentView={emptyFunction}
+                    setContentViewBox={emptyFunction}
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
                     renderDepth={this._props.renderDepth + 1}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 73fdc3a23..39a45693e 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -44,7 +44,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
     private _valInput = React.createRef<HTMLInputElement>();
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
     isKeyValueBox = returnTrue;
     able = returnAlways;
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index b2aab2422..7c532f33f 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -12,8 +12,7 @@ import { ContextMenu } from '../ContextMenu';
 import { EditableView } from '../EditableView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere } from './DocumentView';
-import { FieldViewProps } from './FieldView';
+import { OpenWhere, returnEmptyDocViewList } from './DocumentView';
 import { KeyValueBox } from './KeyValueBox';
 import './KeyValueBox.scss';
 import './KeyValuePair.scss';
@@ -110,6 +109,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
                                 childFiltersByRanges: returnEmptyFilter,
                                 searchFilterDocs: returnEmptyDoclist,
                                 styleProvider: DefaultStyleProvider,
+                                docViewPath: returnEmptyDocViewList,
                                 fieldKey: this._props.keyName,
                                 isSelected: returnFalse,
                                 setHeight: returnFalse,
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index cc7c15a10..10eeff08d 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -14,13 +14,15 @@ import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import BigText from './LabelBigText';
 import './LabelBox.scss';
+import { PinProps, PresBox } from './trails';
+import { Docs } from '../../documents/Documents';
 
-export interface LabelBoxProps {
+export interface LabelBoxProps extends FieldViewProps {
     label?: string;
 }
 
 @observer
-export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProps>() {
+export class LabelBox extends ViewBoxBaseComponent<LabelBoxProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LabelBox, fieldKey);
     }
@@ -30,13 +32,13 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
     private dropDisposer?: DragManager.DragDropDisposer;
     private _timeout: any;
 
-    constructor(props: any) {
+    constructor(props: LabelBoxProps) {
         super(props);
         makeObservable(this);
     }
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
     componentWillUnMount() {
         this._timeout && clearTimeout(this._timeout);
@@ -89,6 +91,19 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
         return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
     }
 
+    getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
+        if (!pinProps) return this.Document;
+        const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.Document.title), annotationOn: this.Document });
+
+        if (anchor) {
+            if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
+            //  addAsAnnotation && this.addDocument(anchor);
+            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}) } }, this.Document);
+            return anchor;
+        }
+        return anchor;
+    };
+
     fitTextToBox = (
         r: any
     ):
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 362a7def1..00e1f04c5 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,4 +1,5 @@
 import { action, computed, makeObservable } from 'mobx';
+import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
@@ -7,13 +8,14 @@ import { TraceMobx } from '../../../fields/util';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
 import { SelectionManager } from '../../util/SelectionManager';
-import { ViewBoxBaseComponent, ViewBoxBaseProps } from '../DocComponent';
+import { ViewBoxBaseComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LinkAnchorBox.scss';
 import { LinkInfo } from './LinkDocPreview';
 const { default: { MEDIUM_GRAY }, } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
-export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps & ViewBoxBaseProps>() {
+@observer
+export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LinkAnchorBox, fieldKey);
     }
@@ -23,13 +25,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps & ViewBox
     _isOpen = false;
     _timeout: NodeJS.Timeout | undefined;
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
 
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     @computed get linkSource() {
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index ba34255dc..8b6293806 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -20,7 +20,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return FieldView.LayoutString(LinkBox, fieldKey);
     }
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
@@ -59,7 +59,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
     disposer: IReactionDisposer | undefined;
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         this.disposer = reaction(
             () => {
                 if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.CollectionFreeFormView) {
@@ -100,6 +100,9 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         this.layoutDoc._width = params.rx - params?.lx;
                         this.layoutDoc._height = params?.by - params?.ty;
                     }
+                } else {
+                    this.layoutDoc._width = Math.max(50, NumCast(this.layoutDoc._width));
+                    this.layoutDoc._height = Math.max(50, NumCast(this.layoutDoc._height));
                 }
             },
             { fireImmediately: true }
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 8ad0b7dde..13f0ac4fc 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,20 +1,27 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { LinkManager } from '../../util/LinkManager';
 import './LinkDescriptionPopup.scss';
 import { TaskCompletionBox } from './TaskCompletedBox';
 
 @observer
 export class LinkDescriptionPopup extends React.Component<{}> {
-    @observable public static descriptionPopup: boolean = false;
-    @observable public static showDescriptions: string = 'ON';
-    @observable public static popupX: number = 700;
-    @observable public static popupY: number = 350;
+    public static Instance: LinkDescriptionPopup;
+    @observable public display: boolean = false;
+    @observable public showDescriptions: string = 'ON';
+    @observable public popupX: number = 700;
+    @observable public popupY: number = 350;
     @observable description: string = '';
     @observable popupRef = React.createRef<HTMLDivElement>();
 
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+        LinkDescriptionPopup.Instance = this;
+    }
+
     @action
     descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.description = e.currentTarget.value;
@@ -22,16 +29,16 @@ export class LinkDescriptionPopup extends React.Component<{}> {
 
     @action
     onDismiss = (add: boolean) => {
-        LinkDescriptionPopup.descriptionPopup = false;
+        this.display = false;
         if (add) {
-            LinkManager.currentLink && (Doc.GetProto(LinkManager.currentLink).link_description = this.description);
+            LinkManager.currentLink && (LinkManager.currentLink[DocData].link_description = this.description);
         }
     };
 
     @action
     onClick = (e: PointerEvent) => {
         if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
-            LinkDescriptionPopup.descriptionPopup = false;
+            this.display = false;
             TaskCompletionBox.taskCompleted = false;
         }
     };
@@ -46,13 +53,13 @@ export class LinkDescriptionPopup extends React.Component<{}> {
     }
 
     render() {
-        return (
+        return !this.display ? null : (
             <div
                 className="linkDescriptionPopup"
                 ref={this.popupRef}
                 style={{
-                    left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
-                    top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
+                    left: this.popupX ? this.popupX : 700,
+                    top: this.popupY ? this.popupY : 350,
                 }}>
                 <input
                     className="linkDescriptionPopup-input"
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index ed4878d9d..2d54da0ed 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -18,9 +18,9 @@ import { SearchUtil } from '../../util/SearchUtil';
 import { SettingsManager } from '../../util/SettingsManager';
 import { Transform } from '../../util/Transform';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DocumentView, DocumentViewSharedProps, OpenWhere, StyleProviderFunc } from './DocumentView';
+import { DocumentView, OpenWhere } from './DocumentView';
+import { StyleProviderFuncType } from './FieldView';
 import './LinkDocPreview.scss';
-import { FieldViewProps } from './FieldView';
 
 export class LinkInfo {
     private static _instance: Opt<LinkInfo>;
@@ -45,7 +45,7 @@ interface LinkDocPreviewProps {
     linkDoc?: Doc;
     linkSrc?: Doc;
     DocumentView?: () => DocumentView;
-    styleProvider?: StyleProviderFunc;
+    styleProvider?: StyleProviderFuncType;
     location: number[];
     hrefs?: string[];
     showHeader?: boolean;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 56fb157da..c185c66fc 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -23,12 +23,12 @@ import { DragManager } from '../../../util/DragManager';
 import { LinkManager } from '../../../util/LinkManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
 import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
@@ -96,7 +96,7 @@ type MapMarker = {
 // });
 
 @observer
-export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(MapBox, fieldKey);
     }
@@ -107,7 +107,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
@@ -248,7 +248,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     componentDidMount() {
         this._unmounting = false;
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     _unmounting = false;
@@ -388,7 +388,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: e => {
                     if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
+                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
                         e.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
@@ -504,7 +504,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
-    getView = async (doc: Doc, options: DocFocusOptions) => {
+    getView = async (doc: Doc, options: FocusViewOptions) => {
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
             this.toggleSidebar();
             options.didMove = true;
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 9734d9db1..9825824bd 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -12,7 +12,7 @@
 // import { SnappingManager } from '../../../util/SnappingManager';
 // import { UndoManager } from '../../../util/UndoManager';
 // import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
-// import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+// import { ViewBoxAnnotatableComponent } from '../../DocComponent';
 // import { Colors } from '../../global/globalEnums';
 // import { AnchorMenu } from '../../pdf/AnchorMenu';
 // import { Annotation } from '../../pdf/Annotation';
@@ -83,7 +83,7 @@
 // } as google.maps.places.AutocompleteOptions;
 
 // @observer
-// export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>>() {
+// export class MapBox2 extends ViewBoxAnnotatableComponent<FieldViewProps & Partial<GoogleMapProps>>() {
 //     private _dropDisposer?: DragManager.DragDropDisposer;
 //     private _disposers: { [name: string]: IReactionDisposer } = {};
 //     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
@@ -137,7 +137,7 @@
 
 //     // iterate allMarkers to size, center, and zoom map to contain all markers
 //     private fitBounds = (map: google.maps.Map) => {
-//         const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds();
+//         const curBounds = map.getBounds ?? new window.google.maps.LatLngBounds();
 //         const isFitting = this.allMapMarkers.reduce((fits, place) => fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean);
 //         !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), new window.google.maps.LatLngBounds()));
 //     };
@@ -257,7 +257,7 @@
 //         map.setZoom(NumCast(this.dataDoc.map_zoom, 2.5));
 //         map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng)));
 //         setTimeout(() => {
-//             if (this._loadPending && this._map.getBounds()) {
+//             if (this._loadPending && this._map.getBounds) {
 //                 this._loadPending = false;
 //                 this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
 //             }
@@ -272,7 +272,7 @@
 
 //     @action
 //     centered = () => {
-//         if (this._loadPending && this._map.getBounds()) {
+//         if (this._loadPending && this._map.getBounds) {
 //             this._loadPending = false;
 //             this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
 //         }
@@ -282,7 +282,7 @@
 
 //     @action
 //     zoomChanged = () => {
-//         if (this._loadPending && this._map.getBounds()) {
+//         if (this._loadPending && this._map.getBounds) {
 //             this._loadPending = false;
 //             this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
 //         }
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index a9c6ba22c..6ccbbbe1c 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -9,19 +9,18 @@
 // import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 // import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView';
 // import { CollectionStackingView } from '../../collections/CollectionStackingView';
-// import { ViewBoxAnnotatableProps } from '../../DocComponent';
 // import { FieldViewProps } from '../FieldView';
 // import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 // import './MapBox.scss';
 
-// interface MapBoxInfoWindowProps {
+// interface MapBoxInfoWindowProps extends FieldViewProps {
 //     place: Doc;
 //     renderDepth: number;
 //     markerMap: { [id: string]: google.maps.Marker };
 //     isAnyChildContentActive: () => boolean;
 // }
 // @observer
-// export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & ViewBoxAnnotatableProps & FieldViewProps> {
+// export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps> {
 //     @action
 //     private handleInfoWindowClose = () => {
 //         if (this.props.place.infoWindowOpen) {
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index e079f7457..8a5bd7ce6 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Button, EditableText, IconButton, Type } from 'browndash-components';
-import { IReactionDisposer, ObservableMap, action, computed, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { MapProvider, Map as MapboxMap } from 'react-map-gl';
@@ -16,18 +16,17 @@ import { LinkManager } from '../../../util/LinkManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
 import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
 import './MapBox.scss';
 
-// amongus
 /**
  * MapBox architecture:
  * Main component: MapBox.tsx
@@ -61,7 +60,7 @@ const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing
 // });
 
 @observer
-export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(MapBoxContainer, fieldKey);
     }
@@ -71,6 +70,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
 
+    constructor(props: FieldViewProps) {
+        super(props);
+        makeObservable(this);
+    }
+
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get allSidebarDocs() {
         return DocListCast(this.dataDoc[this.SidebarKey]);
@@ -97,7 +101,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
 
     componentDidMount() {
         this._unmounting = false;
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     _unmounting = false;
@@ -237,7 +241,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: e => {
                     if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
+                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
                         e.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
@@ -374,7 +378,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         }
     };
 
-    getView = async (doc: Doc, options: DocFocusOptions) => {
+    getView = async (doc: Doc, options: FocusViewOptions) => {
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
             this.toggleSidebar();
             options.didMove = true;
@@ -787,8 +791,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                                           NativeWidth={returnOne}
                                           NativeHeight={returnOne}
                                           onKey={undefined}
-                                          onDoubleClick={undefined}
-                                          onBrowseClick={undefined}
+                                          onDoubleClickScript={undefined}
+                                          onBrowseClickScript={undefined}
                                           childFilters={returnEmptyFilter}
                                           childFiltersByRanges={returnEmptyFilter}
                                           searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 7696a45a0..0eb3ffc3d 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -5,6 +5,7 @@ import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { ComputedField } from '../../../fields/ScriptField';
@@ -22,19 +23,19 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { CollectionStackingView } from '../collections/CollectionStackingView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { Colors } from '../global/globalEnums';
 import { CreateImage } from '../nodes/WebBoxRenderer';
 import { PDFViewer } from '../pdf/PDFViewer';
 import { SidebarAnnos } from '../SidebarAnnos';
-import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { DocumentView, OpenWhere } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import { ImageBox } from './ImageBox';
 import './PDFBox.scss';
 import { PinProps, PresBox } from './trails';
 
 @observer
-export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(PDFBox, fieldKey);
     }
@@ -58,7 +59,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url;
     }
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927;
@@ -97,9 +98,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     crop = (region: Doc | undefined, addCrop?: boolean) => {
         if (!region) return;
         const cropping = Doc.MakeCopy(region, true);
-        Doc.GetProto(region).lockedPosition = true;
-        Doc.GetProto(region).title = 'region:' + this.Document.title;
-        Doc.GetProto(region).followLinkToggle = true;
+        const regionData = region[DocData];
+        regionData.lockedPosition = true;
+        regionData.title = 'region:' + this.Document.title;
+        regionData.followLinkToggle = true;
         this.addDocument(region);
 
         const docViewContent = this.DocumentView?.().ContentDiv!;
@@ -120,7 +122,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         cropping._width = anchw;
         cropping._height = anchh;
         cropping.onClick = undefined;
-        const croppingProto = Doc.GetProto(cropping);
+        const croppingProto = cropping[DocData];
         croppingProto.annotationOn = undefined;
         croppingProto.isDataDoc = true;
         croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
@@ -191,7 +193,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         this._disposers.select = reaction(
             () => this._props.isSelected(),
             () => {
@@ -219,12 +221,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
         return this._props.addDocTab(doc, where);
     };
-    focus = (anchor: Doc, options: DocFocusOptions) => {
+    focus = (anchor: Doc, options: FocusViewOptions) => {
         this._initialScrollTarget = anchor;
         return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.config_scrollTop)), options);
     };
 
-    getView = async (doc: Doc, options: DocFocusOptions) => {
+    getView = async (doc: Doc, options: FocusViewOptions) => {
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
             options.didMove = true;
             this.toggleSidebar(false);
@@ -528,7 +530,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
                     <ComponentTag
                         {...this._props}
-                        setContentView={emptyFunction} // override setContentView to do nothing
+                        setContentViewBox={emptyFunction} // override setContentView to do nothing
                         NativeWidth={this.sidebarNativeWidthFunc}
                         NativeHeight={this.sidebarNativeHeightFunc}
                         PanelHeight={this._props.PanelHeight}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index c04a81f7d..f6d94ce05 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -1,4 +1,4 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DateField } from '../../../../fields/DateField';
@@ -21,6 +21,7 @@ import { media_state } from '../AudioBox';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { VideoBox } from '../VideoBox';
 import { RecordingView } from './RecordingView';
+import { DocData } from '../../../../fields/DocSymbols';
 
 @observer
 export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -30,8 +31,13 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
 
+    constructor(props: FieldViewProps) {
+        super(props);
+        makeObservable(this);
+    }
+
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         Doc.SetNativeWidth(this.dataDoc, 1280);
         Doc.SetNativeHeight(this.dataDoc, 720);
     }
@@ -100,7 +106,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
                   });
         screengrabber.overlayX = 70; //was -400
         screengrabber.overlayY = 590; //was 0
-        Doc.GetProto(screengrabber)[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
+        screengrabber[DocData][Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
         Doc.AddToMyOverlay(screengrabber); //just adds doc to overlay
         DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
             RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index f74e6fb2b..1e3933ac3 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -21,12 +21,13 @@ import { TrackMovements } from '../../util/TrackMovements';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { media_state } from './AudioBox';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import './ScreenshotBox.scss';
 import { VideoBox } from './VideoBox';
+import { DocData } from '../../../fields/DocSymbols';
 
 declare class MediaRecorder {
     constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -109,7 +110,7 @@ declare class MediaRecorder {
 // }
 
 @observer
-export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ScreenshotBox, fieldKey);
     }
@@ -121,7 +122,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         return Cast(this.dataDoc[this._props.fieldKey + '_recordingStart'], DateField)?.date.getTime();
     }
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         this.setupDictation();
@@ -144,7 +145,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
 
     componentDidMount() {
         this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = 0;
-        this._props.setContentView?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+        this._props.setContentViewBox?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
         // this.layoutDoc.videoWall && reaction(() => ({ width: this._props.PanelWidth(), height: this._props.PanelHeight() }),
         //     ({ width, height }) => {
         //         if (this._camera) {
@@ -281,7 +282,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         const dictationText = DocUtils.GetNewTextDoc('dictation', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
         const textField = Doc.LayoutFieldKey(dictationText);
         dictationText._layout_autoHeight = false;
-        const dictationTextProto = Doc.GetProto(dictationText);
+        const dictationTextProto = dictationText[DocData];
         dictationTextProto[`${textField}_recordingSource`] = this.dataDoc;
         dictationTextProto[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
         dictationTextProto.mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
@@ -297,7 +298,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                     <div style={{ position: 'relative', height: this.videoPanelHeight() }}>
                         <CollectionFreeFormView
                             {...this._props}
-                            setContentView={emptyFunction}
+                            setContentViewBox={emptyFunction}
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
                             PanelHeight={this.videoPanelHeight}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 73ad3a004..89650889d 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -14,7 +14,7 @@ import { ScriptManager } from '../../util/ScriptManager';
 import { CompileScript, ScriptParam } from '../../util/Scripting';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { ContextMenu } from '../ContextMenu';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { OverlayView } from '../OverlayView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
@@ -23,7 +23,7 @@ import './ScriptingBox.scss';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
-export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     private dropDisposer?: DragManager.DragDropDisposer;
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(ScriptingBox, fieldStr);
@@ -56,7 +56,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
     @observable private _scriptSuggestedParams: any = '';
     @observable private _scriptParamsText: any = '';
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         if (!this.compileParams.length) {
@@ -116,7 +116,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
 
     @action
     componentDidMount() {
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         this.rawText = this.rawScript;
         const observer = new _global.ResizeObserver(
             action((entries: any) => {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 1913ab032..a6c524881 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -4,10 +4,11 @@ import { observer } from 'mobx-react';
 import { basename } from 'path';
 import * as React from 'react';
 import { Doc, Opt, StrListCast } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
 import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -21,12 +22,12 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, DocumentView } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { DocumentView } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import { RecordingBox } from './RecordingBox';
 import { PinProps, PresBox } from './trails';
 import './VideoBox.scss';
@@ -44,7 +45,7 @@ import './VideoBox.scss';
  */
 
 @observer
-export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(VideoBox, fieldKey);
     }
@@ -61,10 +62,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _playRegionTimer: any = null; // timeout for playback
     private _controlsFadeTimer: any = null; // timeout for controls fade
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
     }
 
     @observable _stackedTimeline: CollectionStackedTimeline | undefined = undefined; // CollectionStackedTimeline ref
@@ -115,7 +116,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     componentDidMount() {
         this.unmounting = false;
-        this._props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
+        this._props.setContentViewBox?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
         this.player && this.setPlayheadTime(this.timeline?.clipStart || 0);
         document.addEventListener('keydown', this.keyEvents, true);
 
@@ -329,11 +330,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             _height: (height / width) * 150,
             title: '--snapshot' + NumCast(this.layoutDoc._layout_currentTimecode) + ' image-',
         });
-        Doc.SetNativeWidth(Doc.GetProto(imageSnapshot), Doc.NativeWidth(this.layoutDoc));
-        Doc.SetNativeHeight(Doc.GetProto(imageSnapshot), Doc.NativeHeight(this.layoutDoc));
+        Doc.SetNativeWidth(imageSnapshot[DocData], Doc.NativeWidth(this.layoutDoc));
+        Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
         this._props.addDocument?.(imageSnapshot);
         const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
-        link && (Doc.GetProto(link.link_anchor_2 as Doc).timecodeToHide = NumCast((link.link_anchor_2 as Doc).timecodeToShow) + 3);
+        link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);
         setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true));
     };
 
@@ -368,7 +369,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         !this.unmounting && this.player && (this.layoutDoc._layout_currentTimecode = this.player.currentTime);
     };
 
-    getView = (doc: Doc, options: DocFocusOptions) => {
+    getView = (doc: Doc, options: FocusViewOptions) => {
         if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
             if (this.heightPercent === 100) {
                 this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
@@ -738,7 +739,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     zoom = (zoom: number) => this.timeline?.setZoom(zoom);
 
     // plays link
-    playLink = (doc: Doc, options: DocFocusOptions) => {
+    playLink = (doc: Doc, options: FocusViewOptions) => {
         const startTime = Math.max(0, NumCast(doc.config_clipStart, this._stackedTimeline?.anchorStart(doc) || 0));
         const endTime = this.timeline?.anchorEnd(doc);
         if (startTime !== undefined) {
@@ -885,10 +886,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     crop = (region: Doc | undefined, addCrop?: boolean) => {
         if (!region) return;
         const cropping = Doc.MakeCopy(region, true);
-        Doc.GetProto(region).backgroundColor = 'transparent';
-        Doc.GetProto(region).lockedPosition = true;
-        Doc.GetProto(region).title = 'region:' + this.Document.title;
-        Doc.GetProto(region).followLinkToggle = true;
+        const regionData = region[DocData];
+        regionData.backgroundColor = 'transparent';
+        regionData.lockedPosition = true;
+        regionData.title = 'region:' + this.Document.title;
+        regionData.followLinkToggle = true;
         region._timecodeToHide = NumCast(region._timecodeToShow) + 0.0001;
         this.addDocument(region);
         const anchx = NumCast(cropping.x);
@@ -904,7 +906,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         cropping.timecodeToHide = undefined;
         cropping.timecodeToShow = undefined;
         cropping.onClick = undefined;
-        const croppingProto = Doc.GetProto(cropping);
+        const croppingProto = cropping[DocData];
         croppingProto.annotationOn = undefined;
         croppingProto.isDataDoc = true;
         croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
@@ -955,7 +957,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         }}>
                         <CollectionFreeFormView
                             {...this._props}
-                            setContentView={emptyFunction}
+                            setContentViewBox={emptyFunction}
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
                             renderDepth={this._props.renderDepth + 1}
@@ -1004,7 +1006,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
 
     @computed get UIButtons() {
-        const bounds = this.DocumentView?.().getBounds();
+        const bounds = this.DocumentView?.().getBounds;
         const width = (bounds?.right || 0) - (bounds?.left || 0);
         const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
         return (
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index b62d32503..5776cf6a6 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -24,7 +24,7 @@ import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { Colors } from '../global/globalEnums';
 import { LightboxView } from '../LightboxView';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
@@ -33,15 +33,15 @@ import { Annotation } from '../pdf/Annotation';
 import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { StyleProp } from '../StyleProvider';
-import { DocComponentView, DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { DocumentView, OpenWhere } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import { LinkInfo } from './LinkDocPreview';
 import { PinProps, PresBox } from './trails';
 import './WebBox.scss';
 const { CreateImage } = require('./WebBoxRenderer');
 const _global = (window /* browser */ || global) /* node */ as any;
 @observer
-export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(WebBox, fieldKey);
     }
@@ -98,7 +98,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return Cast(this.Document[this._props.fieldKey], WebField)?.url;
     }
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         this._webUrl = this._url; // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
@@ -172,7 +172,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     componentDidMount() {
-        this._props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document.  this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
+        this._props.setContentViewBox?.(this); // this tells the DocumentView that this WebBox is the "content" of the document.  this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
 
         runInAction(() => {
             this._annotationKeySuffix = () => (this._urlHash ? this._urlHash + '_' : '') + 'annotations';
@@ -280,7 +280,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this._savedAnnotations;
     };
 
-    focus = (anchor: Doc, options: DocFocusOptions) => {
+    focus = (anchor: Doc, options: FocusViewOptions) => {
         if (anchor !== this.Document && this._outerRef.current) {
             const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
             const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight));
@@ -297,7 +297,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     @action
-    getView = (doc: Doc, options: DocFocusOptions) => {
+    getView = (doc: Doc, options: FocusViewOptions) => {
         if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this.DocumentView?.()));
         if (this.Document.layout_fieldKey === 'layout_icon') this.DocumentView?.().iconify();
         const webUrl = WebCast(doc.config_data)?.url;
@@ -924,7 +924,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
     _innerCollectionView: CollectionFreeFormView | undefined;
     zoomScaling = () => this._innerCollectionView?.zoomScaling() ?? 1;
-    setInnerContent = (component: DocComponentView) => (this._innerCollectionView = component as CollectionFreeFormView);
+    setInnerContent = (component: ViewBoxInterface) => (this._innerCollectionView = component as CollectionFreeFormView);
 
     @computed get content() {
         const interactive = this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
@@ -966,7 +966,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     renderAnnotations = (childFilters: () => string[]) => (
         <CollectionFreeFormView
             {...this._props}
-            setContentView={this.setInnerContent}
+            setContentViewBox={this.setInnerContent}
             NativeWidth={returnZero}
             NativeHeight={returnZero}
             originTopLeft={false}
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 4fb1a7f17..748c3322e 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -18,6 +18,11 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return FieldView.LayoutString(CalendarBox, fieldKey);
     }
 
+    constructor(props: FieldViewProps) {
+        super(props);
+        makeObservable(this);
+    }
+
     componentDidMount(): void {}
 
     componentWillUnmount(): void {}
@@ -105,11 +110,6 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
         });
     }
 
-    constructor(props: any) {
-        super(props);
-        makeObservable(this);
-    }
-
     render() {
         return (
             <div className="calendar-box-conatiner">
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 4ae12505e..bc49ed23d 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -6,12 +6,13 @@ import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { NumCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnFalse, Utils } from '../../../../Utils';
+import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { Transform } from '../../../util/Transform';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { DocFocusOptions, DocumentView, returnEmptyDocViewList } from '../DocumentView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions } from '../FieldView';
 import { FormattedTextBox } from './FormattedTextBox';
 
 var horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
@@ -159,7 +160,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
         const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
         return new Transform(-translateX, -translateY, 1).scale(1 / scale);
     };
-    outerFocus = (target: Doc, options: DocFocusOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
+    outerFocus = (target: Doc, options: FocusViewOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
 
     onKeyDown = (e: any) => {
         e.stopPropagation();
@@ -178,9 +179,6 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
         ele && (ele.style.backgroundColor = 'orange');
     };
 
-    @computed get childContainerViewPath() {
-        return this._textBox.DocumentView?.().docViewPath ?? returnEmptyDocViewList;
-    }
     componentWillUnmount = () => Object.values(this._disposers).forEach(disposer => disposer?.());
     isContentActive = () => this._props.tbox._props.isContentActive() || this._props.tbox.isAnyChildContentActive?.();
 
@@ -210,7 +208,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
                     isDocumentActive={returnFalse}
                     isContentActive={this.isContentActive}
                     styleProvider={this._textBox._props.styleProvider}
-                    containerViewPath={this.childContainerViewPath}
+                    containerViewPath={this._textBox.DocumentView?.().docViewPath}
                     ScreenToLocalTransform={this.getDocTransform}
                     addDocTab={this._textBox._props.addDocTab}
                     pinToPres={returnFalse}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 65898d568..38400d2e7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -14,7 +14,7 @@ import * as React from 'react';
 import { BsMarkdownFill } from 'react-icons/bs';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -42,7 +42,7 @@ import { CollectionStackingView } from '../../collections/CollectionStackingView
 import { CollectionTreeView } from '../../collections/CollectionTreeView';
 import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
@@ -50,8 +50,8 @@ import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { StyleProp } from '../../StyleProvider';
 import { media_state } from '../AudioBox';
-import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
 import { LinkInfo } from '../LinkDocPreview';
 import { PinProps, PresBox } from '../trails';
 import { DashDocCommentView } from './DashDocCommentView';
@@ -68,10 +68,8 @@ import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 // import * as applyDevTools from 'prosemirror-dev-tools';
-
-export interface FormattedTextBoxProps {}
 @observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & FormattedTextBoxProps>() {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
@@ -202,7 +200,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
         return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
     }
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         FormattedTextBox.Instance = this;
@@ -271,14 +269,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
                 if (target) {
                     anchor.followLinkAudio = true;
                     let stopFunc: any;
-                    Doc.GetProto(target).mediaState = media_state.Recording;
-                    Doc.GetProto(target).audioAnnoState = 'recording';
-                    DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
+                    const targetData = target[DocData];
+                    targetData.mediaState = media_state.Recording;
+                    targetData.audioAnnoState = 'recording';
+                    DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
                     let reactionDisposer = reaction(
                         () => target.mediaState,
                         action(dictation => {
                             if (!dictation) {
-                                Doc.GetProto(target).audioAnnoState = 'stopped';
+                                targetData.audioAnnoState = 'stopped';
                                 stopFunc();
                                 reactionDisposer();
                             }
@@ -433,7 +432,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
 
     autoLink = () => {
         const newAutoLinks = new Set<Doc>();
-        const oldAutoLinks = LinkManager.Links(this._props.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
+        const oldAutoLinks = LinkManager.Links(this.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
         if (this._editorView?.state.doc.textContent) {
             const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
             const f = this._editorView.state.selection.from;
@@ -451,7 +450,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
     updateTitle = () => {
         const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
         if (
-            !this._props.dontRegisterView && // (this._props.Document.isTemplateForField === "text" || !this._props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+            !this._props.dontRegisterView && // (this.Document.isTemplateForField === "text" || !this.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             (title.startsWith('-') || title.startsWith('@')) &&
             this._editorView &&
             !this.dataDoc.title_custom &&
@@ -494,7 +493,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
                                     DocUtils.MakeLink(this.Document, target, { link_relationship: LinkManager.AutoKeywords })!);
                             newAutoLinks.add(alink);
                             // DocCast(alink.link_anchor_1).followLinkLocation = 'add:right';
-                            const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this._props.Document[Id] }];
+                            const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.Document[Id] }];
                             allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
                             const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term' });
                             tr = tr.addMark(pos, pos + node.nodeSize, link);
@@ -578,7 +577,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
                 // replace text contents when dragging with Alt
                 if (de.altKey) {
                     const fieldKey = Doc.LayoutFieldKey(draggedDoc);
-                    if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this._props.Document)) {
+                    if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this.Document)) {
                         Doc.GetProto(this.dataDoc)[this.fieldKey] = Field.Copy(draggedDoc[fieldKey]);
                     }
 
@@ -991,7 +990,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
                 };
                 const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
                 if (link) {
-                    Doc.GetProto(link).isDictation = true;
+                    link[DocData].isDictation = true;
                     const audioanchor = Cast(link.link_anchor_2, Doc, null);
                     const textanchor = Cast(link.link_anchor_1, Doc, null);
                     if (audioanchor) {
@@ -1001,7 +1000,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
                             audioId: audioanchor[Id],
                             textId: textanchor[Id],
                         });
-                        Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode;
+                        textanchor[DocData].title = 'dictation:' + audiotag.attrs.timeCode;
                         const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag);
                         const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
                         this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
@@ -1056,7 +1055,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
         return anchorDoc ?? this.Document;
     }
 
-    getView = async (doc: Doc, options: DocFocusOptions) => {
+    getView = async (doc: Doc, options: FocusViewOptions) => {
         if (DocListCast(this.dataDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
             if (!this.SidebarShown) {
                 this.toggleSidebar(false);
@@ -1066,7 +1065,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
         }
         return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
     };
-    focus = (textAnchor: Doc, options: DocFocusOptions) => {
+    focus = (textAnchor: Doc, options: FocusViewOptions) => {
         const focusSpeed = options.zoomTime ?? 500;
         const textAnchorId = textAnchor[Id];
         const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
@@ -1141,7 +1140,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
         return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this._props.NativeDimScaling?.() || 1 : 1;
     }
     componentDidMount() {
-        !this._props.dontSelectOnLoad && this._props.setContentView?.(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._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._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
         this._disposers.layout_autoHeight = reaction(
@@ -1777,7 +1776,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
             }
         }
     };
-    fitContentsToBox = () => BoolCast(this._props.Document._freeform_fitContentsToBox);
+    fitContentsToBox = () => BoolCast(this.Document._freeform_fitContentsToBox);
     sidebarContentScaling = () => (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
     sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
         if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
@@ -2011,7 +2010,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotat
                         cursor: this._props.isContentActive() ? 'text' : undefined,
                         height: this._props.height ? 'max-content' : undefined,
                         overflow: this.layout_autoHeight ? 'hidden' : undefined,
-                        pointerEvents: Doc.ActiveTool === InkTool.None && !this._props.onBrowseClick?.() ? undefined : 'none',
+                        pointerEvents: Doc.ActiveTool === InkTool.None && !this._props.onBrowseClickScript?.() ? undefined : 'none',
                     }}
                     onContextMenu={this.specificContextMenu}
                     onKeyDown={this.onKeyDown}
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 7d0086c0c..6e7c3e612 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -1,4 +1,4 @@
-import { computed } from 'mobx';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnFalse } from '../../../../Utils';
@@ -12,6 +12,10 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ImportElementBox, fieldKey);
     }
+    constructor(props: FieldViewProps) {
+        super(props);
+        makeObservable(this);
+    }
 
     screenToLocalXf = () => this.ScreenToLocalBoxXf().scale(1 * (this._props.NativeDimScaling?.() || 1));
     @computed get mainItem() {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 645ac08e1..9e5ea9524 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, Observ
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
-import { Animation } from '../../../../fields/DocSymbols';
+import { Animation, DocData } from '../../../../fields/DocSymbols';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
 import { InkField } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -31,8 +31,8 @@ import { TreeView } from '../../collections/TreeView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
-import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
@@ -70,7 +70,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
     static navigateToDocScript: ScriptField;
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         if (!PresBox.navigateToDocScript) {
@@ -184,7 +184,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             },
             { fireImmediately: true }
         );
-        this._props.setContentView?.(this);
+        this._props.setContentViewBox?.(this);
         this._unmounting = false;
         this.turnOffEdit(true);
         this._disposers.selection = reaction(
@@ -438,9 +438,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const setData = bestTargetView?.ComponentView?.setData;
             if (setData) setData(activeItem.config_data);
             else {
-                const current = Doc.GetProto(bestTarget)[fkey];
-                Doc.GetProto(bestTarget)[fkey + '_' + Date.now()] = current instanceof ObjectField ? current[Copy]() : current;
-                Doc.GetProto(bestTarget)[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
+                const bestTargetData = bestTarget[DocData];
+                const current = bestTargetData[fkey];
+                bestTargetData[fkey + '_' + Date.now()] = current instanceof ObjectField ? current[Copy]() : current;
+                bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
             }
             bestTarget[fkey + '_usePath'] = activeItem.config_usePath;
             setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
@@ -498,11 +499,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.config_fillColor !== undefined || activeItem.color !== undefined))) {
             if (bestTarget.fillColor !== activeItem.config_fillColor) {
-                Doc.GetProto(bestTarget).fillColor = StrCast(activeItem.config_fillColor, StrCast(bestTarget.fillColor));
+                bestTarget[DocData].fillColor = StrCast(activeItem.config_fillColor, StrCast(bestTarget.fillColor));
                 changed = true;
             }
             if (bestTarget.color !== activeItem.config_color) {
-                Doc.GetProto(bestTarget).color = StrCast(activeItem.config_color, StrCast(bestTarget.color));
+                bestTarget[DocData].color = StrCast(activeItem.config_color, StrCast(bestTarget.color));
                 changed = true;
             }
             if (bestTarget.width !== activeItem.width) {
@@ -559,7 +560,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     return doc;
                 });
             const newList = new List<Doc>([...oldItems, ...hiddenItems, ...newItems]);
-            Doc.GetProto(bestTarget)[fkey + '_annotations'] = newList;
+            bestTarget[DocData][fkey + '_annotations'] = newList;
         }
         if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.config_pinLayoutData !== undefined)) {
             changed = true;
@@ -580,9 +581,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         data.fill && (doc._fillColor = data.fill);
                         doc._width = data.w;
                         doc._height = data.h;
-                        data.data && (Doc.GetProto(doc).data = field);
-                        data.text && (Doc.GetProto(doc).text = tfield);
-                        Doc.AddDocToList(Doc.GetProto(bestTarget), layoutField, doc);
+                        data.data && (doc[DocData].data = field);
+                        data.text && (doc[DocData].text = tfield);
+                        Doc.AddDocToList(bestTarget[DocData], layoutField, doc);
                     }
                 });
             setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10);
@@ -648,7 +649,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             }
             if (pinProps.pinData.dataannos) {
                 const fkey = Doc.LayoutFieldKey(targetDoc);
-                pinDoc.config_annotations = new List<Doc>(DocListCast(Doc.GetProto(targetDoc)[fkey + '_annotations']).filter(doc => !doc.layout_unrendered));
+                pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[DocData][fkey + '_annotations']).filter(doc => !doc.layout_unrendered));
             }
             if (pinProps.pinData.inkable) {
                 pinDoc.config_fillColor = targetDoc.fillColor;
@@ -763,7 +764,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         const effect = activeItem.presentation_effect && activeItem.presentation_effect !== PresEffect.None ? activeItem.presentation_effect : undefined;
         const presTime = NumCast(activeItem.presentation_transition, effect ? 750 : 500);
-        const options: DocFocusOptions = {
+        const options: FocusViewOptions = {
             willPan: activeItem.presentation_movement !== PresMovement.None,
             willZoomCentered: activeItem.presentation_movement === PresMovement.Zoom || activeItem.presentation_movement === PresMovement.Jump || activeItem.presentation_movement === PresMovement.Center,
             zoomScale: activeItem.presentation_movement === PresMovement.Center ? 0 : NumCast(activeItem.config_zoom, 1),
@@ -1115,7 +1116,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         presDocView && SelectionManager.SelectView(presDocView, false);
     };
 
-    focusElement = (doc: Doc, options: DocFocusOptions) => {
+    focusElement = (doc: Doc, options: FocusViewOptions) => {
         this.selectElement(doc);
         return undefined;
     };
@@ -2605,7 +2606,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 moveDocument={returnFalse}
                                 ignoreUnrendered={true}
                                 childDragAction="move"
-                                setContentView={emptyFunction}
+                                setContentViewBox={emptyFunction}
                                 //childLayoutFitWidth={returnTrue}
                                 childOpacity={returnOne}
                                 childClickScript={PresBox.navigateToDocScript}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 4bc79176e..7e68711fa 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -41,7 +41,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     readonly expandViewHeight = 100;
     readonly collapsedHeight = 35;
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 01590749e..ba7286111 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -17,16 +17,15 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
-import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
-import { FieldViewProps } from '../nodes/FieldView';
+import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView';
 import { LinkInfo } from '../nodes/LinkDocPreview';
+import { PDFBox } from '../nodes/PDFBox';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
 import { AnchorMenu } from './AnchorMenu';
 import { Annotation } from './Annotation';
 import { GPTPopup } from './GPTPopup/GPTPopup';
 import './PDFViewer.scss';
-import { PDFBox } from '../nodes/PDFBox';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
@@ -172,7 +171,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
     // scrolls to focus on a nested annotation document.  if this is part a link preview then it will jump to the scroll location,
     // otherwise it will scroll smoothly.
-    scrollFocus = (doc: Doc, scrollTop: number, options: DocFocusOptions) => {
+    scrollFocus = (doc: Doc, scrollTop: number, options: FocusViewOptions) => {
         const mainCont = this._mainCont.current;
         let focusSpeed: Opt<number>;
         if (doc !== this._props.Document && mainCont) {
@@ -521,7 +520,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 {...this._props}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
-                setContentView={emptyFunction} // override setContentView to do nothing
+                setContentViewBox={emptyFunction} // override setContentView to do nothing
                 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}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 4d29573d4..5dc4f5550 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -3,7 +3,7 @@ import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCastAsync, Field } from '../../../fields/Doc';
-import { DirectLinks } from '../../../fields/DocSymbols';
+import { DirectLinks, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -58,7 +58,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     /**
      * This is the constructor for the SearchBox class.
      */
-    constructor(props: any) {
+    constructor(props: SearchBoxProps) {
         super(props);
         makeObservable(this);
         SearchBox.Instance = this;
@@ -207,7 +207,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
         this._results.forEach((_, doc) => {
             this._pageRanks.set(doc, 1.0 / this._results.size);
 
-            if (Doc.GetProto(doc)[DirectLinks].size === 0) {
+            if (doc[DocData][DirectLinks].size === 0) {
                 this._linkedDocsOut.set(doc, new Set(this._results.keys()));
 
                 this._results.forEach((_, linkedDoc) => {
@@ -216,7 +216,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
             } else {
                 const linkedDocSet = new Set<Doc>();
 
-                Doc.GetProto(doc)[DirectLinks].forEach(link => {
+                doc[DocData][DirectLinks].forEach(link => {
                     const d1 = link?.link_anchor_1 as Doc;
                     const d2 = link?.link_anchor_2 as Doc;
                     if (doc === d1 && this._results.has(d2)) {
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index 39e778b76..c9c01189e 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -1,14 +1,14 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ListBox } from 'browndash-components';
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+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 { DocFocusOptions } from '../nodes/DocumentView';
-import { emptyFunction } from '../../../Utils';
 import { SettingsManager } from '../../util/SettingsManager';
+import { FocusViewOptions } from '../nodes/FieldView';
 
 export interface SelectedDocViewProps {
     selectedDocs: Doc[];
@@ -25,7 +25,7 @@ export class SelectedDocView extends React.Component<SelectedDocViewProps> {
             <div className="selectedDocView-container">
                 <ListBox
                     items={this.selectedDocs.map(doc => {
-                        const options: DocFocusOptions = {
+                        const options: FocusViewOptions = {
                             playAudio: false,
                             playMedia: false,
                             willPan: true,
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index 94458563e..4e984f3d6 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -8,7 +8,6 @@ import { Doc } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
 import { SnappingManager } from '../../util/SnappingManager';
 import '../../views/nodes/WebBox.scss';
-import { CollectionFreeFormDocumentViewProps } from '../nodes/CollectionFreeFormDocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import './DashWebRTCVideo.scss';
 import { hangup, initialize, refreshVideos } from './WebCamLogic';
@@ -17,14 +16,12 @@ import { hangup, initialize, refreshVideos } from './WebCamLogic';
  * This models the component that will be rendered, that can be used as a doc that will reflect the video cams.
  */
 @observer
-export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentViewProps & FieldViewProps> {
+export class DashWebRTCVideo extends React.Component<FieldViewProps> {
     private roomText: HTMLInputElement | undefined;
     @observable remoteVideoAdded: boolean = false;
 
     @action
-    changeUILook = () => {
-        this.remoteVideoAdded = true;
-    };
+    changeUILook = () => (this.remoteVideoAdded = true);
 
     /**
      * Function that submits the title entered by user on enter press.
@@ -42,14 +39,9 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV
         return FieldView.LayoutString(DashWebRTCVideo, fieldKey);
     }
 
-    @action
-    onClickRefresh = () => {
-        refreshVideos();
-    };
+    onClickRefresh = () => refreshVideos();
 
-    onClickHangUp = () => {
-        hangup();
-    };
+    onClickHangUp = () => hangup();
 
     render() {
         const content = (
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 9c2350bd0..ff416bbe7 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -31,6 +31,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor }
 import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
 import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util';
 import * as JSZip from 'jszip';
+import { FieldViewProps } from '../client/views/nodes/FieldView';
 export const LinkedTo = '-linkedTo';
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -423,7 +424,7 @@ export namespace Doc {
     //
     export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
         if (key.startsWith('_')) key = key.substring(1);
-        const hasProto = Doc.GetProto(doc) !== doc ? Doc.GetProto(doc) : undefined;
+        const hasProto = doc[DocData] !== doc ? doc[DocData] : undefined;
         const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
         const onProto = hasProto && Object.getOwnPropertyNames(hasProto).indexOf(key) !== -1;
         if (onDeleg || !hasProto || (!onProto && !defaultProto)) {
@@ -510,7 +511,7 @@ export namespace Doc {
     export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc) {
         const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
         if (listDoc[key] === undefined) {
-            Doc.GetProto(listDoc)[key] = new List<Doc>();
+            listDoc[DocData][key] = new List<Doc>();
         }
         const list = Cast(listDoc[key], listSpec(Doc));
         if (list) {
@@ -530,7 +531,7 @@ export namespace Doc {
     export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) {
         const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
         if (listDoc[key] === undefined) {
-            Doc.GetProto(listDoc)[key] = new List<Doc>();
+            listDoc[DocData][key] = new List<Doc>();
         }
         const list = Cast(listDoc[key], listSpec(Doc));
         if (list) {
@@ -564,7 +565,7 @@ export namespace Doc {
             Doc.SetLayout(embedding, Doc.MakeEmbedding(layout));
         }
         embedding.createdFrom = doc;
-        embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length - 1;
+        embedding.proto_embeddingId = doc[DocData].proto_embeddingId = DocListCast(doc[DocData].proto_embeddings).length - 1;
         !Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`));
         embedding.author = Doc.CurrentUserEmail;
 
@@ -1084,8 +1085,8 @@ export namespace Doc {
     export function LayoutField(doc: Doc) {
         return doc[StrCast(doc.layout_fieldKey, 'layout')];
     }
-    export function LayoutFieldKey(doc: Doc): string {
-        return StrCast(Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey
+    export function LayoutFieldKey(doc: Doc, templateLayoutString?: string): string {
+        return StrCast(templateLayoutString || Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey
     }
     export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
@@ -1100,10 +1101,10 @@ export namespace Doc {
         return NumCast(doc._nativeHeight, nheight || dheight);
     }
     export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
-        doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '_nativeWidth'] = width;
+        doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeWidth'] = width;
     }
     export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) {
-        doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height;
+        doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height;
     }
 
     const manager = new UserDocData();
@@ -1127,22 +1128,22 @@ export namespace Doc {
     }
 
     const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) {
-        return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+        return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined;
     });
     export function IsSearchMatch(doc: Doc) {
         return isSearchMatchCache(doc);
     }
     export function IsSearchMatchUnmemoized(doc: Doc) {
-        return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+        return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined;
     }
     export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) {
-        if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
+        if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(doc[DocData]) !== AclPrivate) {
             brushManager.SearchMatchDoc.set(doc, results);
         }
         return doc;
     }
     export function SearchMatchNext(doc: Doc, backward: boolean) {
-        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate) return doc;
         const result = brushManager.SearchMatchDoc.get(doc);
         const num = Math.abs(result?.searchMatch || 0) + 1;
         runInAction(() => result && brushManager.SearchMatchDoc.set(doc, { searchMatch: backward ? -num : num }));
@@ -1160,13 +1161,13 @@ export namespace Doc {
     }
     // returns 'how' a Doc has been brushed over - whether the document itself was brushed, it's prototype, or neither
     export function GetBrushStatus(doc: Doc) {
-        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed;
-        return doc[Brushed] ? DocBrushStatus.selfBrushed : Doc.GetProto(doc)[Brushed] ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed;
+        return doc[Brushed] ? DocBrushStatus.selfBrushed : doc[DocData][Brushed] ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
     }
     export function BrushDoc(doc: Doc, unbrush = false) {
-        if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
+        if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(doc[DocData]) !== AclPrivate) {
             brushManager.brushDoc(doc, unbrush);
-            brushManager.brushDoc(Doc.GetProto(doc), unbrush);
+            brushManager.brushDoc(doc[DocData], unbrush);
         }
         return doc;
     }
@@ -1186,6 +1187,7 @@ export namespace Doc {
     }
     export function linkFollowUnhighlight() {
         clearTimeout(UnhighlightTimer);
+        UnhighlightTimer = 0;
         UnhighlightWatchers.forEach(watcher => watcher());
         UnhighlightWatchers.length = 0;
         highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
@@ -1205,8 +1207,8 @@ export namespace Doc {
 
     export var highlightedDocs = new ObservableSet<Doc>();
     export function IsHighlighted(doc: Doc) {
-        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false;
-        return doc[Highlight] || Doc.GetProto(doc)[Highlight];
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate || doc.opacity === 0) return false;
+        return doc[Highlight] || doc[DocData][Highlight];
     }
     export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presentation_effect?: Doc) {
         runInAction(() => {
@@ -1214,8 +1216,8 @@ export namespace Doc {
             doc[Highlight] = true;
             doc[Animation] = presentation_effect;
             if (dataAndDisplayDocs) {
-                highlightedDocs.add(Doc.GetProto(doc));
-                Doc.GetProto(doc)[Highlight] = true;
+                highlightedDocs.add(doc[DocData]);
+                doc[DocData][Highlight] = true;
             }
         });
     }
@@ -1224,8 +1226,8 @@ export namespace Doc {
         runInAction(() => {
             (doc ? [doc] : Array.from(highlightedDocs)).forEach(doc => {
                 highlightedDocs.delete(doc);
-                highlightedDocs.delete(Doc.GetProto(doc));
-                doc[Highlight] = Doc.GetProto(doc)[Highlight] = false;
+                highlightedDocs.delete(doc[DocData]);
+                doc[Highlight] = doc[DocData][Highlight] = false;
                 doc[Animation] = undefined;
             });
         });
@@ -1346,7 +1348,7 @@ export namespace Doc {
         });
     }
 
-    export function styleFromLayoutString(doc: Doc, props: any, scale: number) {
+    export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: number) {
         const style: { [key: string]: any } = {};
         const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
         const replacer = (match: any, expr: string, offset: any, string: any) => {
@@ -1354,7 +1356,7 @@ export namespace Doc {
             return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? '';
         };
         divKeys.map((prop: string) => {
-            const p = props[prop];
+            const p = (props as any)[prop];
             typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
         });
         return style;
@@ -1593,7 +1595,7 @@ ScriptingGlobals.add(function idToDoc(id: string): any {
     return IdToDoc(id);
 });
 ScriptingGlobals.add(function renameEmbedding(doc: any) {
-    return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
+    return StrCast(doc[DocData].title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
 });
 ScriptingGlobals.add(function getProto(doc: any) {
     return Doc.GetProto(doc);
-- 
cgit v1.2.3-70-g09d2


From c8be94244bcd094efba739e5a46034bceccea80f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 Jan 2024 16:13:27 -0500
Subject: several fixes to drag/drop so that dropAction's set on target work
 for tree views, notetaking, formattedText, etc.   make bringToFront an
 optional prop.

---
 src/client/documents/Documents.ts                  |  3 +-
 src/client/util/DragManager.ts                     |  8 +-
 src/client/views/DocComponent.tsx                  |  6 +-
 src/client/views/GestureOverlay.tsx                |  1 -
 src/client/views/LightboxView.tsx                  |  1 -
 src/client/views/MainView.tsx                      |  5 --
 src/client/views/OverlayView.tsx                   |  1 -
 src/client/views/PropertiesView.tsx                |  1 -
 src/client/views/StyleProvider.tsx                 |  2 +-
 src/client/views/TemplateMenu.tsx                  |  1 -
 .../views/collections/CollectionCarousel3DView.tsx |  3 +-
 .../views/collections/CollectionCarouselView.tsx   |  1 -
 .../collections/CollectionMasonryViewFieldRow.tsx  |  2 +-
 src/client/views/collections/CollectionMenu.tsx    |  1 -
 .../views/collections/CollectionNoteTakingView.tsx |  1 -
 .../collections/CollectionNoteTakingViewColumn.tsx |  6 +-
 .../collections/CollectionStackedTimeline.tsx      |  1 -
 .../views/collections/CollectionStackingView.tsx   |  2 -
 .../CollectionStackingViewFieldColumn.tsx          |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  4 +-
 .../views/collections/CollectionTreeView.tsx       | 11 +--
 src/client/views/collections/CollectionView.tsx    |  2 +-
 src/client/views/collections/TabDocView.tsx        |  2 -
 src/client/views/collections/TreeView.tsx          | 72 +++++++++++-------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 -
 .../collectionLinear/CollectionLinearView.tsx      | 17 ++---
 .../CollectionMulticolumnView.tsx                  |  3 +-
 .../CollectionMultirowView.tsx                     |  3 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  2 -
 .../collectionSchema/SchemaTableCell.tsx           |  1 -
 src/client/views/linking/LinkPopup.tsx             |  4 +-
 src/client/views/newlightbox/NewLightboxView.tsx   |  1 -
 src/client/views/nodes/AudioBox.tsx                |  4 +-
 src/client/views/nodes/DocumentContentsView.tsx    |  1 +
 src/client/views/nodes/DocumentView.tsx            | 87 +++++++++++-----------
 src/client/views/nodes/FieldView.tsx               |  4 +-
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 src/client/views/nodes/KeyValuePair.tsx            |  1 -
 src/client/views/nodes/LinkDocPreview.tsx          |  1 -
 src/client/views/nodes/PDFBox.tsx                  |  2 +-
 src/client/views/nodes/VideoBox.tsx                |  3 +-
 src/client/views/nodes/WebBox.tsx                  |  1 -
 .../views/nodes/formattedText/DashDocView.tsx      |  1 -
 .../views/nodes/formattedText/FormattedTextBox.tsx | 15 +++-
 src/client/views/nodes/trails/PresBox.tsx          |  1 +
 src/client/views/nodes/trails/PresElementBox.tsx   |  1 -
 src/client/views/pdf/PDFViewer.tsx                 |  1 -
 src/client/views/topbar/TopBar.tsx                 |  1 -
 src/mobile/MobileInterface.tsx                     |  1 -
 49 files changed, 138 insertions(+), 160 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index bcc016391..2cbc90387 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -259,6 +259,7 @@ export class DocumentOptions {
     _layout_showCaption?: string; // which field to display in the caption area.  leave empty to have no caption
 
     _chromeHidden?: BOOLt = new BoolInfo('whether the editing chrome for a document is hidden');
+    hideClickBehaviors?: BOOLt = new BoolInfo('whether to hide click behaviors in context menu');
     _gridGap?: NUMt = new NumInfo('gap between items in masonry view', false);
     _xMargin?: NUMt = new NumInfo('gap between left edge of document and start of masonry/stacking layouts', false);
     _yMargin?: NUMt = new NumInfo('gap between top edge of dcoument and start of masonry/stacking layouts', false);
@@ -658,7 +659,7 @@ export namespace Docs {
                 DocumentType.PRES,
                 {
                     layout: { view: PresBox, dataField: defaultDataKey },
-                    options: { defaultDoubleClick: 'ignore', layout_hideLinkAnchors: true },
+                    options: { defaultDoubleClick: 'ignore', hideClickBehaviors: true, layout_hideLinkAnchors: true },
                 },
             ],
             [
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 70c40c54e..a6ad0f1b3 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -181,7 +181,7 @@ export namespace DragManager {
         }
     };
 
-    export function MakeDropTarget(element: HTMLElement, dropFunc: (e: Event, de: DropEvent) => void, doc?: Doc, preDropFunc?: (e: Event, de: DropEvent, targetAction: dropActionType) => void): DragDropDisposer {
+    export function MakeDropTarget(element: HTMLElement, dropFunc: (e: Event, de: DropEvent) => void, doc: Doc, preDropFunc?: (e: Event, de: DropEvent, targetAction: dropActionType) => void): DragDropDisposer {
         if ('canDrop' in element.dataset) {
             throw new Error("Element is already droppable, can't make it droppable again");
         }
@@ -189,13 +189,13 @@ export namespace DragManager {
         const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
         const preDropHandler = (e: Event) => {
             const de = (e as CustomEvent<DropEvent>).detail;
-            (preDropFunc ?? defaultPreDropFunc)(e, de, StrCast(doc?.dropAction) as dropActionType);
+            (preDropFunc ?? defaultPreDropFunc)(e, de, StrCast(doc.dropAction) as dropActionType);
         };
         element.addEventListener('dashOnDrop', handler);
-        doc && element.addEventListener('dashPreDrop', preDropHandler);
+        element.addEventListener('dashPreDrop', preDropHandler);
         return () => {
             element.removeEventListener('dashOnDrop', handler);
-            doc && element.removeEventListener('dashPreDrop', preDropHandler);
+            element.removeEventListener('dashPreDrop', preDropHandler);
             delete element.dataset.canDrop;
         };
     }
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 0e5a4f013..e7469a37c 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -172,7 +172,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
         }
 
         @action.bound
-        removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean): boolean {
+        removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean): boolean {
             const effectiveAcl = GetEffectiveAcl(this.dataDoc);
             const indocs = doc instanceof Doc ? [doc] : doc;
             const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
@@ -189,7 +189,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
                     Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
                     Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', doc);
                     doc.embedContainer = undefined;
-                    if (recent) {
+                    if (recent && !dontAddToRemoved) {
                         doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
                     }
                 });
@@ -214,7 +214,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
             }
             const first = doc instanceof Doc ? doc : doc[0];
             if (!first?._dragOnlyWithinContainer && addDocument !== returnFalse) {
-                return this.removeDocument(doc, annotationKey, false) && addDocument(doc, annotationKey);
+                return this.removeDocument(doc, annotationKey, false, true) && addDocument(doc, annotationKey);
             }
             return false;
         };
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 3c7b5eeca..86b9f5e40 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -479,7 +479,6 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
                 containerViewPath={returnEmptyDoclist}
                 focus={emptyFunction}
                 whenChildContentsActiveChanged={emptyFunction}
-                bringToFront={emptyFunction}
                 childFiltersByRanges={returnEmptyFilter}
                 childFilters={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 530fd3121..c7ff7ce47 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -266,7 +266,6 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                             whenChildContentsActiveChanged={emptyFunction}
                             addDocTab={this.AddDocTab}
                             pinToPres={TabDocView.PinDoc}
-                            bringToFront={emptyFunction}
                             onBrowseClickScript={DocumentView.exploreMode}
                             focus={emptyFunction}
                         />
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 9061e98d4..eca0aca4c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -632,7 +632,6 @@ export class MainView extends ObservableReactComponent<{}> {
                     renderDepth={0}
                     focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
-                    bringToFront={emptyFunction}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
@@ -661,7 +660,6 @@ export class MainView extends ObservableReactComponent<{}> {
                     PanelHeight={this.mainDocViewHeight}
                     focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
-                    bringToFront={emptyFunction}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
@@ -756,7 +754,6 @@ export class MainView extends ObservableReactComponent<{}> {
                         isContentActive={returnTrue}
                         focus={emptyFunction}
                         whenChildContentsActiveChanged={emptyFunction}
-                        bringToFront={emptyFunction}
                         childFilters={returnEmptyFilter}
                         childFiltersByRanges={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
@@ -785,7 +782,6 @@ export class MainView extends ObservableReactComponent<{}> {
                     styleProvider={DefaultStyleProvider}
                     isContentActive={returnTrue}
                     whenChildContentsActiveChanged={emptyFunction}
-                    bringToFront={emptyFunction}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
@@ -913,7 +909,6 @@ export class MainView extends ObservableReactComponent<{}> {
                     fieldKey="data"
                     dropAction="embed"
                     styleProvider={DefaultStyleProvider}
-                    bringToFront={emptyFunction}
                     select={emptyFunction}
                     isAnyChildContentActive={returnFalse}
                     isContentActive={emptyFunction}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 89527c415..20dc6c9fa 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -221,7 +221,6 @@ export class OverlayView extends ObservableReactComponent<{}> {
                     style={{ top: d.type === DocumentType.PRES ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.overlayX}px, ${d.overlayY}px)` }}>
                     <DocumentView
                         Document={d}
-                        bringToFront={emptyFunction}
                         addDocument={undefined}
                         removeDocument={this.removeOverlayDoc}
                         PanelWidth={d[Width]}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 952c8f0b4..208ed56c9 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -319,7 +319,6 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                         whenChildContentsActiveChanged={emptyFunction}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
-                        bringToFront={returnFalse}
                         dontRegisterView={true}
                     />
                 </div>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 2aba439c8..58f9784d4 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -259,7 +259,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             if (opacity() === 0) return 'none';
             if (props?.isGroupActive?.() ) return isInk()  ? 'visiblePainted':  (doc?.
                 isGroup )? undefined: 'all' 
-            if (props?.isDocumentActive?.() && !props.treeViewDoc) return isInk() ? 'visiblePainted' : 'all';
+            if (props?.isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations:
             const lock = () => doc?.pointerEvents !== 'none' ? null : (
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index ba7120b93..5fc33207e 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -123,7 +123,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                     onChildClick={this.scriptField}
                     isAnyChildContentActive={returnFalse}
                     isContentActive={returnTrue}
-                    bringToFront={emptyFunction}
                     focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
                     ScreenToLocalTransform={Transform.Identity}
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 2d2cb4860..7e484f3df 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Utils, returnFalse, returnZero } from '../../../Utils';
+import { Utils, emptyFunction, returnFalse, returnZero } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -81,7 +81,6 @@ export class CollectionCarousel3DView extends CollectionSubView() {
                     isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
                     PanelWidth={this.panelWidth}
                     PanelHeight={this.panelHeight}
-                    bringToFront={returnFalse}
                 />
             );
         };
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 79f5baabe..dbe1536f2 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -78,7 +78,6 @@ export class CollectionCarouselView extends CollectionSubView() {
                         Document={curDoc.layout}
                         TemplateDataDocument={DocCast(curDoc.layout.resolvedDataDoc)}
                         PanelHeight={this.panelHeight}
-                        bringToFront={returnFalse}
                     />
                 </div>
                 {!carouselShowsCaptions ? null : (
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 8627ba650..41c5d5b42 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -71,7 +71,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
         if (ele) {
             this._ele = ele;
             this._props.observeHeight(ele);
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this), this._props.Document);
         }
     };
     @action
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 2dad377e0..0f90818ef 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -93,7 +93,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     fieldKey="data"
                     dropAction="embed"
                     styleProvider={DefaultStyleProvider}
-                    bringToFront={emptyFunction}
                     select={emptyFunction}
                     isContentActive={returnTrue}
                     isAnyChildContentActive={returnFalse}
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index e6ce5baab..3a9892ed0 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -270,7 +270,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents) as any}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
                 addDocTab={this._props.addDocTab}
-                bringToFront={returnFalse}
                 pinToPres={this._props.pinToPres}
             />
         );
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 3f6d3c82e..38846c79d 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -81,7 +81,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
         if (ele) {
             this._ele = ele;
             this._props.observeHeight(ele);
-            this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
+            this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document);
         }
     };
 
@@ -90,11 +90,11 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
     }
 
     @undoBatch
-    columnDrop = action((e: Event, de: DragManager.DropEvent) => {
+    columnDrop = (e: Event, de: DragManager.DropEvent) => {
         const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
         drop.docs?.forEach(d => Doc.SetInPlace(d, this._props.pivotField, drop.val, false));
         return true;
-    });
+    };
 
     getValue = (value: string): any => {
         const parsed = parseInt(value);
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 1394e62ba..5c47d4b9e 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -847,7 +847,6 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
                     onDoubleClickScript={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
                     ignoreAutoHeight={false}
                     hideResizeHandles={true}
-                    bringToFront={emptyFunction}
                     contextMenuItems={this.contextMenuItems}
                 />
             ),
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0da32a9e0..d436d3c2f 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -358,7 +358,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents) as any}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
                 addDocTab={this._props.addDocTab}
-                bringToFront={returnFalse}
                 pinToPres={this._props.pinToPres}
             />
         );
@@ -682,7 +681,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                     styleProvider={this._props.styleProvider}
                     containerViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
-                    bringToFront={emptyFunction}
                     childFilters={this._props.childFilters}
                     childFiltersByRanges={this._props.childFiltersByRanges}
                     searchFilterDocs={this._props.searchFilterDocs}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 69f1c332d..c455f20d8 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -74,7 +74,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         if (ele) {
             this._ele = ele;
             this._props.observeHeight(ele);
-            this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
+            this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document);
         }
     };
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 0aa74aaba..fdbd1cc90 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -196,10 +196,8 @@ export function CollectionSubView<X>(moreProps?: X) {
         @undoBatch
         protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {}
 
-        protected onInternalPreDrop(e: Event, de: DragManager.DropEvent) {
+        protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, dropAction: dropActionType) {
             if (de.complete.docDragData) {
-                // override the dropEvent's dropAction
-                const dropAction = this.layoutDoc.dropAction as dropActionType;
                 // if the dropEvent's dragAction is, say 'embed', but we're just dragging within a collection, we may not actually want to make an embedding.
                 // so we check if our collection has a dropAction set on it and if so, we use that instead.
                 if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d))) {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 4696d7948..cc2cf87aa 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -140,15 +140,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this));
     };
 
-    protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent) => {
-        const dropAction = this.layoutDoc.dropAction as dropActionType;
+    protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => {
         const dragData = de.complete.docDragData;
         if (dragData) {
             const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.Document) ? true : false;
             const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d));
-            if (isAlreadyInTree() !== sameTree) {
-                console.log('WHAAAT');
-            }
             dragData.dropAction = dropAction && !isAlreadyInTree() ? dropAction : sameTree && dragData.dropAction !== 'inSame' ? 'same' : dragData.dropAction;
             e.stopPropagation();
         }
@@ -254,7 +250,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 moveDocument={returnFalse}
                 removeDocument={returnFalse}
                 whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                bringToFront={returnFalse}
             />
         );
     }
@@ -349,7 +344,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                     styleProvider={this._props.styleProvider}
                     containerViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
-                    bringToFront={emptyFunction}
                     childFilters={this._props.childFilters}
                     childFiltersByRanges={this._props.childFiltersByRanges}
                     searchFilterDocs={this._props.searchFilterDocs}
@@ -453,14 +447,13 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                         pointerEvents={this._props.isContentActive() && SnappingManager.IsDragging ? returnAll : returnNone}
                         isAnnotationOverlay={true}
                         isAnnotationOverlayScrollable={true}
-                        childDocumentsActive={this._props.isDocumentActive}
+                        childDocumentsActive={this._props.isContentActive}
                         fieldKey={this._props.fieldKey + '_annotations'}
                         dropAction="move"
                         select={emptyFunction}
                         addDocument={this.addAnnotationDocument}
                         removeDocument={this.remAnnotationDocument}
                         moveDocument={this.moveAnnotationDocument}
-                        bringToFront={emptyFunction}
                         renderDepth={this._props.renderDepth + 1}>
                         {this.content}
                     </CollectionFreeFormView>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 26ff5cc06..6cef469b6 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -189,7 +189,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
 
             !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' });
 
-            if (!Doc.noviceMode && !this.Document.annotationOn) {
+            if (!Doc.noviceMode && !this.Document.annotationOn && !this.Document.hideClickBehaviors) {
                 const existingOnClick = cm.findByDescription('OnClick...');
                 const onClicks = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
                 const funcs = [
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index da15f8dc5..9bc3ef822 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -470,7 +470,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                     whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
                     focus={this.focusFunc}
                     containerViewPath={returnEmptyDoclist}
-                    bringToFront={emptyFunction}
                     pinToPres={TabDocView.PinDoc}
                 />
                 {this.disableMinimap() ? null : <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />}
@@ -604,7 +603,6 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
                     isSelected={returnFalse}
                     dontRegisterView={true}
                     fieldKey={Doc.LayoutFieldKey(this._props.document)}
-                    bringToFront={emptyFunction}
                     addDocument={returnFalse}
                     moveDocument={returnFalse}
                     removeDocument={returnFalse}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 38a2fe978..40933321f 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -4,7 +4,7 @@ import { IconButton, Size } from 'browndash-components';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Utils, emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick } from '../../../Utils';
+import { Utils, emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../Utils';
 import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -282,12 +282,13 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     _treeEle: any;
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this._treedropDisposer?.();
-        ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this))), this.Document);
+        ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), this.Document, this.preTreeDrop.bind(this))), this.Document);
         if (this._treeEle) this._props.unobserveHeight(this._treeEle);
         this._props.observeHeight((this._treeEle = ele));
     };
 
     componentWillUnmount() {
+        this._treedropDisposer?.();
         this._renderTimer && clearTimeout(this._renderTimer);
         Object.values(this._disposers).forEach(disposer => disposer?.());
         this._treeEle && this._props.unobserveHeight(this._treeEle);
@@ -339,11 +340,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         const before = pt[1] < rect.top + rect.height / 2;
         const inside = pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length);
         this._header.current!.className = 'treeView-header';
-        if (!this.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.treeView.Document) {
-            if (inside) this._header.current!.className += ' treeView-header-inside';
-            else if (before) this._header.current!.className += ' treeView-header-above';
-            else if (!before) this._header.current!.className += ' treeView-header-below';
-        }
+        if (inside) this._header.current!.className += ' treeView-header-inside';
+        else if (before) this._header.current!.className += ' treeView-header-above';
+        else if (!before) this._header.current!.className += ' treeView-header-below';
         e.stopPropagation();
     };
 
@@ -387,9 +386,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         return this._props.addDocument(folder);
     };
 
-    preTreeDrop = (e: Event, de: DragManager.DropEvent) => {
-        const dragData = de.complete.docDragData;
-        dragData && (dragData.dropAction = this.treeView.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction);
+    preTreeDrop = (e: Event, de: DragManager.DropEvent, docDropAction: dropActionType) => {
+        // fall through and let the CollectionTreeView handle this since treeView items have no special properties of their own
     };
 
     @undoBatch
@@ -416,7 +414,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 docDragData.dropAction,
                 docDragData.removeDocument,
                 docDragData.moveDocument,
-                docDragData.treeViewDoc === this.treeView.Document
+                docDragData.treeViewDoc === this.treeView.Document,
+                de.embedKey
             );
             e.stopPropagation();
             !added && e.preventDefault();
@@ -426,7 +425,16 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     };
 
     dropping: boolean = false;
-    dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, removeDocument: DragManager.RemoveFunction | undefined, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) {
+    dropDocuments(
+        droppedDocuments: Doc[],
+        before: boolean,
+        inside: number | boolean,
+        dropAction: dropActionType,
+        removeDocument: DragManager.RemoveFunction | undefined,
+        moveDocument: DragManager.MoveFunction | undefined,
+        forceAdd: boolean,
+        canEmbed?: boolean
+    ) {
         const parentAddDoc = (doc: Doc | Doc[]) => this._props.addDocument(doc, undefined, undefined, before);
         const localAdd = (doc: Doc | Doc[]) => {
             const innerAdd = (doc: Doc) => {
@@ -438,9 +446,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
         };
         const addDoc = inside ? localAdd : parentAddDoc;
-        const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument;
         const canAdd = (!this.treeView.outlineMode && !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd;
         if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) {
+            const move = (!dropAction || canEmbed || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument;
             this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = true);
             const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false);
             this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = false);
@@ -602,7 +610,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => (d.zIndex = i));
                 }
                 const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
-                const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
+                const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false);
                 !dataIsComputed && added && Doc.SetContainer(doc, this.Document);
 
                 return added;
@@ -841,7 +849,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 ? folderOp
                 : Doc.IsSystem(this.Document)
                   ? []
-                  : this.treeView.fileSysMode && this.Document === Doc.GetProto(this.Document)
+                  : this.treeView.fileSysMode && this.Document === this.Document[DocData]
                     ? [openEmbedding, makeFolder]
                     : this.Document._type_collection === CollectionViewType.Docking
                       ? []
@@ -974,8 +982,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 Document={this.Document}
                 layout_fitWidth={returnTrue}
                 scriptContext={this}
-                hideDecorationTitle={this.treeView.outlineMode}
-                hideResizeHandles={this.treeView.outlineMode}
+                hideDecorations={true}
+                hideClickBehaviors={true}
                 styleProvider={this.titleStyleProvider}
                 onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
                 containerViewPath={this.treeView.childContainerViewPath}
@@ -999,7 +1007,6 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 isDocumentActive={this._props.isContentActive}
                 focus={this.refocus}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                bringToFront={emptyFunction}
                 disableBrushing={this.treeView._props.disableBrushing}
                 hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
                 dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
@@ -1041,7 +1048,19 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     key="titleheader"
                     ref={this._header}
                     onClick={this.ignoreEvent}
-                    onPointerDown={this.ignoreEvent}
+                    onPointerDown={e => {
+                        this.treeView.isContentActive() &&
+                            setupMoveUpEvents(
+                                this,
+                                e,
+                                () => {
+                                    this._dref?.startDragging(e.clientX, e.clientY, '' as any);
+                                    return true;
+                                },
+                                returnFalse,
+                                emptyFunction
+                            );
+                    }}
                     onPointerEnter={this.onPointerEnter}
                     onPointerLeave={this.onPointerLeave}>
                     <div
@@ -1073,22 +1092,22 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     isContentActive={isActive}
                     isDocumentActive={isActive}
                     styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
-                    hideTitle={asText}
                     fitContentsToBox={returnTrue}
-                    hideDecorationTitle={this.treeView.outlineMode}
-                    hideResizeHandles={this.treeView.outlineMode}
-                    onClickScript={this.onChildClick}
-                    focus={this.refocus}
-                    onKey={this.onKeyDown}
+                    hideTitle={asText}
+                    hideDecorations={true}
+                    hideClickBehaviors={true}
                     hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
                     dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
                     ScreenToLocalTransform={this.docTransform}
                     renderDepth={this._props.renderDepth + 1}
+                    onClickScript={this.onChildClick}
+                    onKey={this.onKeyDown}
                     treeViewDoc={this.treeView?.Document}
                     containerViewPath={this.treeView.childContainerViewPath}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
+                    focus={this.refocus}
                     addDocument={this._props.addDocument}
                     moveDocument={this.move}
                     removeDocument={this._props.removeDoc}
@@ -1098,7 +1117,6 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     addDocTab={this._props.addDocTab}
                     pinToPres={this.treeView._props.pinToPres}
                     disableBrushing={this.treeView._props.disableBrushing}
-                    bringToFront={returnFalse}
                     scriptContext={this}
                 />
             </div>
@@ -1143,7 +1161,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         const before = pt[1] < rect.top + rect.height / 2;
         const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
 
-        const docs = this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false));
+        const docs = this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false, false));
     };
 
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7203041e0..53dc389b4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1243,7 +1243,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 pinToPres={this._props.pinToPres}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
                 dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
-                bringToFront={this.bringToFront}
                 layout_showTitle={this._props.childlayout_showTitle}
                 dontRegisterView={this._props.dontRegisterView}
                 pointerEvents={this.childPointerEventsFunc}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 72bdb9b6b..228af78aa 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -79,16 +79,16 @@ export class CollectionLinearView extends CollectionSubView() {
 
     @action
     changeDescriptionSetting = () => {
-        if (LinkDescriptionPopup.showDescriptions) {
-            if (LinkDescriptionPopup.showDescriptions === 'ON') {
-                LinkDescriptionPopup.showDescriptions = 'OFF';
-                LinkDescriptionPopup.descriptionPopup = false;
+        if (LinkDescriptionPopup.Instance.showDescriptions) {
+            if (LinkDescriptionPopup.Instance.showDescriptions === 'ON') {
+                LinkDescriptionPopup.Instance.showDescriptions = 'OFF';
+                LinkDescriptionPopup.Instance.display = false;
             } else {
-                LinkDescriptionPopup.showDescriptions = 'ON';
+                LinkDescriptionPopup.Instance.showDescriptions = 'ON';
             }
         } else {
-            LinkDescriptionPopup.showDescriptions = 'OFF';
-            LinkDescriptionPopup.descriptionPopup = false;
+            LinkDescriptionPopup.Instance.showDescriptions = 'OFF';
+            LinkDescriptionPopup.Instance.display = false;
         }
     };
 
@@ -110,7 +110,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
                 <Tooltip title={<div className="dash-tooltip">{'Toggle description pop-up'} </div>} placement="top">
                     <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
-                        Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : 'ON'}
+                        Labels: {LinkDescriptionPopup.Instance.showDescriptions ? LinkDescriptionPopup.Instance.showDescriptions : 'ON'}
                     </span>
                 </Tooltip>
 
@@ -191,7 +191,6 @@ export class CollectionLinearView extends CollectionSubView() {
                     styleProvider={this._props.styleProvider}
                     containerViewPath={this.childContainerViewPath}
                     whenChildContentsActiveChanged={emptyFunction}
-                    bringToFront={emptyFunction}
                     childFilters={this._props.childFilters}
                     childFiltersByRanges={this._props.childFiltersByRanges}
                     searchFilterDocs={this._props.searchFilterDocs}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 1fa106b45..b181b59ce 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -4,7 +4,7 @@ import { Button } from 'browndash-components';
 import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { returnFalse } from '../../../../Utils';
+import { emptyFunction, returnFalse } from '../../../../Utils';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { DragManager, dropActionType } from '../../../util/DragManager';
@@ -287,7 +287,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
                 addDocTab={this._props.addDocTab}
                 pinToPres={this._props.pinToPres}
-                bringToFront={returnFalse}
                 dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
             />
         );
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 97a444b8c..659f7ccdc 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { returnFalse } from '../../../../Utils';
+import { emptyFunction, returnFalse } from '../../../../Utils';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { DragManager, dropActionType } from '../../../util/DragManager';
@@ -281,7 +281,6 @@ export class CollectionMultirowView extends CollectionSubView() {
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
                 addDocTab={this._props.addDocTab}
                 pinToPres={this._props.pinToPres}
-                bringToFront={returnFalse}
                 dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
             />
         );
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index ec91b25f8..581425d77 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -914,7 +914,6 @@ export class CollectionSchemaView extends CollectionSubView() {
                                 whenChildContentsActiveChanged={returnFalse}
                                 addDocTab={this._props.addDocTab}
                                 pinToPres={this._props.pinToPres}
-                                bringToFront={returnFalse}
                             />
                         )}
                     </div>
@@ -989,7 +988,6 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
                 searchFilterDocs={this._props.schema.searchFilterDocs}
                 rootSelected={this._props.schema.rootSelected}
                 ScreenToLocalTransform={this.screenToLocalXf}
-                bringToFront={emptyFunction}
                 dragWhenActive={true}
                 isDocumentActive={this._props.schema._props.childDocumentsActive?.() ? this._props.schema._props.isDocumentActive : this._props.schema.isContentActive}
                 isContentActive={emptyFunction}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index fda5764dd..dbaa6e110 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -82,7 +82,6 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
             setHeight: returnFalse,
             select: emptyFunction,
             dragAction: 'move',
-            bringToFront: emptyFunction,
             renderDepth: 1,
             isContentActive: returnFalse,
             whenChildContentsActiveChanged: emptyFunction,
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 31841c596..c9e3c203d 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -7,7 +7,7 @@ import { Doc } from '../../../fields/Doc';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
 import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere } from '../nodes/DocumentView';
+import { OpenWhere, returnEmptyDocViewList } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { SearchBox } from '../search/SearchBox';
 import './LinkPopup.scss';
@@ -61,6 +61,7 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
 
                     <SearchBox
                         Document={Doc.MySearcher}
+                        docViewPath={returnEmptyDocViewList}
                         linkFrom={linkDoc}
                         linkCreateAnchor={this.props.linkCreateAnchor}
                         linkSearch={true}
@@ -81,7 +82,6 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
                         renderDepth={0}
                         focus={emptyFunction}
                         whenChildContentsActiveChanged={emptyFunction}
-                        bringToFront={emptyFunction}
                         childFilters={returnEmptyFilter}
                         childFiltersByRanges={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index ed575c329..12b9870ca 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -311,7 +311,6 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
                         whenChildContentsActiveChanged={emptyFunction}
                         addDocTab={this.addDocTab}
                         pinToPres={TabDocView.PinDoc}
-                        bringToFront={emptyFunction}
                         onBrowseClickScript={DocumentView.exploreMode}
                         focus={emptyFunction}
                     />
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 2b71fd156..8a38ef663 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -568,8 +568,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     const [xp, yp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
                     de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
                 },
-                this.layoutDoc,
-                undefined
+                this.layoutDoc
             );
         }
     };
@@ -718,7 +717,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 renderDepth={this._props.renderDepth + 1}
                 startTag={'_timecodeToShow' /* audioStart */}
                 endTag={'_timecodeToHide' /* audioEnd */}
-                bringToFront={emptyFunction}
                 playFrom={this.playFrom}
                 setTime={this.setPlayheadTime}
                 playing={this.playing}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 9538cebb2..d1e141061 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -150,6 +150,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
             // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
             'hideResizeHandles',
             'hideTitle',
+            'bringToFront',
             'contentPointerEvents',
             'LayoutTemplateString',
             'LayoutTemplate',
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3e5e43b47..76ad29f60 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -255,17 +255,17 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             { fireImmediately: true }
         );
     }
-    preDropFunc = (e: Event, de: DragManager.DropEvent) => {
-        const dropAction = this.layoutDoc.dropAction as dropActionType;
-        if (de.complete.docDragData && this.isContentActive() && !this._props.treeViewDoc) {
-            dropAction && (de.complete.docDragData.dropAction = dropAction);
+    preDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => {
+        const dragData = de.complete.docDragData;
+        if (dragData && this.isContentActive() && !this.props.dontRegisterView) {
+            dragData.dropAction = dropAction ? dropAction : dragData.dropAction;
             e.stopPropagation();
         }
     };
     setupHandlers() {
         this.cleanupHandlers(false);
         if (this._mainCont.current) {
-            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.Document, this.preDropFunc);
+            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.Document, this.preDrop);
         }
     }
 
@@ -311,7 +311,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
             let stopPropagate = true;
             let preventDefault = true;
-            !this.layoutDoc._keepZWhenDragged && this._props.bringToFront(this.Document);
+            !this.layoutDoc._keepZWhenDragged && this._props.bringToFront?.(this.Document);
             if (this._doubleTap) {
                 const defaultDblclick = this._props.defaultDoubleClick?.() || this.Document.defaultDoubleClick;
                 if (this.onDoubleClickHandler?.script) {
@@ -375,7 +375,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 const sendToBack = e.altKey;
                 this._singleClickFunc =
                     // prettier-ignore
-                    clickFunc ?? (() => (sendToBack ? documentView._props.bringToFront(this.Document, true) : 
+                    clickFunc ?? (() => (sendToBack ? documentView._props.bringToFront?.(this.Document, true) : 
                                                       this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? 
                                         this._props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
                 const waitFordblclick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
@@ -610,40 +610,38 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
             !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' });
 
-            if (!Doc.IsSystem(this.Document) && this.Document.type !== DocumentType.PRES && ![CollectionViewType.Docking, CollectionViewType.Tree].includes(this.Document._type_collection as any)) {
+            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: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
+                    event: undoBatch(action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged))),
+                    icon: 'hand-point-up',
+                });
+                !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'layer-group' });
+            }
+
+            if (!Doc.IsSystem(this.Document) && !this.Document.hideClickBehaviors && !this._props.hideClickBehaviors) {
                 const existingOnClick = cm.findByDescription('OnClick...');
                 const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
 
-                if (this._props.bringToFront !== emptyFunction) {
-                    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: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
-                        event: undoBatch(action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged))),
-                        icon: 'hand-point-up',
-                    });
-                    !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'layer-group' });
-                }
-
                 onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
                 !Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
 
-                if (!this._props.treeViewDoc) {
-                    if (!this.Document.annotationOn) {
-                        const options = cm.findByDescription('Options...');
-                        const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
-                        !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
-
-                        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) {
-                        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' });
-                    }
+                if (!this.Document.annotationOn) {
+                    const options = cm.findByDescription('Options...');
+                    const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
+                    !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
+
+                    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) {
+                    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' });
                 }
             }
 
@@ -727,8 +725,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/dataViz/';
                 break;
         }
-        // Add link to help documentation
-        if (!this._props.treeViewDoc && documentationDescription && documentationLink) {
+        // Add link to help documentation (unless the doc contents have been overriden in which case the documentation isn't relevant)
+        if (!this.docContents && documentationDescription && documentationLink) {
             helpItems.push({
                 description: documentationDescription,
                 event: () => window.open(documentationLink, '_blank'),
@@ -996,12 +994,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         TraceMobx();
         const highlighting = this.highlighting;
         const borderPath = this.borderPath;
-        const boxShadow =
-            this._props.treeViewDoc || !highlighting
-                ? this.boxShadow
-                : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
-                  ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
-                  : this.boxShadow || (this.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
+        const boxShadow = !highlighting
+            ? this.boxShadow
+            : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
+              ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
+              : this.boxShadow || (this.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
         const renderDoc = this.renderDoc({
             borderRadius: this.borderRounding,
             outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px',
@@ -1219,7 +1216,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     }
 
     @computed get getBounds() {
-        if (!this._docViewInternal?._contentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
+        if (!this._docViewInternal?._contentDiv || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
             return undefined;
         }
         if (this._docViewInternal._componentView?.screenBounds?.()) {
@@ -1282,7 +1279,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
             const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
             this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished);
             this.Document.deiconifyLayout = undefined;
-            this._props.bringToFront(this.Document);
+            this._props.bringToFront?.(this.Document);
         }
     }
 
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 5bb295565..3cf0fabc2 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -54,6 +54,7 @@ export interface FieldViewSharedProps {
     forceAutoHeight?: boolean;
     ignoreAutoHeight?: boolean;
     disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
+    hideClickBehaviors?: boolean; // whether to suppress menu item options for changing click behaviors
     CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
     containerViewPath?: () => DocumentView[];
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
@@ -86,7 +87,7 @@ export interface FieldViewSharedProps {
     moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean;
     pinToPres: (document: Doc, pinProps: PinProps) => void;
     ScreenToLocalTransform: () => Transform;
-    bringToFront: (doc: Doc, sendToBack?: boolean) => void;
+    bringToFront?: (doc: Doc, sendToBack?: boolean) => void;
     waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
     defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
     pointerEvents?: () => Opt<string>;
@@ -108,7 +109,6 @@ export interface FieldViewProps extends FieldViewSharedProps {
     // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields
     // Also, see InkingStroke for examples of creating text boxes from render() methods which set some of these fields
     backgroundColor?: string;
-    treeViewDoc?: Doc;
     color?: string;
     height?: number;
     width?: number;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 2a10bd766..923aead64 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -227,7 +227,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
             this._props.addDocTab(cropping, OpenWhere.inParent);
         }
         DocumentManager.Instance.AddViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200));
-        this._props.bringToFront(cropping);
+        this._props.bringToFront?.(cropping);
         return cropping;
     };
 
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 7c532f33f..f9e8ce4f3 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -114,7 +114,6 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
                                 isSelected: returnFalse,
                                 setHeight: returnFalse,
                                 select: emptyFunction,
-                                bringToFront: emptyFunction,
                                 renderDepth: 1,
                                 isContentActive: returnFalse,
                                 whenChildContentsActiveChanged: emptyFunction,
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 2d54da0ed..4b242649a 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -293,7 +293,6 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
                             focus={emptyFunction}
                             whenChildContentsActiveChanged={returnFalse}
                             ignoreAutoHeight={true} // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size.
-                            bringToFront={returnFalse}
                             NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
                             NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
                         />
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 0eb3ffc3d..1274220b6 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -134,7 +134,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
         }
-        this._props.bringToFront(cropping);
+        this._props.bringToFront?.(cropping);
 
         CreateImage(
             '',
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index a6c524881..40647feff 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -855,7 +855,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
                     renderDepth={this._props.renderDepth + 1}
                     startTag={'_timecodeToShow' /* videoStart */}
                     endTag={'_timecodeToHide' /* videoEnd */}
-                    bringToFront={emptyFunction}
                     playFrom={this.playFrom}
                     setTime={this.setPlayheadTime}
                     playing={this.playing}
@@ -928,7 +927,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
         }
-        this._props.bringToFront(cropping);
+        this._props.bringToFront?.(cropping);
         return cropping;
     };
     savedAnnotations = () => this._savedAnnotations;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 5776cf6a6..5a07540da 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -983,7 +983,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
             childFilters={childFilters}
             select={emptyFunction}
             isAnyChildContentActive={returnFalse}
-            bringToFront={emptyFunction}
             styleProvider={this.childStyleProvider}
             whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
             removeDocument={this.removeDocument}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index bc49ed23d..7335c9286 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -217,7 +217,6 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
                     PanelHeight={this._dashDoc[Height]}
                     focus={this.outerFocus}
                     whenChildContentsActiveChanged={this._props.tbox.whenChildContentsActiveChanged}
-                    bringToFront={emptyFunction}
                     dontRegisterView={false}
                     childFilters={this._props.tbox?._props.childFilters}
                     childFiltersByRanges={this._props.tbox?._props.childFiltersByRanges}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 38400d2e7..731ab1d53 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -89,6 +89,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _editorView: Opt<EditorView>;
     public _applyingChange: string = '';
+    private _inDrop = false;
     private _finishingLink = false;
     private _searchIndex = 0;
     private _lastTimedMark: Mark | undefined = undefined;
@@ -355,7 +356,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
                     if ((!prevData && !protoData) || newText || (!newText && !templateData)) {
                         // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
-                        if ((this._finishingLink || this._props.isContentActive()) && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
+                        if ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
                             const numstring = NumCast(dataDoc[this.fieldKey], null);
                             dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText);
                             dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited
@@ -571,8 +572,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         if (dragData) {
             const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
             const effectiveAcl = GetEffectiveAcl(dataDoc);
-            const draggedDoc = dragData.draggedDocuments.lastElement();
+            const draggedDoc = dragData.droppedDocuments.lastElement();
             let added: Opt<boolean>;
+            const dropAction = dragData.dropAction || dragData.userDropAction;
             if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {
                 // replace text contents when dragging with Alt
                 if (de.altKey) {
@@ -582,7 +584,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     }
 
                     // embed document when drag marked as embed
-                } else if (de.embedKey) {
+                } else if (de.embedKey || dropAction) {
                     const node = schema.nodes.dashDoc.create({
                         width: NumCast(draggedDoc._width),
                         height: NumCast(draggedDoc._height),
@@ -590,20 +592,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         docId: draggedDoc[Id],
                         float: 'unset',
                     });
-                    if (!['embed', 'copy'].includes((dragData.dropAction ?? '') as any)) {
+                    if (!['embed', 'copy'].includes((dropAction ?? '') as any)) {
                         added = dragData.removeDocument?.(draggedDoc) ? true : false;
+                    } else {
+                        added = true;
                     }
                     if (added) {
                         draggedDoc._freeform_fitContentsToBox = true;
                         Doc.SetContainer(draggedDoc, this.Document);
                         const view = this._editorView!;
                         try {
+                            this._inDrop = true;
                             const pos = view.posAtCoords({ left: de.x, top: de.y })?.pos;
                             pos && view.dispatch(view.state.tr.insert(pos, node));
                             added = pos ? true : false; // pos will be null if you don't drop onto an actual text location
                         } catch (e) {
                             console.log('Drop failed', e);
                             added = false;
+                        } finally {
+                            this._inDrop = false;
                         }
                     }
                 }
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 9e5ea9524..667bc2f5c 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -2607,6 +2607,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 ignoreUnrendered={true}
                                 childDragAction="move"
                                 setContentViewBox={emptyFunction}
+                                hideClickBehaviors={true}
                                 //childLayoutFitWidth={returnTrue}
                                 childOpacity={returnOne}
                                 childClickScript={PresBox.navigateToDocScript}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 7e68711fa..5b2aa1cde 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -128,7 +128,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     whenChildContentsActiveChanged={returnFalse}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
-                    bringToFront={returnFalse}
                 />
             </div>
         );
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index ba7286111..0d4cfda88 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -535,7 +535,6 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 isAnnotationOverlayScrollable={true}
                 childFilters={childFilters}
                 select={emptyFunction}
-                bringToFront={emptyFunction}
                 styleProvider={this.childStyleProvider}
             />
         </div>
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 5828313c8..4155800b8 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -104,7 +104,6 @@ export class TopBar extends React.Component {
                     fieldKey="data"
                     dropAction="embed"
                     styleProvider={DefaultStyleProvider}
-                    bringToFront={emptyFunction}
                     select={emptyFunction}
                     isContentActive={returnTrue}
                     isAnyChildContentActive={returnFalse}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 9d68ea731..c31e73b42 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -398,7 +398,6 @@ export class MobileInterface extends React.Component {
                     styleProvider={this.whitebackground}
                     containerViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
-                    bringToFront={emptyFunction}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-- 
cgit v1.2.3-70-g09d2


From e3709b4445732791f696cdf26274ab09294ce208 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 27 Jan 2024 04:21:08 -0500
Subject: made dataViz nodes linked to schema nodes update automatically as
 cahnges are made.  fixed user created templates from disappearing from menu,
 and made them work.  added toJavascriptString and made DashField views
 convert to text.  added support for turning text into javascript rendering
 (paint) code.

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/CurrentUserUtils.ts                | 12 +++--
 src/client/util/DropConverter.ts                   |  1 +
 src/client/util/UndoManager.ts                     |  2 +-
 src/client/views/ContextMenuItem.tsx               | 38 ++++++-------
 src/client/views/DocComponent.tsx                  |  6 +--
 src/client/views/DocumentDecorations.tsx           | 16 ++++--
 src/client/views/EditableView.tsx                  |  9 ++--
 .../collectionFreeForm/CollectionFreeFormView.scss | 48 +++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 21 +++++++-
 .../collectionSchema/CollectionSchemaView.tsx      |  3 +-
 .../views/global/globalCssVariables.module.scss    |  2 +
 .../global/globalCssVariables.module.scss.d.ts     |  1 +
 src/client/views/linking/LinkMenuItem.tsx          |  2 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 63 +++++++++++-----------
 .../views/nodes/DataVizBox/components/Chart.scss   |  1 -
 src/client/views/nodes/DocumentView.tsx            |  6 +--
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  8 ++-
 .../views/nodes/formattedText/DashFieldView.tsx    | 13 +++--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 34 ++++++++++--
 src/client/views/nodes/formattedText/nodes_rts.ts  |  3 ++
 src/fields/CursorField.ts                          |  5 +-
 src/fields/DateField.ts                            |  5 +-
 src/fields/Doc.ts                                  | 39 +++++++++-----
 src/fields/FieldSymbols.ts                         |  1 +
 src/fields/HtmlField.ts                            | 15 +++---
 src/fields/IconField.ts                            | 17 +++---
 src/fields/InkField.ts                             |  5 +-
 src/fields/List.ts                                 |  5 +-
 src/fields/ObjectField.ts                          |  3 +-
 src/fields/Proxy.ts                                |  5 +-
 src/fields/RefField.ts                             | 11 ++--
 src/fields/RichTextField.ts                        |  5 +-
 src/fields/SchemaHeaderField.ts                    |  5 +-
 src/fields/ScriptField.ts                          |  5 +-
 src/fields/URLField.ts                             |  8 ++-
 36 files changed, 273 insertions(+), 152 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ee3bf0d82..f1a0b37b3 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -211,7 +211,7 @@ export class DocumentOptions {
     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);
-    _embedContainer?: DOCt = new DocInfo('document that displays (contains) this discument', false);
+    _embedContainer?: DOCt = new DocInfo('document that displays (contains) this document', false);
     rootDocument?: DOCt = new DocInfo('document that supplies the information needed for a rendering template (eg, pres slide for PresElement)');
     color?: STRt = new StrInfo('foreground color data doc', false);
     hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection', false);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6ddb65941..8f46f844c 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -61,7 +61,7 @@ export let resolvedPorts: { server: number, socket: number };
 export class CurrentUserUtils {
 
     // initializes experimental advanced template views - slideView,  headerView
-    static setupExperimentalTemplateButtons(doc: Doc, tempDocs?:Doc) {
+    static setupExperimentalTemplateButtons(doc: Doc, tempDocs:Opt<Doc>, userBtns:Doc[]) {
         const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [
             {
                 btnOpts: { title: "slide", icon: "address-card" }, 
@@ -83,7 +83,7 @@ export class CurrentUserUtils {
                 )
             },
         ];
-        const requiredTypes = requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => {
+        const requiredTypes = [...requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => {
             const tempBtn =  DocListCast(tempDocs?.data)?.find(doc => doc.title === btnOpts.title);
             const reqdScripts = { onDragStart:  '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }' };
             const assignBtnAndTempOpts = (templateBtn:Opt<Doc>, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => {
@@ -94,7 +94,7 @@ export class CurrentUserUtils {
                 return templateBtn;
             };
             return DocUtils.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: MakeTemplate(template(templateOpts))}), reqdScripts);
-        });
+        }), ...userBtns];
 
         const reqdOpts:DocumentOptions = {
             title: "Experimental Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true,
@@ -201,7 +201,7 @@ export class CurrentUserUtils {
             makeIconTemplate(DocumentType.AUDIO, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "lightgreen"}),
             makeIconTemplate(DocumentType.PDF,   "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "pink"}),
             makeIconTemplate(DocumentType.WEB,   "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "brown"}),
-            makeIconTemplate(DocumentType.RTF,   "text",  { iconTemplate:DocumentType.LABEL, _layout_showTitle: "author_date"}),
+            makeIconTemplate(DocumentType.RTF,   "text",  { iconTemplate:DocumentType.LABEL, _layout_showTitle: "title"}),
             makeIconTemplate(DocumentType.IMG,   "data",  { iconTemplate:DocumentType.IMG,   _height: undefined}),
             makeIconTemplate(DocumentType.COL,   "icon",  { iconTemplate:DocumentType.IMG}),
             makeIconTemplate(DocumentType.COL,   "icon",  { iconTemplate:DocumentType.IMG}),
@@ -465,7 +465,9 @@ export class CurrentUserUtils {
     static setupToolsBtnPanel(doc: Doc, field:string) {
         const myTools = DocCast(doc[field]);
         const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, DocListCast(myTools?.data)?.length ? DocListCast(myTools.data)[0]:undefined);
-        const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc,DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined);
+        const tempBtns = DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined;
+        const userTemplateBtns = DocListCast(tempBtns?.data).filter(btn => !btn.isSystem);
+        const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc, tempBtns, userTemplateBtns);
         const reqdToolOps:DocumentOptions = {
             title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
             layout_explainer: "This is a palette of documents that can be created.",
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index f62ec8f83..8c3b56452 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -80,6 +80,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
                 title: StrCast(layoutDoc.title),
                 btnType: ButtonType.ClickButton,
                 icon: 'bolt',
+                isSystem: false,
             });
             dbox.dragFactory = layoutDoc;
             dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined;
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 9a6719ea5..421855bf3 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -102,7 +102,7 @@ export namespace UndoManager {
                     'UndoEvent : ' +
                     event.prop +
                     ' = ' +
-                    (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toScriptString(val)).join(',') : Field.toScriptString(value))
+                    (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toJavascriptString(val)).join(',') : Field.toJavascriptString(value))
             );
             currentBatch.push(event);
             tempEvents?.push(event);
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 3c9d821a9..b2076e1a5 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -38,16 +38,16 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
 
     componentDidMount() {
         runInAction(() => (this._items.length = 0));
-        if ((this.props as SubmenuProps)?.subitems) {
-            (this.props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i)));
+        if ((this._props as SubmenuProps)?.subitems) {
+            (this._props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i)));
         }
     }
 
     handleEvent = async (e: React.MouseEvent<HTMLDivElement>) => {
-        if ('event' in this.props) {
-            this.props.closeMenu?.();
-            const batch = this.props.undoable !== false ? UndoManager.StartBatch(`Click Menu item: ${this.props.description}`) : undefined;
-            await this.props.event({ x: e.clientX, y: e.clientY });
+        if ('event' in this._props) {
+            this._props.closeMenu?.();
+            const batch = this._props.undoable !== false ? UndoManager.StartBatch(`Click Menu item: ${this._props.description}`) : undefined;
+            await this._props.event({ x: e.clientX, y: e.clientY });
             batch?.end();
         }
     };
@@ -91,11 +91,11 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
     }
 
     render() {
-        if ('event' in this.props) {
+        if ('event' in this._props) {
             return (
-                <div className={'contextMenu-item' + (this.props.selected ? ' contextMenu-itemSelected' : '')} onPointerDown={this.handleEvent}>
-                    {this.props.icon ? <span className="contextMenu-item-icon-background">{this.isJSXElement(this.props.icon) ? this.props.icon : <FontAwesomeIcon icon={this.props.icon} size="sm" />}</span> : null}
-                    <div className="contextMenu-description">{this.props.description.replace(':', '')}</div>
+                <div className={'contextMenu-item' + (this._props.selected ? ' contextMenu-itemSelected' : '')} onPointerDown={this.handleEvent}>
+                    {this._props.icon ? <span className="contextMenu-item-icon-background">{this.isJSXElement(this._props.icon) ? this._props.icon : <FontAwesomeIcon icon={this._props.icon} size="sm" />}</span> : null}
+                    <div className="contextMenu-description">{this._props.description.replace(':', '')}</div>
                     <div
                         className={`contextMenu-item-background`}
                         style={{
@@ -104,7 +104,7 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
                     />
                 </div>
             );
-        } else if ('subitems' in this.props) {
+        } else if ('subitems' in this._props) {
             const where = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center';
             const marginTop = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : '';
 
@@ -118,32 +118,32 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
                         background: SettingsManager.userBackgroundColor,
                     }}>
                     {this._items.map(prop => (
-                        <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />
+                        <ContextMenuItem {...prop} key={prop.description} closeMenu={this._props.closeMenu} />
                     ))}
                 </div>
             );
-            if (!(this.props as SubmenuProps).noexpand) {
+            if (!(this._props as SubmenuProps).noexpand) {
                 return (
                     <div className="contextMenu-inlineMenu">
                         {this._items.map(prop => (
-                            <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />
+                            <ContextMenuItem {...prop} key={prop.description} closeMenu={this._props.closeMenu} />
                         ))}
                     </div>
                 );
             }
             return (
                 <div
-                    className={'contextMenu-item' + (this.props.selected ? ' contextMenu-itemSelected' : '')}
-                    style={{ alignItems: where, borderTop: this.props.addDivider ? 'solid 1px' : undefined }}
+                    className={'contextMenu-item' + (this._props.selected ? ' contextMenu-itemSelected' : '')}
+                    style={{ alignItems: where, borderTop: this._props.addDivider ? 'solid 1px' : undefined }}
                     onMouseLeave={this.onPointerLeave}
                     onMouseEnter={this.onPointerEnter}>
-                    {this.props.icon ? (
+                    {this._props.icon ? (
                         <span className="contextMenu-item-icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: 'center', alignSelf: 'center' }}>
-                            <FontAwesomeIcon icon={this.props.icon} size="sm" />
+                            <FontAwesomeIcon icon={this._props.icon} size="sm" />
                         </span>
                     ) : null}
                     <div className="contextMenu-description" onMouseEnter={this.onPointerEnter} style={{ alignItems: 'center', alignSelf: 'center' }}>
-                        {this.props.description}
+                        {this._props.description}
                         <FontAwesomeIcon icon={'angle-right'} size="lg" style={{ position: 'absolute', right: '10px' }} />
                     </div>
                     <div
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index b21b13e4c..73fa6709c 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -188,8 +188,8 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
                 const recent = this.Document !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined;
                 toRemove.forEach(doc => {
                     leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
-                    Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
-                    Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', doc);
+                    Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc, true);
+                    Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', doc, true);
                     doc.embedContainer = undefined;
                     if (recent && !dontAddToRemoved) {
                         doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
@@ -243,7 +243,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
                         inheritParentAcls(targetDataDoc, doc, true);
                     });
 
-                    const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
+                    const annoDocs = Doc.Get(targetDataDoc, annotationKey ?? this.annotationKey, true) as List<Doc>; // get the dataDoc directly ... when using templates there may be some default items already there, but we can't change them.  maybe we should copy them over, though...
                     if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add)));
                     else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
                     targetDataDoc[(annotationKey ?? this.annotationKey) + '_modificationDate'] = new DateField();
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 5ef62b2c5..2193acf62 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -18,7 +18,6 @@ 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 { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
 import { SnappingManager } from '../util/SnappingManager';
@@ -112,7 +111,6 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
 
     @action
     titleBlur = () => {
-        this._editingTitle = false;
         if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) {
             this._titleControlString = this._accumulatedTitle;
         } else if (this._titleControlString.startsWith('#')) {
@@ -616,7 +614,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         if (SelectionManager.Views.length === 1) {
             const selected = SelectionManager.Views[0];
             if (this._titleControlString.startsWith('=')) {
-                return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.Document, this: selected.layoutDoc }, console.log).result?.toString() || '';
+                return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })?.script.run({ self: selected.Document, this: selected.layoutDoc }, console.log).result?.toString() || '';
             }
             if (this._titleControlString.startsWith('#')) {
                 return Field.toString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
@@ -641,7 +639,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const { b, r, x, y } = this.Bounds;
         const seldocview = SelectionManager.Views.lastElement();
         if (SnappingManager.IsDragging || r - x < 1 || x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(r) || isNaN(b) || isNaN(x) || isNaN(y)) {
-            setTimeout(action(() => (this._showNothing = true)));
+            setTimeout(
+                action(() => {
+                    this._editingTitle = false;
+                    this._showNothing = true;
+                })
+            );
             return null;
         }
 
@@ -726,7 +729,10 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 name="dynbox"
                 autoComplete="on"
                 value={hideTitle ? '' : this._accumulatedTitle}
-                onBlur={e => !hideTitle && this.titleBlur()}
+                onBlur={action((e: React.FocusEvent) => {
+                    this._editingTitle = false;
+                    !hideTitle && this.titleBlur();
+                })}
                 onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
                 onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
                 onPointerDown={e => e.stopPropagation()}
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index c6666d79d..4508d00a7 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,4 +1,4 @@
-import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as Autosuggest from 'react-autosuggest';
@@ -97,9 +97,10 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
         super.componentDidUpdate(prevProps);
         if (this._editing && this._props.editing === false) {
             this._inputref?.value && this.finalizeEdit(this._inputref.value, false, true, false);
-        } else if (this._props.editing !== undefined) {
-            this._editing = this._props.editing;
-        }
+        } else
+            runInAction(() => {
+                if (this._props.editing !== undefined) this._editing = this._props.editing;
+            });
     }
 
     componentWillUnmount() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 7d3acaea7..9e7d364ea 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -176,6 +176,11 @@
     // touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions.
     touch-action: none;
     transform-origin: top left;
+    > svg {
+        height: 100%;
+        width: 100%;
+        margin: auto;
+    }
 
     .collectionfreeformview-placeholder {
         background: gray;
@@ -270,34 +275,31 @@
     padding: 10px;
 
     .msg {
-      position: relative;
-      // display: block;
-      -webkit-user-select: none;
-      -khtml-user-select: none;
-      -moz-user-select: none;
-      -o-user-select: none;
-      user-select: none;
-      
+        position: relative;
+        // display: block;
+        -webkit-user-select: none;
+        -khtml-user-select: none;
+        -moz-user-select: none;
+        -o-user-select: none;
+        user-select: none;
     }
-    
+
     .gif-container {
-      position: relative;
-      margin-top: 5px;
-      // display: block;
-
-      justify-content: center;
-      align-items: center;
-      -webkit-user-select: none;
-      -khtml-user-select: none;
-      -moz-user-select: none;
-      -o-user-select: none;
-      user-select: none;
-
-      
+        position: relative;
+        margin-top: 5px;
+        // display: block;
+
+        justify-content: center;
+        align-items: center;
+        -webkit-user-select: none;
+        -khtml-user-select: none;
+        -moz-user-select: none;
+        -o-user-select: none;
+        user-select: none;
+
         .gif {
             background-color: transparent;
             height: 300px;
         }
     }
-
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 53dc389b4..be19fc50f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -39,7 +39,7 @@ import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
 import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
 import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
-import { FocusViewOptions, FieldViewProps } from '../../nodes/FieldView';
+import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../../nodes/trails/PresBox';
 import { CreateImage } from '../../nodes/WebBoxRenderer';
@@ -73,6 +73,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return 'CollectionFreeFormView(' + this.Document.title?.toString() + ')';
     } // this makes mobx trace() statements more descriptive
 
+    @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
+    @computed get paintFunc() {
+        const paintFunc = StrCast(this.Document[DocData].paintFunc).trim();
+        return !paintFunc
+            ? ''
+            : `const dashDiv = document.querySelector('#${this._paintedId}'); 
+        (async () => {  ${paintFunc} })()`;
+    }
     constructor(props: any) {
         super(props);
         makeObservable(this);
@@ -1227,6 +1235,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 onKey={this.onKeyDown}
                 onDoubleClickScript={this.onChildDoubleClickHandler}
                 onBrowseClickScript={this.onBrowseClickHandler}
+                bringToFront={this.bringToFront}
                 ScreenToLocalTransform={childLayout.z ? this.ScreenToLocalBoxXf : this.ScreenToContentsXf}
                 PanelWidth={childLayout[Width]}
                 PanelHeight={childLayout[Height]}
@@ -1482,6 +1491,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 );
             })
         );
+
+        this._disposers.paintFunc = reaction(
+            () => ({ code: this.paintFunc, first: this._firstRender, width: this.Document._width, height: this.Document._height }),
+            ({ code, first }) => code && !first && eval(code),
+            { fireImmediately: true }
+        );
+
         this._disposers.layoutElements = reaction(
             // layoutElements can't be a computed value because doLayoutComputation() is an action that has side effect of updating clusters
             () => this.doInternalLayoutComputation,
@@ -1847,6 +1863,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return (
             <div
                 className="collectionfreeformview-container"
+                id={this._paintedId}
                 ref={r => {
                     this.createDashEventsTarget(r);
                     this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
@@ -1868,7 +1885,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     width: `${100 / (this.nativeDimScaling || 1)}%`,
                     height: this._props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
                 }}>
-                {this._lightboxDoc ? (
+                {this.paintFunc ? null : this._lightboxDoc ? (
                     <div style={{ padding: 15, width: '100%', height: '100%' }}>
                         <DocumentView
                             {...this._props}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 581425d77..31b4a2dd4 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -25,6 +25,7 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
+const { default: { SCHEMA_NEW_NODE_HEIGHT } } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
 
 export enum ColumnType {
     Number,
@@ -69,7 +70,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     public static _minColWidth: number = 25;
     public static _rowMenuWidth: number = 60;
     public static _previewDividerWidth: number = 4;
-    public static _newNodeInputHeight: number = 20;
+    public static _newNodeInputHeight: number = Number(SCHEMA_NEW_NODE_HEIGHT);
     public fieldInfos = new ObservableMap<string, FInfo>();
 
     @observable _menuKeys: string[] = [];
diff --git a/src/client/views/global/globalCssVariables.module.scss b/src/client/views/global/globalCssVariables.module.scss
index 44e8efe23..ad0c5c21d 100644
--- a/src/client/views/global/globalCssVariables.module.scss
+++ b/src/client/views/global/globalCssVariables.module.scss
@@ -64,6 +64,7 @@ $mainTextInput-zindex: 999; // then text input overlay so that it's context menu
 $docDecorations-zindex: 998; // then doc decorations appear over everything else
 $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
 $SCHEMA_DIVIDER_WIDTH: 4;
+$SCHEMA_NEW_NODE_HEIGHT: 30;
 $MINIMIZED_ICON_SIZE: 24;
 $MAX_ROW_HEIGHT: 44px;
 $DFLT_IMAGE_NATIVE_DIM: 900px;
@@ -78,6 +79,7 @@ $DATA_VIZ_TABLE_ROW_HEIGHT: 30;
 
 :export {
     contextMenuZindex: $contextMenu-zindex;
+    SCHEMA_NEW_NODE_HEIGHT: $SCHEMA_NEW_NODE_HEIGHT;
     SCHEMA_DIVIDER_WIDTH: $SCHEMA_DIVIDER_WIDTH;
     MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
     MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
diff --git a/src/client/views/global/globalCssVariables.module.scss.d.ts b/src/client/views/global/globalCssVariables.module.scss.d.ts
index bcbb1f068..12cc3fc89 100644
--- a/src/client/views/global/globalCssVariables.module.scss.d.ts
+++ b/src/client/views/global/globalCssVariables.module.scss.d.ts
@@ -1,5 +1,6 @@
 interface IGlobalScss {
     contextMenuZindex: string; // context menu shows up over everything
+    SCHEMA_NEW_NODE_HEIGHT: string;
     SCHEMA_DIVIDER_WIDTH: string;
     MINIMIZED_ICON_SIZE: string;
     MAX_ROW_HEIGHT: string;
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index dc4aee1ca..ad6deeefb 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -136,7 +136,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
 
     deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this._props.linkDoc))));
     @observable _hover = false;
-    docView = () => this.props.docView;
+    docView = () => this._props.docView;
     render() {
         const destinationIcon = Doc.toIcon(this._props.destinationDoc) as any as IconProp;
 
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 8365f4770..e453bcee0 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
-import { ObservableMap, action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
@@ -40,9 +40,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+    private _disposers: { [name: string]: IReactionDisposer } = {};
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
     crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
-    @observable _schemaDataVizChildren: any = undefined;
     @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
 
@@ -256,12 +256,39 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
     componentDidMount() {
         this._props.setContentViewBox?.(this);
         if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
+        this._disposers.datavis = reaction(
+            () => {
+                const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
+                const keys = Cast(getFrom?.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
+                if (!keys) return;
+                const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]);
+                var current: { [key: string]: string }[] = [];
+                children
+                    .filter(child => child)
+                    .forEach(child => {
+                        const row: { [key: string]: string } = {};
+                        keys.forEach(key => {
+                            var cell = child[key];
+                            if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+                            row[key] = StrCast(cell);
+                        });
+                        current.push(row);
+                    });
+                return current;
+            },
+            current => {
+                current && DataVizBox.dataset.set(CsvCast(this.Document[this.fieldKey]).url.href, current);
+            },
+            { fireImmediately: true }
+        );
     }
 
     fetchData = () => {
-        DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
-        fetch('/csvData?uri=' + this.dataUrl?.url.href) //
-            .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, res))));
+        if (!this.Document.dataViz_asSchema) {
+            DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
+            fetch('/csvData?uri=' + this.dataUrl?.url.href) //
+                .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, res))));
+        }
     };
 
     // toggles for user to decide which chart type to view the data in
@@ -327,33 +354,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() im
         GPTPopup.Instance.addDoc = this.sidebarAddDocument;
     };
 
-    @action
-    updateSchemaViz = () => {
-        const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
-        const keys = Cast(getFrom.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
-        if (!keys) return;
-        const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]);
-        var current: { [key: string]: string }[] = [];
-        for (let i = 0; i < children.length; i++) {
-            var row: { [key: string]: string } = {};
-            if (children[i]) {
-                for (let j = 0; j < keys.length; j++) {
-                    var cell = children[i][keys[j]];
-                    if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
-                    row[keys[j]] = StrCast(cell);
-                }
-            }
-            current.push(row);
-        }
-        DataVizBox.dataset.set(CsvCast(this.Document[this.fieldKey]).url.href, current);
-    };
-
     render() {
-        if (this.layoutDoc && this.layoutDoc.dataViz_asSchema) {
-            this._schemaDataVizChildren = DocListCast(DocCast(this.layoutDoc.dataViz_asSchema)[Doc.LayoutFieldKey(DocCast(this.layoutDoc.dataViz_asSchema))]).length;
-            this.updateSchemaViz();
-        }
-
         const scale = this._props.NativeDimScaling?.() || 1;
         return !this.records.length ? (
             // displays how to get data into the DataVizBox if its empty
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index 2f7dd0487..116a45623 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -93,7 +93,6 @@
     display: flex;
     flex-direction: column;
     cursor: default;
-    margin-top: 30px;
     height: calc(100% - 40px); // bcz: hack 40px is the size of the button rows
     .tableBox-container {
         overflow: scroll;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a82580ddb..2e1ba2a7e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -592,13 +592,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 !Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
 
                 if (!this.Document.annotationOn) {
-                    const options = cm.findByDescription('Options...');
-                    const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
-                    !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
-
                     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' });
+                    cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
                 } else if (LinkManager.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' });
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index cf07d98be..8290e102c 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -73,7 +73,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
     };
 
     specificContextMenu = (): void => {
-        if (!Doc.noviceMode) {
+        if (!Doc.noviceMode && Cast(this.layoutDoc.dragFactory, Doc, null)) {
             const cm = ContextMenu.Instance;
             cm.addItem({ description: 'Show Template', event: this.showTemplate, icon: 'tag' });
             cm.addItem({ description: 'Use as Render Template', event: this.dragAsTemplate, icon: 'tag' });
@@ -366,7 +366,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         );
     }
 
-    render() {
+    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 });
@@ -389,5 +389,9 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                         background={SettingsManager.userBackgroundColor} size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />; 
         }
         return this.defaultButton;
+    };
+
+    render() {
+        return <div onContextMenu={this.specificContextMenu}>{this.renderButton()}</div>;
     }
 }
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index dc9914637..18286267a 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,10 +1,10 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
-import { Doc } from '../../../../fields/Doc';
+import { Doc, Field } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
@@ -114,6 +114,13 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
         }
     }
 
+    componentDidMount() {
+        this._reactionDisposer = reaction(
+            () => (this._dashDoc ? Field.toKeyValueString(this._dashDoc, this._props.fieldKey) : undefined),
+            keyvalue => keyvalue && this._props.tbox.tryUpdateDoc(true)
+        );
+    }
+
     componentWillUnmount() {
         this._reactionDisposer?.();
     }
@@ -184,7 +191,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                 }}>
                 {this._props.hideKey ? null : (
                     <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
-                        {this._fieldKey}
+                        {(this._textBoxDoc === this._dashDoc ? '' : this._dashDoc?.title + ':') + this._fieldKey}
                     </span>
                 )}
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 731ab1d53..6f9c2893e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -67,6 +67,7 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
 import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
+import { CollectionView } from '../../collections/CollectionView';
 // import * as applyDevTools from 'prosemirror-dev-tools';
 @observer
 export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -325,13 +326,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         } catch (e) {}
     };
 
+    leafText = (node: Node) => {
+        if (node.type === this._editorView?.state.schema.nodes.dashField) {
+            const refDoc = !node.attrs.docId ? this.Document : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
+            return Field.toJavascriptString(refDoc[node.attrs.fieldKey as string] as Field);
+        }
+        return '';
+    };
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView && (this._editorView as any).docView) {
             const state = this._editorView.state.apply(tx);
             this._editorView.updateState(state);
+            this.tryUpdateDoc(false);
+        }
+    };
 
+    tryUpdateDoc = (force: boolean) => {
+        if (this._editorView && (this._editorView as any).docView) {
+            const state = this._editorView.state;
             const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
-            const newText = state.doc.textBetween(0, state.doc.content.size, ' \n');
+            const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText);
             const newJson = JSON.stringify(state.toJSON());
             const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
             const templateData = this.Document !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template
@@ -350,13 +364,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 dataDoc.tags = accumTags.length ? new List<string>(Array.from(new Set<string>(accumTags))) : undefined;
 
                 let unchanged = true;
-                if (this._applyingChange !== this.fieldKey && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
+                if (this._applyingChange !== this.fieldKey && (force || removeSelection(newJson) !== removeSelection(prevData?.Data))) {
                     this._applyingChange = this.fieldKey;
                     const textChange = newText !== prevData?.Text;
                     textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
                     if ((!prevData && !protoData) || newText || (!newText && !templateData)) {
                         // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
-                        if ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
+                        if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data))) {
                             const numstring = NumCast(dataDoc[this.fieldKey], null);
                             dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText);
                             dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited
@@ -480,7 +494,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             var alink: Doc | undefined;
             this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
                 const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
-                if (!sel.$anchor.pos || editorView.state.doc.textBetween(sel.$anchor.pos - 1, sel.$to.pos).trim() === autoLinkTerm) {
+                if (!sel.$anchor.pos || [autoLinkTerm, StrCast(target.title)].includes(editorView.state.doc.textBetween(sel.$anchor.pos - 1, sel.$to.pos).trim())) {
                     tr = tr.addMark(sel.from, sel.to, splitter);
                     tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
                         if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
@@ -919,6 +933,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR),
                 icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars',
             });
+        optionItems.push({
+            description: 'Make Paint Function',
+            event: () => {
+                this.dataDoc.layout_painted = CollectionView.LayoutString('painted');
+                this.layoutDoc.layout_fieldKey = 'layout_painted';
+                this.layoutDoc.type_collection = CollectionViewType.Freeform;
+                this.DocumentView?.().setToggleDetail();
+                this.dataDoc.paintFunc = ComputedField.MakeFunction(`toJavascriptString(this['${this.fieldKey}']?.Text)`);
+            },
+            icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
+        });
         !Doc.noviceMode &&
             optionItems.push({
                 description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`,
@@ -1682,7 +1707,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         FormattedTextBox.LiveTextUndo = undefined;
 
         const state = this._editorView!.state;
-        const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
         if (StrCast(this.Document.title).startsWith('@') && !this.dataDoc.title_custom) {
             UndoManager.RunInBatch(() => {
                 this.dataDoc.title_custom = true;
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index d023020e1..31f001b11 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -2,6 +2,8 @@ import * as React from 'react';
 import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model';
 import { listItem, orderedList } from 'prosemirror-schema-list';
 import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
+import { DocServer } from '../../../DocServer';
+import { Doc, Field } from '../../../../fields/Doc';
 
 const blockquoteDOM: DOMOutputSpec = ['blockquote', 0],
     hrDOM: DOMOutputSpec = ['hr'],
@@ -264,6 +266,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             hideKey: { default: false },
             editable: { default: true },
         },
+        leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field),
         group: 'inline',
         draggable: false,
         toDOM(node) {
diff --git a/src/fields/CursorField.ts b/src/fields/CursorField.ts
index 84917ae53..870dfbeac 100644
--- a/src/fields/CursorField.ts
+++ b/src/fields/CursorField.ts
@@ -1,6 +1,6 @@
 import { createSimpleSchema, object, serializable } from 'serializr';
 import { Deserializable } from '../client/util/SerializationHelper';
-import { Copy, FieldChanged, ToScriptString, ToString } from './FieldSymbols';
+import { Copy, FieldChanged, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 import { ObjectField } from './ObjectField';
 
 export type CursorPosition = {
@@ -55,6 +55,9 @@ export default class CursorField extends ObjectField {
         return new CursorField(this.data);
     }
 
+    [ToJavascriptString]() {
+        return 'invalid';
+    }
     [ToScriptString]() {
         return 'invalid';
     }
diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts
index 2ea619bd9..1e5222fb6 100644
--- a/src/fields/DateField.ts
+++ b/src/fields/DateField.ts
@@ -1,7 +1,7 @@
 import { Deserializable } from '../client/util/SerializationHelper';
 import { serializable, date } from 'serializr';
 import { ObjectField } from './ObjectField';
-import { Copy, ToScriptString, ToString } from './FieldSymbols';
+import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
 
 @scriptingGlobal
@@ -23,6 +23,9 @@ export class DateField extends ObjectField {
         return `${this.date.toLocaleString()}`;
     }
 
+    [ToJavascriptString]() {
+        return `"${this.date.toISOString()}"`;
+    }
     [ToScriptString]() {
         return `new DateField(new Date("${this.date.toISOString()}"))`;
     }
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ff416bbe7..f4141cf46 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -18,7 +18,7 @@ import {
     DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
     Initializing, Self, SelfProxy, UpdatingFromServer, Width
 } from './DocSymbols'; // prettier-ignore
-import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToScriptString, ToString } from './FieldSymbols';
+import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 import { InkField, InkTool } from './InkField';
 import { List, ListFieldName } from './List';
 import { ObjectField } from './ObjectField';
@@ -52,6 +52,23 @@ export namespace Field {
             default:       return field?.[ToScriptString]?.() ?? 'null';
         } // prettier-ignore
     }
+    export function toJavascriptString(field: Field) {
+        var rawjava = '';
+
+        switch (typeof field) {
+            case 'string':
+            case 'number':
+            case 'boolean':rawjava = String(field);
+                           break;
+            default:       rawjava = field?.[ToJavascriptString]?.() ?? 'null';
+        } // prettier-ignore
+        var script = rawjava;
+        Doc.MyPublishedDocs.forEach(doc => {
+            const regex = new RegExp(`^\\^${doc.title}\\s`, 'm');
+            script = script.replace(regex, Cast(doc.text, RichTextField, null)?.Text ?? '');
+        });
+        return script;
+    }
     export function toString(field: Field) {
         if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field);
         return field?.[ToString]?.() || '';
@@ -287,6 +304,7 @@ export class Doc extends RefField {
     public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
     public [Width] = () => NumCast(this[SelfProxy]._width);
     public [Height] = () => NumCast(this[SelfProxy]._height);
+    public [ToJavascriptString] = () => `idToDoc("${this[Self][Id]}")`; // what should go here?
     public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
     public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
     public get [DocLayout]() { return this[SelfProxy].__LAYOUT__; } // prettier-ignore
@@ -508,12 +526,9 @@ export namespace Doc {
      * Removes doc from the list of Docs at listDoc[fieldKey]
      * @returns true if successful, false otherwise.
      */
-    export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc) {
+    export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, ignoreProto = false) {
         const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
-        if (listDoc[key] === undefined) {
-            listDoc[DocData][key] = new List<Doc>();
-        }
-        const list = Cast(listDoc[key], listSpec(Doc));
+        const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
         if (list) {
             const ind = list.indexOf(doc);
             if (ind !== -1) {
@@ -528,12 +543,9 @@ export namespace Doc {
      * Adds doc to the list of Docs stored at listDoc[fieldKey].
      * @returns true if successful, false otherwise.
      */
-    export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) {
+    export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean, ignoreProto?: boolean) {
         const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
-        if (listDoc[key] === undefined) {
-            listDoc[DocData][key] = new List<Doc>();
-        }
-        const list = Cast(listDoc[key], listSpec(Doc));
+        const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
         if (list) {
             if (!allowDuplicates) {
                 const pind = list.findIndex(d => d instanceof Doc && d[Id] === doc[Id]);
@@ -1020,7 +1032,7 @@ export namespace Doc {
     //
     export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>): boolean {
         // find the metadata field key that this template field doc will display (indicated by its title)
-        const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '');
+        const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutFieldKey(templateField);
 
         // update the original template to mark it as a template
         templateField.isTemplateForField = metadataFieldKey;
@@ -1640,3 +1652,6 @@ ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: a
 ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) {
     Doc.setDocRangeFilter(container, key, range);
 });
+ScriptingGlobals.add(function toJavascriptString(str: string) {
+    return Field.toJavascriptString(str as Field);
+});
diff --git a/src/fields/FieldSymbols.ts b/src/fields/FieldSymbols.ts
index 0dbeb064b..0c44d2417 100644
--- a/src/fields/FieldSymbols.ts
+++ b/src/fields/FieldSymbols.ts
@@ -5,5 +5,6 @@ export const Parent = Symbol('FieldParent');
 export const Copy = Symbol('FieldCopy');
 export const ToValue = Symbol('FieldToValue');
 export const ToScriptString = Symbol('FieldToScriptString');
+export const ToJavascriptString = Symbol('FieldToJavascriptString');
 export const ToPlainText = Symbol('FieldToPlainText');
 export const ToString = Symbol('FieldToString');
diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts
index 6e8bba977..b67f0f7e9 100644
--- a/src/fields/HtmlField.ts
+++ b/src/fields/HtmlField.ts
@@ -1,9 +1,9 @@
-import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, primitive } from "serializr";
-import { ObjectField } from "./ObjectField";
-import { Copy, ToScriptString, ToString} from "./FieldSymbols";
+import { Deserializable } from '../client/util/SerializationHelper';
+import { serializable, primitive } from 'serializr';
+import { ObjectField } from './ObjectField';
+import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 
-@Deserializable("html")
+@Deserializable('html')
 export class HtmlField extends ObjectField {
     @serializable(primitive())
     readonly html: string;
@@ -17,8 +17,11 @@ export class HtmlField extends ObjectField {
         return new HtmlField(this.html);
     }
 
+    [ToJavascriptString]() {
+        return 'invalid';
+    }
     [ToScriptString]() {
-        return "invalid";
+        return 'invalid';
     }
     [ToString]() {
         return this.html;
diff --git a/src/fields/IconField.ts b/src/fields/IconField.ts
index 76c4ddf1b..4d2badb68 100644
--- a/src/fields/IconField.ts
+++ b/src/fields/IconField.ts
@@ -1,9 +1,9 @@
-import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, primitive } from "serializr";
-import { ObjectField } from "./ObjectField";
-import { Copy, ToScriptString, ToString } from "./FieldSymbols";
+import { Deserializable } from '../client/util/SerializationHelper';
+import { serializable, primitive } from 'serializr';
+import { ObjectField } from './ObjectField';
+import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 
-@Deserializable("icon")
+@Deserializable('icon')
 export class IconField extends ObjectField {
     @serializable(primitive())
     readonly icon: string;
@@ -17,10 +17,13 @@ export class IconField extends ObjectField {
         return new IconField(this.icon);
     }
 
+    [ToJavascriptString]() {
+        return 'invalid';
+    }
     [ToScriptString]() {
-        return "invalid";
+        return 'invalid';
     }
     [ToString]() {
-        return "ICONfield";
+        return 'ICONfield';
     }
 }
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index 22bea3927..b3e01229a 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -2,7 +2,7 @@ import { Bezier } from 'bezier-js';
 import { alias, createSimpleSchema, list, object, serializable } from 'serializr';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { Deserializable } from '../client/util/SerializationHelper';
-import { Copy, ToScriptString, ToString } from './FieldSymbols';
+import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 import { ObjectField } from './ObjectField';
 
 // Helps keep track of the current ink tool in use.
@@ -85,6 +85,9 @@ export class InkField extends ObjectField {
         return new InkField(this.inkData);
     }
 
+    [ToJavascriptString]() {
+        return '[' + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}}`) + ']';
+    }
     [ToScriptString]() {
         return 'new InkField([' + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}}`) + '])';
     }
diff --git a/src/fields/List.ts b/src/fields/List.ts
index b8ad552d2..9458a9611 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -5,7 +5,7 @@ import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { Deserializable, afterDocDeserialize, autoObject } from '../client/util/SerializationHelper';
 import { Field } from './Doc';
 import { FieldTuples, Self, SelfProxy } from './DocSymbols';
-import { Copy, FieldChanged, Parent, ToScriptString, ToString } from './FieldSymbols';
+import { Copy, FieldChanged, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 import { ObjectField } from './ObjectField';
 import { ProxyField } from './Proxy';
 import { RefField } from './RefField';
@@ -310,6 +310,9 @@ class ListImpl<T extends Field> extends ObjectField {
     private [Self] = this;
     private [SelfProxy]: List<Field>; // also used in utils.ts even though it won't be found using find all references
 
+    [ToJavascriptString]() {
+        return `[${(this as any).map((field: any) => Field.toScriptString(field))}]`;
+    }
     [ToScriptString]() {
         return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`;
     }
diff --git a/src/fields/ObjectField.ts b/src/fields/ObjectField.ts
index b5bc2952a..e1b5b036c 100644
--- a/src/fields/ObjectField.ts
+++ b/src/fields/ObjectField.ts
@@ -1,5 +1,5 @@
 import { RefField } from './RefField';
-import { FieldChanged, Parent, Copy, ToScriptString, ToString } from './FieldSymbols';
+import { FieldChanged, Parent, Copy, ToScriptString, ToString, ToJavascriptString } from './FieldSymbols';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { Field } from './Doc';
 
@@ -12,6 +12,7 @@ export abstract class ObjectField {
     public [Parent]?: RefField | ObjectField;
     abstract [Copy](): ObjectField;
 
+    abstract [ToJavascriptString](): string;
     abstract [ToScriptString](): string;
     abstract [ToString](): string;
 }
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index 3a46e3581..820d9b6ff 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -4,7 +4,7 @@ import { DocServer } from '../client/DocServer';
 import { scriptingGlobal } from '../client/util/ScriptingGlobals';
 import { Deserializable } from '../client/util/SerializationHelper';
 import { Field, FieldWaiting, Opt } from './Doc';
-import { Copy, Id, ToScriptString, ToString, ToValue } from './FieldSymbols';
+import { Copy, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols';
 import { ObjectField } from './ObjectField';
 import { RefField } from './RefField';
 
@@ -38,6 +38,9 @@ export class ProxyField<T extends RefField> extends ObjectField {
         return new ProxyField<T>(this.fieldId);
     }
 
+    [ToJavascriptString]() {
+        return Field.toScriptString(this[ToValue](undefined)?.value);
+    }
     [ToScriptString]() {
         return Field.toScriptString(this[ToValue](undefined)?.value); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ?
     }
diff --git a/src/fields/RefField.ts b/src/fields/RefField.ts
index b6ef69750..01828dd14 100644
--- a/src/fields/RefField.ts
+++ b/src/fields/RefField.ts
@@ -1,11 +1,11 @@
-import { serializable, primitive, alias } from "serializr";
-import { Utils } from "../Utils";
-import { Id, HandleUpdate, ToScriptString, ToString } from "./FieldSymbols";
-import { afterDocDeserialize } from "../client/util/SerializationHelper";
+import { serializable, primitive, alias } from 'serializr';
+import { Utils } from '../Utils';
+import { Id, HandleUpdate, ToScriptString, ToString, ToJavascriptString } from './FieldSymbols';
+import { afterDocDeserialize } from '../client/util/SerializationHelper';
 
 export type FieldId = string;
 export abstract class RefField {
-    @serializable(alias("id", primitive({ afterDeserialize: afterDocDeserialize })))
+    @serializable(alias('id', primitive({ afterDeserialize: afterDocDeserialize })))
     private __id: FieldId;
     readonly [Id]: FieldId;
 
@@ -16,6 +16,7 @@ export abstract class RefField {
 
     protected [HandleUpdate]?(diff: any): void | Promise<void>;
 
+    abstract [ToJavascriptString](): string;
     abstract [ToScriptString](): string;
     abstract [ToString](): string;
 }
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index 3e75a071f..50cfab988 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -1,7 +1,7 @@
 import { serializable } from 'serializr';
 import { scriptingGlobal } from '../client/util/ScriptingGlobals';
 import { Deserializable } from '../client/util/SerializationHelper';
-import { Copy, ToScriptString, ToString } from './FieldSymbols';
+import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 import { ObjectField } from './ObjectField';
 
 @scriptingGlobal
@@ -27,6 +27,9 @@ export class RichTextField extends ObjectField {
         return new RichTextField(this.Data, this.Text);
     }
 
+    [ToJavascriptString]() {
+        return '`' + this.Text + '`';
+    }
     [ToScriptString]() {
         return `new RichTextField("${this.Data.replace(/"/g, '\\"')}", "${this.Text}")`;
     }
diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts
index 6dde2e5aa..fb4dc4e5b 100644
--- a/src/fields/SchemaHeaderField.ts
+++ b/src/fields/SchemaHeaderField.ts
@@ -1,7 +1,7 @@
 import { Deserializable } from '../client/util/SerializationHelper';
 import { serializable, primitive } from 'serializr';
 import { ObjectField } from './ObjectField';
-import { Copy, ToScriptString, ToString, FieldChanged } from './FieldSymbols';
+import { Copy, ToScriptString, ToString, FieldChanged, ToJavascriptString } from './FieldSymbols';
 import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { ColumnType } from '../client/views/collections/collectionSchema/CollectionSchemaView';
 
@@ -114,6 +114,9 @@ export class SchemaHeaderField extends ObjectField {
         return new SchemaHeaderField(this.heading, this.color, this.type, this.width, this.desc, this.collapsed);
     }
 
+    [ToJavascriptString]() {
+        return `["${this.heading}","${this.color}",${this.type},${this.width},${this.desc},${this.collapsed}]`;
+    }
     [ToScriptString]() {
         return `schemaHeaderField("${this.heading}","${this.color}",${this.type},${this.width},${this.desc},${this.collapsed})`;
     }
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 62690a9fb..c7fe72ca6 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -6,7 +6,7 @@ import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGloba
 import { autoObject, Deserializable } from '../client/util/SerializationHelper';
 import { numberRange } from '../Utils';
 import { Doc, Field, Opt } from './Doc';
-import { Copy, Id, ToScriptString, ToString, ToValue } from './FieldSymbols';
+import { Copy, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols';
 import { List } from './List';
 import { ObjectField } from './ObjectField';
 import { Cast, StrCast } from './Types';
@@ -113,6 +113,9 @@ export class ScriptField extends ObjectField {
         return `${this.script.originalScript} + ${this.setterscript?.originalScript}`;
     }
 
+    [ToJavascriptString]() {
+        return this.script.originalScript;
+    }
     [ToScriptString]() {
         return this.script.originalScript;
     }
diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts
index 817b62373..87334ad16 100644
--- a/src/fields/URLField.ts
+++ b/src/fields/URLField.ts
@@ -1,7 +1,7 @@
 import { Deserializable } from '../client/util/SerializationHelper';
 import { serializable, custom } from 'serializr';
 import { ObjectField } from './ObjectField';
-import { ToScriptString, ToString, Copy } from './FieldSymbols';
+import { ToScriptString, ToString, Copy, ToJavascriptString } from './FieldSymbols';
 import { scriptingGlobal } from '../client/util/ScriptingGlobals';
 import { Utils } from '../Utils';
 
@@ -36,6 +36,12 @@ export abstract class URLField extends ObjectField {
         }
         return `new ${this.constructor.name}("${this.url?.href}")`;
     }
+    [ToJavascriptString]() {
+        if (Utils.prepend(this.url?.pathname) === this.url?.href) {
+            return `new ${this.constructor.name}("${this.url.pathname}")`;
+        }
+        return `new ${this.constructor.name}("${this.url?.href}")`;
+    }
     [ToString]() {
         if (Utils.prepend(this.url?.pathname) === this.url?.href) {
             return this.url.pathname;
-- 
cgit v1.2.3-70-g09d2


From 0ea81ce3582c739078b86ea3bc1b58bdeb3fe839 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 27 Jan 2024 11:28:23 -0500
Subject: fixed searchBox

---
 src/client/util/SearchUtil.ts                                      | 3 ++-
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx      | 2 +-
 src/client/views/search/SearchBox.tsx                              | 7 ++-----
 3 files changed, 5 insertions(+), 7 deletions(-)

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

diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 2cc64f415..68c981399 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -1,3 +1,4 @@
+import { ObservableMap } from 'mobx';
 import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { StrCast } from '../../fields/Types';
@@ -44,7 +45,7 @@ export namespace SearchUtil {
         ];
         query = query.toLowerCase();
 
-        const results = new Map<Doc, string[]>();
+        const results = new ObservableMap<Doc, string[]>();
         if (collectionDoc) {
             const docs = DocListCast(collectionDoc[Doc.LayoutFieldKey(collectionDoc)]);
             const docIDs: String[] = [];
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index be19fc50f..d19c9f07d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1785,7 +1785,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         );
     }
     get pannableContents() {
-        this.incrementalRender();
+        setTimeout(this.incrementalRender);
         return (
             <CollectionFreeFormPannableContents
                 Document={this.Document}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 5dc4f5550..c8caff9fa 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -55,9 +55,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     @observable _deletedDocsStatus: boolean = false;
     @observable _onlyEmbeddings: boolean = true;
 
-    /**
-     * This is the constructor for the SearchBox class.
-     */
     constructor(props: SearchBoxProps) {
         super(props);
         makeObservable(this);
@@ -69,11 +66,11 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
      * the search panel, the search input box is automatically selected. This allows the user to
      * type in the search input box immediately, without needing clicking on it first.
      */
-    componentDidMount = action(() => {
+    componentDidMount() {
         if (this._inputRef.current) {
             this._inputRef.current.focus();
         }
-    });
+    }
 
     /**
      * This method is called when the SearchBox component is about to be unmounted. When the user
-- 
cgit v1.2.3-70-g09d2


From e17a812f8a8a2c389bf098f7a30c8326881d4dc6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 27 Jan 2024 12:43:20 -0500
Subject: added searchable flag to docoption types. fixed searchbox scrolling
 and showing results. added search by key and exact match.

---
 src/client/documents/Documents.ts                  |   9 ++
 src/client/util/SearchUtil.ts                      |  55 +++-------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   8 +-
 src/client/views/newlightbox/utils.ts              | 121 ++++++++++-----------
 src/client/views/nodes/LinkDocPreview.tsx          |   2 +-
 src/client/views/search/SearchBox.scss             |   1 +
 src/client/views/search/SearchBox.tsx              |  27 +++--
 7 files changed, 104 insertions(+), 119 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f1a0b37b3..ac418ecfe 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -81,6 +81,7 @@ export class FInfo {
         this.description = d;
         this.readOnly = readOnly ?? false;
     }
+    searchable = () => true;
 }
 class BoolInfo extends FInfo {
     fieldType? = 'boolean';
@@ -89,6 +90,7 @@ class BoolInfo extends FInfo {
         super(d);
         this.filterable = filterable;
     }
+    override searchable = () => false;
 }
 class NumInfo extends FInfo {
     fieldType? = 'number';
@@ -98,6 +100,7 @@ class NumInfo extends FInfo {
         this.values = values;
         this.filterable = filterable;
     }
+    override searchable = () => false;
 }
 class StrInfo extends FInfo {
     fieldType? = 'string';
@@ -116,34 +119,40 @@ class DocInfo extends FInfo {
         this.values = values;
         this.filterable = filterable;
     }
+    override searchable = () => false;
 }
 class DimInfo extends FInfo {
     fieldType? = 'enumeration';
     values? = [DimUnit.Pixel, DimUnit.Ratio];
     readOnly = false;
     filterable = false;
+    override searchable = () => false;
 }
 class PEInfo extends FInfo {
     fieldType? = 'enumeration';
     values? = ['all', 'none'];
     readOnly = false;
     filterable = false;
+    override searchable = () => false;
 }
 class DAInfo extends FInfo {
     fieldType? = 'enumeration';
     values? = ['embed', 'copy', 'move', 'same', 'proto', 'none'];
     readOnly = false;
     filterable = false;
+    override searchable = () => false;
 }
 class CTypeInfo extends FInfo {
     fieldType? = 'enumeration';
     values? = Array.from(Object.keys(CollectionViewType));
     readOnly = false;
     filterable = false;
+    override searchable = () => false;
 }
 class DTypeInfo extends FInfo {
     fieldType? = 'enumeration';
     values? = Array.from(Object.keys(DocumentType));
+    override searchable = () => false;
 }
 class DateInfo extends FInfo {
     fieldType? = 'date';
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 68c981399..218667d3e 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -3,47 +3,21 @@ import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { StrCast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
+import { DocOptions, FInfo } from '../documents/Documents';
 
 export namespace SearchUtil {
     export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
 
-    export function SearchCollection(collectionDoc: Opt<Doc>, query: string) {
+    export function SearchCollection(collectionDoc: Opt<Doc>, query: string, matchKeyNames: boolean) {
         const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
-        const blockedKeys = [
-            'x',
-            'y',
-            'proto',
-            'width',
-            'layout_autoHeight',
-            'acl-Override',
-            'acl-Guest',
-            'embedContainer',
-            'zIndex',
-            'height',
-            'text_scrollHeight',
-            'text_height',
-            'cloneFieldFilter',
-            'isDataDoc',
-            'text_annotations',
-            'dragFactory_count',
-            'text_noTemplate',
-            'proto_embeddings',
-            'isSystem',
-            'layout_fieldKey',
-            'isBaseProto',
-            'xMargin',
-            'yMargin',
-            'links',
-            'layout',
-            'layout_keyValue',
-            'layout_fitWidth',
-            'type_collection',
-            'title_custom',
-            'freeform_panX',
-            'freeform_panY',
-            'freeform_scale',
-        ];
-        query = query.toLowerCase();
+        const blockedKeys = matchKeyNames
+            ? []
+            : Object.entries(DocOptions)
+                  .filter(([key, info]: [string, FInfo]) => !info?.searchable())
+                  .map(([key]) => key);
+
+        const exact = query.startsWith('=');
+        query = query.toLowerCase().split('=').lastElement();
 
         const results = new ObservableMap<Doc, string[]>();
         if (collectionDoc) {
@@ -54,11 +28,10 @@ export namespace SearchUtil {
                 if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) {
                     const hlights = new Set<string>();
                     SearchUtil.documentKeys(doc).forEach(
-                        key =>
-                            (Field.toString(doc[key] as Field) + Field.toScriptString(doc[key] as Field))
-                                .toLowerCase() //
-                                .includes(query) && hlights.add(key)
-                    );
+                        key => (val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))(
+                            matchKeyNames ? key : Field.toString(doc[key] as Field)) 
+                        && hlights.add(key)
+                    ); // prettier-ignore
                     blockedKeys.forEach(key => hlights.delete(key));
 
                     if (Array.from(hlights.keys()).length > 0) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d19c9f07d..d3c196a60 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -304,10 +304,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     focus = (anchor: Doc, options: FocusViewOptions) => {
         if (this._lightboxDoc) return;
         if (anchor === this.Document) {
-            if (options.willZoomCentered && options.zoomScale) {
-                this.fitContentOnce();
-                options.didMove = true;
-            }
+            // if (options.willZoomCentered && options.zoomScale) {
+            //     this.fitContentOnce();
+            //     options.didMove = true;
+            // }
         }
         if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return;
         const xfToCollection = options?.docTransform ?? Transform.Identity();
diff --git a/src/client/views/newlightbox/utils.ts b/src/client/views/newlightbox/utils.ts
index 6016abca4..c879718e3 100644
--- a/src/client/views/newlightbox/utils.ts
+++ b/src/client/views/newlightbox/utils.ts
@@ -1,121 +1,120 @@
-import { DocumentType } from "../../documents/DocumentTypes";
-import { IRecommendation } from "./components";
+import { DocumentType } from '../../documents/DocumentTypes';
+import { IRecommendation } from './components';
 
 export interface IDocRequest {
-    id: string,
-    title: string,
-    text: string,
-    type: string
+    id: string;
+    title: string;
+    text: string;
+    type: string;
 }
 
 export const fetchRecommendations = async (src: string, query: string, docs?: IDocRequest[], dummy?: boolean) => {
-    console.log("[rec] making request")
+    console.log('[rec] making request');
     if (dummy) {
         return {
-            "recommendations": dummyRecs,
-            "keywords": dummyKeywords,
-            "num_recommendations": 4,
-            "max_x": 100,
-            "max_y": 100,
-            "min_x": 0,
-            "min_y": 0
-
+            recommendations: [], //dummyRecs,
+            keywords: dummyKeywords,
+            num_recommendations: 4,
+            max_x: 100,
+            max_y: 100,
+            min_x: 0,
+            min_y: 0,
         };
     }
     const response = await fetch('http://127.0.0.1:8000/recommend', {
         method: 'POST',
         headers: {
-            'Accept': 'application/json',
-            'Content-Type': 'application/json'
+            Accept: 'application/json',
+            'Content-Type': 'application/json',
         },
-        body: JSON.stringify({ 
-            "src": src,
-            "query": query,
-            "docs": docs
-        })
-    })
+        body: JSON.stringify({
+            src: src,
+            query: query,
+            docs: docs,
+        }),
+    });
     const data = await response.json();
-    
+
     return data;
-}
+};
 
 export const fetchKeywords = async (text: string, n: number, dummy?: boolean) => {
-    console.log("[fetchKeywords]")
+    console.log('[fetchKeywords]');
     if (dummy) {
         return {
-            "keywords": dummyKeywords
+            keywords: dummyKeywords,
         };
     }
     const response = await fetch('http://127.0.0.1:8000/keywords', {
         method: 'POST',
         headers: {
-            'Accept': 'application/json',
-            'Content-Type': 'application/json'
+            Accept: 'application/json',
+            'Content-Type': 'application/json',
         },
-        body: JSON.stringify({ 
-            "text": text,
-            "n": n
-        })
-    })
-    const data = await response.json()
+        body: JSON.stringify({
+            text: text,
+            n: n,
+        }),
+    });
+    const data = await response.json();
     return data;
-}
+};
 
 export const getType = (type: DocumentType | string) => {
-    switch(type) {
+    switch (type) {
         case DocumentType.AUDIO:
-            return "Audio"
+            return 'Audio';
         case DocumentType.VID:
-            return "Video"
+            return 'Video';
         case DocumentType.PDF:
-            return "PDF"
+            return 'PDF';
         case DocumentType.WEB:
-            return "Webpage"
-        case "YouTube":
-            return "Video"
-        case "HTML":
-            return "Webpage"
+            return 'Webpage';
+        case 'YouTube':
+            return 'Video';
+        case 'HTML':
+            return 'Webpage';
         default:
-            return "Unknown: " + type
+            return 'Unknown: ' + type;
     }
-}
+};
 
 const dummyRecs = {
-    "a": {
+    a: {
         title: 'Vannevar Bush - American Engineer',
-        previewUrl: 'https://cdn.britannica.com/98/23598-004-1E6A382E/Vannevar-Bush-Differential-Analyzer-1935.jpg', 
+        previewUrl: 'https://cdn.britannica.com/98/23598-004-1E6A382E/Vannevar-Bush-Differential-Analyzer-1935.jpg',
         type: 'web',
         distance: 2.3,
         source: 'www.britannica.com',
         related_concepts: ['vannevar bush', 'knowledge'],
         embedding: {
             x: 0,
-            y: 0
-        }
+            y: 0,
+        },
     },
-    "b": {
+    b: {
         title: "From Memex to hypertext: Vannevar Bush and the mind's machine",
         type: 'pdf',
         distance: 5.4,
         source: 'Google Scholar',
         related_concepts: ['memex', 'vannevar bush', 'hypertext'],
     },
-    "c": {
+    c: {
         title: 'How the hyperlink changed everything | Small Thing Big Idea, a TED series',
-        previewUrl: 'https://pi.tedcdn.com/r/talkstar-photos.s3.amazonaws.com/uploads/b17d043f-2642-4117-a913-52204505513f/MargaretGouldStewart_2018V-embed.jpg?u%5Br%5D=2&u%5Bs%5D=0.5&u%5Ba%5D=0.8&u%5Bt%5D=0.03&quality=82w=640', 
+        previewUrl: 'https://pi.tedcdn.com/r/talkstar-photos.s3.amazonaws.com/uploads/b17d043f-2642-4117-a913-52204505513f/MargaretGouldStewart_2018V-embed.jpg?u%5Br%5D=2&u%5Bs%5D=0.5&u%5Ba%5D=0.8&u%5Bt%5D=0.03&quality=82w=640',
         type: 'youtube',
         distance: 5.3,
         source: 'www.youtube.com',
-        related_concepts: ['User Control', 'Explanations']
+        related_concepts: ['User Control', 'Explanations'],
     },
-    "d": {
+    d: {
         title: 'Recommender Systems: Behind the Scenes of Machine Learning-Based Personalization',
-        previewUrl: 'https://sloanreview.mit.edu/wp-content/uploads/2018/10/MAG-Ransbotham-Ratings-Recommendations-1200X627-1200x627.jpg', 
+        previewUrl: 'https://sloanreview.mit.edu/wp-content/uploads/2018/10/MAG-Ransbotham-Ratings-Recommendations-1200X627-1200x627.jpg',
         type: 'pdf',
         distance: 9.3,
         source: 'www.altexsoft.com',
-        related_concepts: ['User Control', 'Explanations']
-    }
-}
+        related_concepts: ['User Control', 'Explanations'],
+    },
+};
 
-const dummyKeywords = ['user control', 'vannevar bush', 'hypermedia', 'hypertext']
\ No newline at end of file
+const dummyKeywords = ['user control', 'vannevar bush', 'hypermedia', 'hypertext'];
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 4b242649a..3f793b85e 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -184,7 +184,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
             LinkFollower.FollowLink(this._linkDoc, this._linkSrc, false);
         } else if (this._props.hrefs?.length) {
             const webDoc =
-                Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0]).keys()).lastElement() ??
+                Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0], false).keys()).lastElement() ??
                 Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
             DocumentManager.Instance.showDocument(webDoc, {
                 openLocation: OpenWhere.lightbox,
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 09e459f7d..94e64b952 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -8,6 +8,7 @@
     background: none;
     z-index: 1000;
     padding: 0px;
+    overflow: auto;
     cursor: default;
 
     .searchBox-bar {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index c8caff9fa..0b664beaa 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -162,12 +162,13 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
      * which the first letter is capitalized. This is used when displaying the type on the
      * right side of each search result.
      */
-    static formatType(type: String): String {
-        if (type === 'pdf') {
-            return 'PDF';
-        } else if (type === 'image') {
-            return 'Img';
-        }
+    static formatType(type: string, colType: string): String {
+        switch (type) {
+            case DocumentType.PDF : return 'PDF';
+            case DocumentType.IMG : return 'Img';
+            case DocumentType.RTF : return 'Rtf';
+            case DocumentType.COL : return 'Col:'+colType.substring(0,3);
+        } // prettier-ignore
 
         return type.charAt(0).toUpperCase() + type.substring(1, 3);
     }
@@ -183,7 +184,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     @action
     searchCollection(query: string) {
         this._selectedResult = undefined;
-        this._results = SearchUtil.SearchCollection(CollectionDockingView.Instance?.Document, query);
+        this._results = SearchUtil.SearchCollection(CollectionDockingView.Instance?.Document, query, this._docTypeString === 'keys');
 
         this.computePageRanks();
     }
@@ -357,11 +358,11 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
      */
     @computed
     public get selectOptions() {
-        const selectValues = ['all', DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.WEB, DocumentType.VID, DocumentType.AUDIO, DocumentType.COL];
+        const selectValues = ['all', DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.WEB, DocumentType.VID, DocumentType.AUDIO, DocumentType.COL, 'keys'];
 
         return selectValues.map(value => (
             <option key={value} value={value}>
-                {SearchBox.formatType(value)}
+                {SearchBox.formatType(value, '')}
             </option>
         ));
     }
@@ -387,10 +388,10 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
                 className += ' searchBox-results-scroll-view-result-selected';
             }
 
-            const formattedType = SearchBox.formatType(StrCast(result[0].type));
+            const formattedType = SearchBox.formatType(StrCast(result[0].type), StrCast(result[0].type_collection));
             const title = result[0].title;
 
-            if (this._docTypeString === 'all' || this._docTypeString === result[0].type) {
+            if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === result[0].type) {
                 validResults++;
                 resultsJSX.push(
                     <Tooltip key={result[0][Id]} placement={'right'} title={<div className="dash-tooltip">{title as string}</div>}>
@@ -412,7 +413,9 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
                             }}
                             className={className}>
                             <div className="searchBox-result-title">{title as string}</div>
-                            <div className="searchBox-result-type">{formattedType}</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>
-- 
cgit v1.2.3-70-g09d2


From b8288a83669f1c6690082a18c7f4d14a76e31586 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 27 Jan 2024 13:56:45 -0500
Subject: from last

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d3c196a60..82ada4fb5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1785,7 +1785,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         );
     }
     get pannableContents() {
-        setTimeout(this.incrementalRender);
+        this.incrementalRender(); // needs to happen synchronously or freshly typed text documents will flash and miss their first characters
         return (
             <CollectionFreeFormPannableContents
                 Document={this.Document}
-- 
cgit v1.2.3-70-g09d2


From 8ac814bbb81b690a6a10f5a07aa5ce0e8cafe283 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 30 Jan 2024 00:40:43 -0500
Subject: changed dropConverter to keep title of dropped Doc. added paintFunc
 node/ checkbox view to formatted text.  changed paintFunc to be computed
 based on layouytfieldkey being text in a freeformview.  changed some
 inputRules to apply to code blocks.  changed : contextmenu to allow regular
 note to be created.  changed experimental tools to be user tmeplate tools. 
 fixed focus on search bar when opening context menu

---
 package-lock.json                                  |  10 +-
 package.json                                       |   2 +-
 src/Utils.ts                                       |   8 +-
 src/client/documents/Documents.ts                  |  39 ++++-
 src/client/util/CurrentUserUtils.ts                |  70 ++++-----
 src/client/util/DropConverter.ts                   |  16 +-
 src/client/views/ContextMenu.tsx                   |  18 ++-
 src/client/views/GlobalKeyHandler.ts               |   2 +-
 src/client/views/InkControlPtHandles.tsx           |   2 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   6 +-
 src/client/views/nodes/DocumentView.tsx            |  10 +-
 .../views/nodes/FontIconBox/FontIconBox.scss       |  10 ++
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |   8 +-
 .../nodes/formattedText/DashDocCommentView.tsx     |  39 ++++-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   7 +-
 .../views/nodes/formattedText/PaintButtonView.tsx  | 113 ++++++++++++++
 .../views/nodes/formattedText/RichTextRules.ts     | 164 +++++++++++++--------
 src/client/views/nodes/formattedText/nodes_rts.ts  |  12 ++
 src/fields/Doc.ts                                  |  10 +-
 19 files changed, 397 insertions(+), 149 deletions(-)
 create mode 100644 src/client/views/nodes/formattedText/PaintButtonView.tsx

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

diff --git a/package-lock.json b/package-lock.json
index 3564c4f1f..aa4108243 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -154,7 +154,7 @@
                 "prosemirror-commands": "^1.5.2",
                 "prosemirror-find-replace": "^0.9.0",
                 "prosemirror-history": "^1.3.2",
-                "prosemirror-inputrules": "^1.3.0",
+                "prosemirror-inputrules": "github:bobzel/prosemirror-inputrules#3b3b3e0b1a1dcf80490d81675206369b6be96276",
                 "prosemirror-keymap": "^1.2.2",
                 "prosemirror-model": "^1.19.3",
                 "prosemirror-schema-list": "^1.3.0",
@@ -28700,8 +28700,9 @@
         },
         "node_modules/prosemirror-inputrules": {
             "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.3.0.tgz",
-            "integrity": "sha512-z1GRP2vhh5CihYMQYsJSa1cOwXb3SYxALXOIfAkX8nZserARtl9LiL+CEl+T+OFIsXc3mJIHKhbsmRzC0HDAXA==",
+            "resolved": "git+ssh://git@github.com/bobzel/prosemirror-inputrules.git#3b3b3e0b1a1dcf80490d81675206369b6be96276",
+            "integrity": "sha512-l81tcwS7ugPJEvCy78RtEvR2/2mVaTkFHJD9ACRxRDXhrfPWV0FkFYBAO7GolN4XlzIbIsal2MVvq2baLQ2guw==",
+            "license": "MIT",
             "dependencies": {
                 "prosemirror-state": "^1.0.0",
                 "prosemirror-transform": "^1.0.0"
@@ -32239,7 +32240,8 @@
         "node_modules/text-table": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
+            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+            "dev": true
         },
         "node_modules/textarea-caret": {
             "version": "3.1.0",
diff --git a/package.json b/package.json
index 760099bd5..f8144a0b3 100644
--- a/package.json
+++ b/package.json
@@ -237,7 +237,7 @@
         "prosemirror-commands": "^1.5.2",
         "prosemirror-find-replace": "^0.9.0",
         "prosemirror-history": "^1.3.2",
-        "prosemirror-inputrules": "^1.3.0",
+        "prosemirror-inputrules": "github:bobzel/prosemirror-inputrules#3b3b3e0b1a1dcf80490d81675206369b6be96276",
         "prosemirror-keymap": "^1.2.2",
         "prosemirror-model": "^1.19.3",
         "prosemirror-schema-list": "^1.3.0",
diff --git a/src/Utils.ts b/src/Utils.ts
index 502cf7db7..e8bd35ac4 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -514,8 +514,8 @@ export function intersectRect(r1: { left: number; top: number; width: number; he
     return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
 }
 
-export function stringHash(s?:string) {
-    return !s? undefined:  Math.abs(s.split('').reduce((a: any, b: any) => ((a) => a & a)((a << 5) - a + b.charCodeAt(0)),0));
+export function stringHash(s?: string) {
+    return !s ? undefined : Math.abs(s.split('').reduce((a: any, b: any) => (a => a & a)((a << 5) - a + b.charCodeAt(0)), 0));
 }
 
 export function percent2frac(percent: string) {
@@ -852,9 +852,11 @@ export function setupMoveUpEvents(
     (target as any)._downX = (target as any)._lastX = e.clientX;
     (target as any)._downY = (target as any)._lastY = e.clientY;
     (target as any)._noClick = false;
+    var moving = false;
 
     const _moveEvent = (e: PointerEvent): void => {
-        if (Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) {
+        if (moving || Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) {
+            moving = true;
             if ((target as any)._doubleTime) {
                 clearTimeout((target as any)._doubleTime);
                 (target as any)._doubleTime = undefined;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ac418ecfe..cc983ffa7 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1667,10 +1667,37 @@ export namespace DocUtils {
     }
 
     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, i) => ({
+                description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
+                event: undoable((args: { x: number; y: number }) => {
+                    const newDoc = DocUtils.copyDragFactory(dragDoc);
+                    if (newDoc) {
+                        newDoc.author = Doc.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: 'Quick Notes',
-                subitems: DocListCast((Doc.UserDoc()['template_notes'] as Doc).data).map((note, i) => ({
+                description: 'Styled Notes',
+                subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map((note, i) => ({
                     description: ':' + StrCast(note.title),
                     event: undoable((args: { x: number; y: number }) => {
                         const textDoc = Docs.Create.TextDocument('', {
@@ -1691,14 +1718,14 @@ export namespace DocUtils {
                 })) as ContextMenuProps[],
                 icon: 'sticky-note',
             });
-        const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data)
+        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, i) => ({
                 description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
                 event: undoable((args: { x: number; y: number }) => {
-                    const newDoc = DocUtils.copyDragFactory(dragDoc);
+                    const newDoc = DocUtils.delegateDragFactory(dragDoc);
                     if (newDoc) {
                         newDoc.author = Doc.CurrentUserEmail;
                         newDoc.x = x;
@@ -1714,8 +1741,8 @@ export namespace DocUtils {
                 icon: Doc.toIcon(dragDoc),
             })) as ContextMenuProps[];
         ContextMenu.Instance.addItem({
-            description: 'Create document',
-            subitems: documentList,
+            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)
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 8f46f844c..31f0308b7 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -61,49 +61,16 @@ export let resolvedPorts: { server: number, socket: number };
 export class CurrentUserUtils {
 
     // initializes experimental advanced template views - slideView,  headerView
-    static setupExperimentalTemplateButtons(doc: Doc, tempDocs:Opt<Doc>, userBtns:Doc[]) {
-        const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [
-            {
-                btnOpts: { title: "slide", icon: "address-card" }, 
-                templateOpts: { _width: 400, _height: 300, title: "slideView",  _xMargin: 3, _yMargin: 3, isSystem: true }, 
-                template: (opts:DocumentOptions) => Docs.Create.MultirowDocument(
-                    [
-                        Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200,  isSystem: true }),
-                        Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true,  _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
-                    ], opts)
-            },
-            {
-                btnOpts: { title: "mobile", icon: "mobile" }, 
-                templateOpts: { title: "NEW MOBILE BUTTON", onClick: undefined, }, 
-                template: (opts:DocumentOptions) => this.mobileButton(opts,
-                        [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }),
-                        this.mobileTextContainer({},
-                            [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])
-                        ]
-                )
-            },
-        ];
-        const requiredTypes = [...requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => {
-            const tempBtn =  DocListCast(tempDocs?.data)?.find(doc => doc.title === btnOpts.title);
-            const reqdScripts = { onDragStart:  '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }' };
-            const assignBtnAndTempOpts = (templateBtn:Opt<Doc>, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => {
-                if (templateBtn) {
-                    DocUtils.AssignOpts(templateBtn,btnOpts);
-                    DocUtils.AssignDocField(templateBtn, "dragFactory", opts => template(opts), templateOptions);
-                }
-                return templateBtn;
-            };
-            return DocUtils.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: MakeTemplate(template(templateOpts))}), reqdScripts);
-        }), ...userBtns];
-
+    static setupUserDocumentCreatorButtons(doc: Doc, userDocTemplates: Opt<Doc>) {
+        const userTemplates = DocListCast(userDocTemplates?.data).filter(doc => !Doc.IsSystem(doc));
         const reqdOpts:DocumentOptions = {
-            title: "Experimental Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true,
+            title: "User Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true, hidden: false,
             _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, isSystem: true, _forceActive: true,
             _layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
         };
         const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
-        const reqdFuncs = { hidden: "IsNoviceMode()" };
-        return DocUtils.AssignScripts(DocUtils.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs);
+        const reqdFuncs = { /* hidden: "IsNoviceMode()"  */ };
+        return DocUtils.AssignScripts(DocUtils.AssignOpts(userDocTemplates, reqdOpts, userTemplates) ?? Docs.Create.MasonryDocument(userTemplates, reqdOpts), reqdScripts, reqdFuncs);
     }
 
     /// Initializes templates for editing click funcs of a document 
@@ -148,7 +115,7 @@ export class CurrentUserUtils {
     static setupNoteTemplates(doc: Doc, field="template_notes") {
         const tempNotes = DocCast(doc[field]);
         const reqdTempOpts:DocumentOptions[] = [
-            { noteType: "Note", backgroundColor: "yellow", icon: "sticky-note"},
+            { noteType: "Postit", backgroundColor: "yellow", icon: "sticky-note"},
             { noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" },
             { noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }];
         const reqdNoteList = reqdTempOpts.map(opts => {
@@ -257,6 +224,16 @@ export class CurrentUserUtils {
             MakeTemplate(Doc.GetProto(header), true, "Untitled Header");
             return header; 
         }
+        const slideView = (opts:DocumentOptions) => {
+            const slide = Docs.Create.MultirowDocument(
+                    [
+                        Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200,  isSystem: true }),
+                        Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true,  _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
+                    ], opts);
+
+            MakeTemplate(Doc.GetProto(slide), true, "Untitled Slide View");
+            return slide; 
+        }
         const emptyThings:{key:string,  // the field name where the empty thing will be stored
                           opts:DocumentOptions,   // the document options that are required for the empty thing
                           funcs?:{[key:string]: any},  // computed fields that are rquired for the empth thing
@@ -279,6 +256,7 @@ export class CurrentUserUtils {
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts),   opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeView_HideUnrendered: true}},
+            {key: "ViewSlide",  creator: slideView,                                         opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}},
             {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: "embed" as dropActionType, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }},
             {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }},
             {key: "Slide",      creator: opts => Docs.Create.TreeDocument([], opts),        opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree, 
@@ -307,6 +285,7 @@ export class CurrentUserUtils {
             { toolTip: "Tap or drag to create a scripting box",   title: "Script", icon: "terminal",    dragFactory: doc.emptyScript as Doc,     clickFactory: DocCast(doc.emptyScript),                                             funcs: { hidden: "IsNoviceMode()"}},
             { toolTip: "Tap or drag to create a data viz node",   title: "DataViz", icon: "chart-bar",  dragFactory: doc.emptyDataViz as Doc,    clickFactory: DocCast(doc.emptyDataViz)},
             { toolTip: "Tap or drag to create a bullet slide",    title: "PPT Slide", icon: "file",     dragFactory: doc.emptySlide as Doc,      clickFactory: DocCast(doc.emptySlide),      openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+            { toolTip: "Tap or drag to create a view slide",      title: "View Slide", icon: "address-card", dragFactory: doc.emptyViewSlide as Doc,clickFactory: DocCast(doc.emptyViewSlide),openFactoryLocation: OpenWhere.overlay,funcs: { hidden: "IsNoviceMode()"}},
             { toolTip: "Tap or drag to create a data note",       title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader),     openFactoryAsDelegate: true,            funcs: { hidden: "IsNoviceMode()"} },
             { toolTip: "Toggle a Calculator REPL",                title: "replviewer",  icon: "calculator",                                      clickFactory: '<ScriptingRepl />' as any,   openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
            // { toolTip: "Toggle an UndoStack",                     title: "undostacker", icon: "calculator",                                      clickFactory: "<UndoStack />" as any,         openFactoryLocation: OpenWhere.overlay},
@@ -330,7 +309,7 @@ export class CurrentUserUtils {
         });
 
         const reqdOpts:DocumentOptions = {
-            title: "Basic Item Creators", _layout_showTitle: "title", _xMargin: 0, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
+            title: "Document Creators", _layout_showTitle: "title", _xMargin: 0, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
             _layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
              childDragAction: 'embed' 
         };
@@ -464,16 +443,17 @@ export class CurrentUserUtils {
     /// Initializes the panel of draggable tools that is opened from the left sidebar.
     static setupToolsBtnPanel(doc: Doc, field:string) {
         const myTools = DocCast(doc[field]);
-        const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, DocListCast(myTools?.data)?.length ? DocListCast(myTools.data)[0]:undefined);
-        const tempBtns = DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined;
-        const userTemplateBtns = DocListCast(tempBtns?.data).filter(btn => !btn.isSystem);
-        const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc, tempBtns, userTemplateBtns);
+        const allTools = DocListCast(myTools?.data);
+        const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, allTools?.length ? allTools[0]:undefined);
+        const userTools = allTools && allTools?.length > 1 ? allTools[1]:undefined;
+        const userBtns = CurrentUserUtils.setupUserDocumentCreatorButtons(doc, userTools);
+        //doc.myUserBtns = new PrefetchProxy(userBtns);
         const reqdToolOps:DocumentOptions = {
             title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
             layout_explainer: "This is a palette of documents that can be created.",
             _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true, 
         };
-        return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]);
+        return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, userBtns]);
     }
 
     /// initializes the left sidebar dashboard pane
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 8c3b56452..ba981145d 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -3,7 +3,7 @@ import { DocData } from '../../fields/DocSymbols';
 import { ObjectField } from '../../fields/ObjectField';
 import { RichTextField } from '../../fields/RichTextField';
 import { listSpec } from '../../fields/Schema';
-import { ScriptField } from '../../fields/ScriptField';
+import { ComputedField, ScriptField } from '../../fields/ScriptField';
 import { Cast, StrCast } from '../../fields/Types';
 import { ImageField } from '../../fields/URLField';
 import { Docs } from '../documents/Documents';
@@ -39,15 +39,12 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = und
             any = makeTemplate(d, false) || any;
         }
     });
-    if (first) {
-        if (!docs.length) {
-            // bcz: feels hacky : if the root level document has items, it's not a field template
-            any = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData]) || any;
-        }
-    }
-    if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) {
+    if (first && !docs.length) {
+        // bcz: feels hacky : if the root level document has items, it's not a field template
+        any = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData], true) || any;
+    } else if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) {
         if (!StrCast(layoutDoc.title).startsWith('-')) {
-            any = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData]);
+            any = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData], true);
         }
     }
     rename && (doc.title = rename);
@@ -82,6 +79,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
                 icon: 'bolt',
                 isSystem: false,
             });
+            dbox.title = ComputedField.MakeFunction('this.dragFactory.title');
             dbox.dragFactory = layoutDoc;
             dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined;
             dbox.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory)');
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 8dcdd80e5..8c3c9df2e 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -183,11 +183,12 @@ export class ContextMenu extends ObservableReactComponent<{}> {
 
     @computed get menuItems() {
         if (!this._searchString) {
-            return this._items.map((item, ind) => <ContextMenuItem {...item} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} key={ind + item.description} closeMenu={this.closeMenu} />);
+            return this._items.map((item, ind) => <ContextMenuItem key={item.description + ind} {...item} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} closeMenu={this.closeMenu} />);
         }
         return this.filteredItems.map((value, index) =>
             Array.isArray(value) ? (
                 <div
+                    key={index + value.join(' -> ')}
                     className="contextMenu-group"
                     style={{
                         background: StrCast(SettingsManager.userVariantColor),
@@ -204,7 +205,10 @@ export class ContextMenu extends ObservableReactComponent<{}> {
         return this._showSearch ? 1 : this._items.reduce((p, mi) => p + ((mi as any).noexpand ? 1 : (mi as any).subitems?.length || 1), 0) > 15;
     }
 
+    _searchRef = React.createRef<HTMLInputElement>(); // bcz: we shouldn't need this, since we set autoFocus on the <input> tag, but for some reason we do...
+
     render() {
+        this.itemsNeedSearch && setTimeout(() => this._searchRef.current?.focus());
         return (
             <div
                 className="contextMenu-cont"
@@ -226,7 +230,17 @@ export class ContextMenu extends ObservableReactComponent<{}> {
                         <span className="icon-background">
                             <FontAwesomeIcon icon="search" size="lg" />
                         </span>
-                        <input style={{ color: 'black' }} className="contextMenu-item contextMenu-description search" type="text" placeholder="Filter Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
+                        <input
+                            ref={this._searchRef}
+                            style={{ color: 'black' }}
+                            className="contextMenu-item contextMenu-description search"
+                            type="text"
+                            placeholder="Filter Menu..."
+                            value={this._searchString}
+                            onKeyDown={this.onKeyDown}
+                            onChange={this.onChange}
+                            autoFocus
+                        />
                     </span>
                 )}
                 {this.menuItems}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index d134d9e7b..733383002 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -160,7 +160,7 @@ export class KeyManager {
                     if (LightboxView.LightboxDoc) {
                         LightboxView.Instance.SetLightboxDoc(undefined);
                         SelectionManager.DeselectAll();
-                    } else DocumentDecorations.Instance.onCloseClick(true);
+                    } else if (!window.getSelection()?.toString()) DocumentDecorations.Instance.onCloseClick(true);
                     return { stopPropagation: true, preventDefault: true };
                 }
                 break;
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 7dd57e04d..31b13d2c8 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -189,6 +189,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
     @observable _overStart: boolean = false;
     @observable _overEnd: boolean = false;
 
+    _throttle = 0; // need to throttle dragging since the position may change when the control points change.  this allows the stroke to settle so that we don't get increasingly bad jitter
     @action
     dragRotate = (e: React.PointerEvent, pt1: () => { X: number; Y: number }, pt2: () => { X: number; Y: number }) => {
         SnappingManager.SetIsDragging(true);
@@ -196,6 +197,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
             this,
             e,
             action(e => {
+                if (this._throttle++ % 2 !== 0) return false;
                 if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
                 // compute stretch factor by finding scaling along axis between start and end points
                 const p1 = pt1();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 82ada4fb5..54e8b08b6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
 import { DocData, Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
@@ -53,6 +53,7 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable
 import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
+import { RichTextField } from '../../../../fields/RichTextField';
 
 export interface collectionFreeformViewProps {
     NativeWidth?: () => number;
@@ -75,7 +76,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
     @computed get paintFunc() {
-        const paintFunc = StrCast(this.Document[DocData].paintFunc).trim();
+        const field = this.layoutDoc[this.fieldKey];
+        const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim();
         return !paintFunc
             ? ''
             : `const dashDiv = document.querySelector('#${this._paintedId}'); 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2e1ba2a7e..51f4b1a68 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -456,11 +456,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
     deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
     setToggleDetail = undoable(
-        () =>
+        (defaultLayout: string) =>
             (this.Document.onClick = ScriptField.MakeScript(
                 `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
                     .replace('layout_', '')
-                    .replace(/^layout$/, 'detail')}")`,
+                    .replace(/^layout$/, 'detail')}", "${defaultLayout}")`,
                 { documentView: 'any' }
             )),
         'set toggle detail'
@@ -1212,7 +1212,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     };
     public noOnClick = () => this._docViewInternal?.noOnClick();
     public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle);
-    public setToggleDetail = () => this._docViewInternal?.setToggleDetail();
+    public setToggleDetail = (defaultLayout = '') => this._docViewInternal?.setToggleDetail(defaultLayout);
     public onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY);
     public cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents();
     public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource);
@@ -1460,8 +1460,8 @@ ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView
     LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0);
 });
 
-ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
-    if (dv.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(false, 'layout');
+ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string, defaultLayout = '') {
+    if (dv.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true);
     else dv.switchViews(true, detailLayoutKeySuffix, undefined, true);
 });
 
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss
index db2ffa756..2db285910 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss
@@ -1,5 +1,15 @@
 @import '../../global/globalCssVariables.module.scss';
 
+// bcz: something's messed up with the IconButton css.  this mostly fixes the fit-all button, the color buttons, the undo +/- expander and the dropdown doc type list (eg 'text')
+.iconButton-container {
+    width: unset !important;
+    min-width: 30px !important;
+    height: unset !important;
+    min-height: 30px;
+    .color {
+        height: 3px !important;
+    }
+}
 .menuButton {
     height: 100%;
     display: flex;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 8290e102c..3577cc8d9 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -381,7 +381,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
             case ButtonType.ColorButton:        return this.colorButton; 
             case ButtonType.MultiToggleButton:  return this.multiToggleButton;
             case ButtonType.ToggleButton:       return this.toggleButton;
-            case ButtonType.ClickButton:
+            case ButtonType.ClickButton:return <IconButton {...btnProps} size={Size.MEDIUM} color={color} />;
             case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />;
             case ButtonType.TextButton: return <Button     {...btnProps} color={color} 
                         background={SettingsManager.userBackgroundColor} text={StrCast(this.dataDoc.buttonText)}/>;
@@ -392,6 +392,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
     };
 
     render() {
-        return <div onContextMenu={this.specificContextMenu}>{this.renderButton()}</div>;
+        return (
+            <div style={{ margin: 'auto', width: '100%' }} onContextMenu={this.specificContextMenu}>
+                {this.renderButton()}
+            </div>
+        );
     }
 }
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index b7d2a24c2..a72ed1813 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -3,6 +3,8 @@ import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
 import { DocServer } from '../../../DocServer';
 import * as React from 'react';
+import { IReactionDisposer, computed, reaction } from 'mobx';
+import { NumCast } from '../../../../fields/Types';
 
 // creates an inline comment in a note when '>>' is typed.
 // the comment sits on the right side of the note and vertically aligns with its anchor in the text.
@@ -10,8 +12,10 @@ import * as React from 'react';
 export class DashDocCommentView {
     dom: HTMLDivElement; // container for label and value
     root: any;
+    node: any;
 
     constructor(node: any, view: any, getPos: any) {
+        this.node = node;
         this.dom = document.createElement('div');
         this.dom.style.width = node.attrs.width;
         this.dom.style.height = node.attrs.height;
@@ -32,10 +36,14 @@ export class DashDocCommentView {
         };
 
         this.root = ReactDOM.createRoot(this.dom);
-        this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} docId={node.attrs.docId} />);
+        this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
         (this as any).dom = this.dom;
     }
 
+    setHeight = (hgt: number) => {
+        !this.node.attrs.reflow && DocServer.GetRefField(this.node.attrs.docId).then(doc => doc instanceof Doc && (this.dom.style.height = hgt + ''));
+    };
+
     destroy() {
         this.root.unmount();
     }
@@ -51,9 +59,15 @@ interface IDashDocCommentViewInternal {
     docId: string;
     view: any;
     getPos: any;
+    setHeight: (height: number) => void;
 }
 
 export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
+    _reactionDisposer: IReactionDisposer | undefined;
+
+    @computed get _dashDoc() {
+        return DocServer.GetRefField(this.props.docId);
+    }
     constructor(props: any) {
         super(props);
         this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
@@ -61,15 +75,32 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
         this.onPointerUpCollapsed = this.onPointerUpCollapsed.bind(this);
         this.onPointerDownCollapsed = this.onPointerDownCollapsed.bind(this);
     }
+    componentDidMount(): void {
+        this._reactionDisposer?.();
+        this._dashDoc.then(
+            doc =>
+                doc instanceof Doc &&
+                (this._reactionDisposer = reaction(
+                    () => NumCast((doc as Doc)._height),
+                    hgt => this.props.setHeight(hgt),
+                    {
+                        fireImmediately: true,
+                    }
+                ))
+        );
+    }
+    componentWillUnmount(): void {
+        this._reactionDisposer?.();
+    }
 
     onPointerLeaveCollapsed(e: any) {
-        DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
+        this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
         e.preventDefault();
         e.stopPropagation();
     }
 
     onPointerEnterCollapsed(e: any) {
-        DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
+        this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
         e.preventDefault();
         e.stopPropagation();
     }
@@ -82,7 +113,7 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
             const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
             this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
             setTimeout(() => {
-                expand && DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+                expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
                 try {
                     this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1))));
                 } catch (e) {}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0b857794b..973f90501 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -68,6 +68,7 @@ import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 import { CollectionView } from '../../collections/CollectionView';
+import { PaintButtonView } from './PaintButtonView';
 // import * as applyDevTools from 'prosemirror-dev-tools';
 @observer
 export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -1410,6 +1411,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     dashField(node: any, view: any, getPos: any) {
                         return new DashFieldView(node, view, getPos, self);
                     },
+                    paintButton(node: any, view: any, getPos: any) {
+                        return new PaintButtonView(node, view, getPos, self);
+                    },
                     equation(node: any, view: any, getPos: any) {
                         return new EquationView(node, view, getPos, self);
                     },
@@ -1538,15 +1542,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._downTime = Date.now();
-        this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
         FormattedTextBoxComment.textBox = this;
         if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
             if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
                 // stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes.
                 e.stopPropagation(); // if the text box's content is active, then it consumes all down events
                 document.addEventListener('pointerup', this.onSelectEnd);
+                (this.ProseRef?.children?.[0] as any).focus();
             }
         }
+        this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
         if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
             e.preventDefault();
         }
diff --git a/src/client/views/nodes/formattedText/PaintButtonView.tsx b/src/client/views/nodes/formattedText/PaintButtonView.tsx
new file mode 100644
index 000000000..74423c772
--- /dev/null
+++ b/src/client/views/nodes/formattedText/PaintButtonView.tsx
@@ -0,0 +1,113 @@
+import { action, computed, IReactionDisposer, makeObservable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import * as ReactDOM from 'react-dom/client';
+import { Doc } from '../../../../fields/Doc';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+import './DashFieldView.scss';
+import { FormattedTextBox } from './FormattedTextBox';
+import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { CollectionView } from '../../collections/CollectionView';
+import { StrCast } from '../../../../fields/Types';
+
+export class PaintButtonView {
+    dom: HTMLDivElement; // container for label and value
+    root: any;
+    node: any;
+    tbox: FormattedTextBox;
+
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this.node = node;
+        this.tbox = tbox;
+        this.dom = document.createElement('div');
+        this.dom.style.width = node.attrs.width;
+        this.dom.style.height = node.attrs.height;
+        this.dom.style.position = 'relative';
+        this.dom.style.display = 'inline-block';
+        this.dom.onkeypress = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onkeydown = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onkeyup = function (e: any) {
+            e.stopPropagation();
+        };
+        this.dom.onmousedown = function (e: any) {
+            e.stopPropagation();
+        };
+
+        this.root = ReactDOM.createRoot(this.dom);
+        this.root.render(<PaintButtonViewInternal node={node} getPos={getPos} width={node.attrs.width} height={node.attrs.height} tbox={tbox} />);
+    }
+    destroy() {
+        setTimeout(() => {
+            try {
+                this.root.unmount();
+            } catch {}
+        });
+    }
+    deselectNode() {
+        this.dom.classList.remove('ProseMirror-selectednode');
+    }
+    selectNode() {
+        this.dom.classList.add('ProseMirror-selectednode');
+    }
+}
+
+interface IPaintButtonViewInternal {
+    tbox: FormattedTextBox;
+    width: number;
+    height: number;
+    node: any;
+    getPos: any;
+}
+
+@observer
+export class PaintButtonViewInternal extends ObservableReactComponent<IPaintButtonViewInternal> {
+    _reactionDisposer: IReactionDisposer | undefined;
+    _textBoxDoc: Doc;
+
+    constructor(props: IPaintButtonViewInternal) {
+        super(props);
+        makeObservable(this);
+        this._textBoxDoc = this._props.tbox.Document;
+    }
+
+    return100 = () => 100;
+    @computed get _checked() {
+        return this._props.tbox.Document.onClick ? true : false;
+    }
+
+    onCheckClick = () => {
+        const textView = this._props.tbox.DocumentView?.();
+        if (textView) {
+            const paintedField = 'layout_' + this._props.tbox.fieldKey + 'Painted';
+            const layoutFieldKey = StrCast(textView.layoutDoc.layout_fieldKey, 'layout');
+            if (textView.layoutDoc.onClick) {
+                textView.layoutDoc[paintedField] = undefined;
+                textView.layoutDoc.onClick = undefined;
+            } else {
+                textView.layoutDoc.type_collection = CollectionViewType.Freeform;
+                textView.dataDoc[paintedField] = CollectionView.LayoutString(this._props.tbox.fieldKey);
+                textView.layoutDoc.layout_fieldKey = paintedField;
+                textView.setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', ''));
+                textView.layoutDoc.layout_fieldKey = layoutFieldKey;
+            }
+        }
+    };
+
+    render() {
+        return (
+            <div
+                className="dashFieldView"
+                style={{
+                    width: this._props.width,
+                    height: this._props.height,
+                    pointerEvents: this._props.tbox._props.isSelected() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
+                }}>
+                <input type="checkbox" value="paint" checked={this._checked} onChange={e => this.onCheckClick()} />
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index be32a2c4a..8f7bc5282 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -4,8 +4,7 @@ import { Doc, StrListCast } from '../../../../fields/Doc';
 import { DocData } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
-import { ComputedField } from '../../../../fields/ScriptField';
-import { NumCast } from '../../../../fields/Types';
+import { NumCast, StrCast } from '../../../../fields/Types';
 import { Utils } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
@@ -15,6 +14,7 @@ import { RichTextMenu } from './RichTextMenu';
 import { schema } from './schema_rts';
 import { CollectionView } from '../../collections/CollectionView';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { ContextMenu } from '../../ContextMenu';
 
 export class RichTextRules {
     public Document: Doc;
@@ -69,23 +69,39 @@ export class RichTextRules {
 
             // ``` create code block
             textblockTypeInputRule(/^```$/, schema.nodes.code_block),
-
-            new InputRule(new RegExp(/^\^@paint/), (state, match, start, end) => {
-                const { dataDoc, layoutDoc, fieldKey } = this.TextBox;
-                layoutDoc.type_collection = CollectionViewType.Freeform;
-                dataDoc.layout_painted = CollectionView.LayoutString('painted');
-                const layoutFieldKey = layoutDoc.layout_fieldKey;
-                layoutDoc.layout_fieldKey = 'layout_painted';
-                this.TextBox.DocumentView?.().setToggleDetail();
-                layoutDoc.layout_fieldKey = layoutFieldKey;
-                dataDoc.paintFunc = ComputedField.MakeFunction(`toJavascriptString(this['${fieldKey}']?.Text)`);
-                const comment = '/* this is now a paint func */';
-                const tr = state.tr
-                    .deleteRange(start, end)
-                    .insertText(comment)
-                    .insert(start + comment.length, schema.nodes.code_block.create());
-                return tr.setSelection(new TextSelection(tr.doc.resolve(start + comment.length + 2)));
-            }),
+            new InputRule(
+                new RegExp(/(^|\n)\^@paint/), // for code blocks '^' means the beginning of the block, not the line, so need to test for \n
+                (state, match, start, end) => {
+                    const { dataDoc, layoutDoc, fieldKey } = this.TextBox;
+                    layoutDoc.type_collection = CollectionViewType.Freeform;
+                    const paintedField = 'layout_' + this.TextBox.fieldKey + 'Painted';
+                    dataDoc[paintedField] = CollectionView.LayoutString(this.TextBox.fieldKey);
+                    const layoutFieldKey = StrCast(layoutDoc.layout_fieldKey);
+                    layoutDoc.layout_fieldKey = paintedField;
+                    this.TextBox.DocumentView?.().setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', ''));
+                    layoutDoc.layout_fieldKey = layoutFieldKey;
+                    const comment = '/* enable as paint function ';
+                    const endComment = ' */\n';
+                    const inCode = state.tr.selection.$anchor.node().type === schema.nodes.code_block;
+                    if (inCode) {
+                        const tr = state.tr
+                            .deleteRange(start, end)
+                            .insertText(comment)
+                            .insert(start + comment.length, schema.nodes.paintButton.create())
+                            .insertText(endComment);
+                        return tr.setSelection(new TextSelection(tr.doc.resolve(start + comment.length + endComment.length + 1)));
+                    } else {
+                        const tr = state.tr
+                            .deleteRange(start, end)
+                            .insertText(comment)
+                            .insert(start + comment.length, schema.nodes.paintButton.create())
+                            .insertText(endComment)
+                            .insert(start + comment.length + endComment.length + 1, schema.nodes.code_block.create());
+                        return tr.setSelection(new TextSelection(tr.doc.resolve(start + comment.length + endComment.length + 3)));
+                    }
+                },
+                { inCode: true }
+            ),
 
             // %<font-size> set the font size
             new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => {
@@ -93,6 +109,32 @@ export class RichTextRules {
                 return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
             }),
 
+            //Create annotation to a field on the text document
+            new InputRule(new RegExp(/>::$/), (state, match, start, end) => {
+                const creator = (doc: Doc) => {
+                    const textDoc = this.Document[DocData];
+                    const numInlines = NumCast(textDoc.inlineTextCount);
+                    textDoc.inlineTextCount = numInlines + 1;
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const newNode = schema.nodes.dashComment.create({ docId: doc[Id], reflow: false });
+                    const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: doc[Id], float: 'right' });
+                    const sm = state.storedMarks || undefined;
+                    this.TextBox.EditorView?.dispatch(
+                        node
+                            ? this.TextBox.EditorView.state.tr
+                                  .insert(start, newNode)
+                                  .replaceRangeWith(start + 1, end + 2, dashDoc)
+                                  .insertText(' ', start + 2)
+                                  .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+                            : this.TextBox.EditorView.state.tr
+                    );
+                };
+                DocUtils.addDocumentCreatorMenuItems(creator, creator, 200, 200);
+                const cm = ContextMenu.Instance;
+                cm.displayMenu(200, 200, undefined, true);
+
+                return null;
+            }),
             //Create annotation to a field on the text document
             new InputRule(new RegExp(/>>$/), (state, match, start, end) => {
                 const textDoc = this.Document[DocData];
@@ -117,7 +159,7 @@ export class RichTextRules {
                 textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
                 textDoc[inlineFieldKey] = ''; // set a default value for the annotation
                 const node = (state.doc.resolve(start) as any).nodeAfter;
-                const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id] });
+                const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id], reflow: true });
                 const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: textDocInline[Id], float: 'right' });
                 const sm = state.storedMarks || undefined;
                 const replaced = node
@@ -265,49 +307,53 @@ export class RichTextRules {
             // [[fieldKey]] => show field
             // [[fieldKey=value]] => show field and also set its value
             // [[fieldKey:docTitle]] => show field of doc
-            new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => {
-                const fieldKey = match[1];
-                const docTitle = match[3]?.replace(':', '');
-                const value = match[2]?.substring(1);
-                const linkToDoc = (target: Doc) => {
-                    const rstate = this.TextBox.EditorView?.state;
-                    const selection = rstate?.selection.$from.pos;
-                    if (rstate) {
-                        this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
-                    }
+            new InputRule(
+                new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
+                (state, match, start, end) => {
+                    const fieldKey = match[1];
+                    const docTitle = match[3]?.replace(':', '');
+                    const value = match[2]?.substring(1);
+                    const linkToDoc = (target: Doc) => {
+                        const rstate = this.TextBox.EditorView?.state;
+                        const selection = rstate?.selection.$from.pos;
+                        if (rstate) {
+                            this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
+                        }
 
-                    DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' });
+                        DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' });
 
-                    const fstate = this.TextBox.EditorView?.state;
-                    if (fstate && selection) {
-                        this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
-                    }
-                };
-                const getTitledDoc = (docTitle: string) => {
-                    if (!DocServer.FindDocByTitle(docTitle)) {
-                        Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
-                    }
-                    const titledDoc = DocServer.FindDocByTitle(docTitle);
-                    return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
-                };
-                if (!fieldKey) {
-                    if (docTitle) {
-                        const target = getTitledDoc(docTitle);
-                        if (target) {
-                            setTimeout(() => linkToDoc(target));
-                            return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
+                        const fstate = this.TextBox.EditorView?.state;
+                        if (fstate && selection) {
+                            this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
+                        }
+                    };
+                    const getTitledDoc = (docTitle: string) => {
+                        if (!DocServer.FindDocByTitle(docTitle)) {
+                            Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
+                        }
+                        const titledDoc = DocServer.FindDocByTitle(docTitle);
+                        return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
+                    };
+                    if (!fieldKey) {
+                        if (docTitle) {
+                            const target = getTitledDoc(docTitle);
+                            if (target) {
+                                setTimeout(() => linkToDoc(target));
+                                return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
+                            }
                         }
+                        return state.tr;
                     }
-                    return state.tr;
-                }
-                if (value !== '' && value !== undefined) {
-                    const num = value.match(/^[0-9.]$/);
-                    this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
-                }
-                const target = getTitledDoc(docTitle);
-                const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false });
-                return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
-            }),
+                    if (value !== '' && value !== undefined) {
+                        const num = value.match(/^[0-9.]$/);
+                        this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
+                    }
+                    const target = getTitledDoc(docTitle);
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false });
+                    return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
+                },
+                { inCode: true }
+            ),
 
             // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
             // wiki:title
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 31f001b11..4706a97fa 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -179,6 +179,7 @@ export const nodes: { [index: string]: NodeSpec } = {
     dashComment: {
         attrs: {
             docId: { default: '' },
+            reflow: { default: true },
         },
         inline: true,
         group: 'inline',
@@ -275,6 +276,17 @@ export const nodes: { [index: string]: NodeSpec } = {
         },
     },
 
+    paintButton: {
+        inline: true,
+        attrs: {},
+        group: 'inline',
+        draggable: false,
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+            return ['div', { ...node.attrs, ...attrs }];
+        },
+    },
+
     video: {
         inline: true,
         attrs: {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 67f09f37b..30e3aa5f0 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -60,7 +60,7 @@ export namespace Field {
             case 'number':
             case 'boolean':rawjava = String(field);
                            break;
-            default:       rawjava = field?.[ToJavascriptString]?.() ?? 'null';
+            default:       rawjava = field?.[ToJavascriptString]?.() ?? '';
         } // prettier-ignore
         var script = rawjava;
         // this is a bit hacky, but we treat '^@' references to a published document
@@ -866,7 +866,7 @@ export namespace Doc {
     export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) {
         if (infield instanceof Promise) return;
         if (!(infield instanceof Doc)) {
-            infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
+            infield?.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
             return;
         }
         const doc = infield as Doc;
@@ -1032,13 +1032,13 @@ export namespace Doc {
     //  This function converts a generic field layout display into a field layout that displays a specific
     // metadata field indicated by the title of the template field (not the default field that it was rendering)
     //
-    export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>): boolean {
+    export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>, keepFieldKey = false): boolean {
         // find the metadata field key that this template field doc will display (indicated by its title)
-        const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutFieldKey(templateField);
+        const metadataFieldKey = keepFieldKey ? Doc.LayoutFieldKey(templateField) : StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutFieldKey(templateField);
 
         // update the original template to mark it as a template
         templateField.isTemplateForField = metadataFieldKey;
-        templateField.title = metadataFieldKey;
+        !keepFieldKey && (templateField.title = metadataFieldKey);
 
         const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)];
         const templateCaptionValue = templateField.caption;
-- 
cgit v1.2.3-70-g09d2


From 0d8c05db9cbb5a23a94554c65015c347ff8c38b9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 31 Jan 2024 20:39:47 -0500
Subject: cleaned up accessing/setting proto_embeddings with api on Doc.  fixed
 some css related to IconButtons.  added a paintView toggle button to dec
 decorations for text with code blocks. enabled text with code to modify Docs
 and get this and documentView as params.

---
 package-lock.json                                  |   9 +-
 package.json                                       |   2 +-
 src/client/util/DocumentManager.ts                 |   4 +-
 src/client/util/DropConverter.ts                   |   1 +
 src/client/util/Scripting.ts                       |   3 +-
 src/client/views/DocComponent.tsx                  |   2 +-
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/PropertiesDocContextSelector.tsx  |   6 +-
 src/client/views/PropertiesView.tsx                |   5 +-
 src/client/views/StyleProvider.scss                |   4 +
 src/client/views/StyleProvider.tsx                 |  18 +++-
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 src/client/views/collections/TreeView.scss         |   4 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  14 ++-
 .../collectionSchema/CollectionSchemaView.scss     |   7 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   6 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  55 +++++-----
 .../views/nodes/formattedText/PaintButtonView.tsx  | 113 ---------------------
 .../views/nodes/formattedText/RichTextRules.ts     |  49 +++------
 src/fields/Doc.ts                                  |  25 +++--
 21 files changed, 127 insertions(+), 206 deletions(-)
 delete mode 100644 src/client/views/nodes/formattedText/PaintButtonView.tsx

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

diff --git a/package-lock.json b/package-lock.json
index aa4108243..aafc14a86 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -154,7 +154,7 @@
                 "prosemirror-commands": "^1.5.2",
                 "prosemirror-find-replace": "^0.9.0",
                 "prosemirror-history": "^1.3.2",
-                "prosemirror-inputrules": "github:bobzel/prosemirror-inputrules#3b3b3e0b1a1dcf80490d81675206369b6be96276",
+                "prosemirror-inputrules": "^1.4.0",
                 "prosemirror-keymap": "^1.2.2",
                 "prosemirror-model": "^1.19.3",
                 "prosemirror-schema-list": "^1.3.0",
@@ -28699,10 +28699,9 @@
             }
         },
         "node_modules/prosemirror-inputrules": {
-            "version": "1.3.0",
-            "resolved": "git+ssh://git@github.com/bobzel/prosemirror-inputrules.git#3b3b3e0b1a1dcf80490d81675206369b6be96276",
-            "integrity": "sha512-l81tcwS7ugPJEvCy78RtEvR2/2mVaTkFHJD9ACRxRDXhrfPWV0FkFYBAO7GolN4XlzIbIsal2MVvq2baLQ2guw==",
-            "license": "MIT",
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz",
+            "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==",
             "dependencies": {
                 "prosemirror-state": "^1.0.0",
                 "prosemirror-transform": "^1.0.0"
diff --git a/package.json b/package.json
index f8144a0b3..a94f8c9be 100644
--- a/package.json
+++ b/package.json
@@ -237,7 +237,7 @@
         "prosemirror-commands": "^1.5.2",
         "prosemirror-find-replace": "^0.9.0",
         "prosemirror-history": "^1.3.2",
-        "prosemirror-inputrules": "github:bobzel/prosemirror-inputrules#3b3b3e0b1a1dcf80490d81675206369b6be96276",
+        "prosemirror-inputrules": "^1.4.0",
         "prosemirror-keymap": "^1.2.2",
         "prosemirror-model": "^1.19.3",
         "prosemirror-schema-list": "^1.3.0",
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index f730d17fe..53d472c66 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -355,8 +355,8 @@ export function DocFocusOrOpen(doc: Doc, options: FocusViewOptions = { willZoomC
             });
         }
     };
-    if (Doc.IsDataProto(doc) && DocListCast(doc.proto_embeddings).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) {
-        doc = DocListCast(doc.proto_embeddings).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!;
+    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;
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index ba981145d..54066d267 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -73,6 +73,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
                 _nativeHeight: 100,
                 _width: 100,
                 _height: 100,
+                _layout_hideContextMenu: true,
                 backgroundColor: StrCast(doc.backgroundColor),
                 title: StrCast(layoutDoc.title),
                 btnType: ButtonType.ClickButton,
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index dfaacf318..f5e162d16 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -61,7 +61,8 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
     const compiledFunction = (() => {
         try {
             return new Function(...paramNames, `return ${script}`);
-        } catch {
+        } catch (e) {
+            console.log(e);
             return undefined;
         }
     })();
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 73fa6709c..58be41f53 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -189,7 +189,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
                 toRemove.forEach(doc => {
                     leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
                     Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc, true);
-                    Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', doc, true);
+                    Doc.RemoveEmbedding(doc, doc);
                     doc.embedContainer = undefined;
                     if (recent && !dontAddToRemoved) {
                         doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2193acf62..dec109d7b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -283,7 +283,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             } else {
                 var openDoc = selectedDocs[0].Document;
                 if (openDoc.layout_fieldKey === 'layout_icon') {
-                    openDoc = DocListCast(openDoc.proto_embeddings).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc);
+                    openDoc = Doc.GetEmbeddings(openDoc).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc);
                     Doc.deiconifyView(openDoc);
                 }
                 LightboxView.Instance.SetLightboxDoc(
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 54f141a36..361451c4d 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -28,14 +28,14 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope
         if (!this._props.DocView) return [];
         const target = this._props.DocView._props.Document;
         const targetContext = this._props.DocView.containerViewPath?.().lastElement()?.Document;
-        const embeddings = DocListCast(target.proto_embeddings);
+        const embeddings = Doc.GetEmbeddings(target);
         const containerProtos = embeddings.filter(embedding => embedding.embedContainer && embedding.embedContainer instanceof Doc).reduce((set, embedding) => set.add(Cast(embedding.embedContainer, Doc, null)), new Set<Doc>());
-        const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.proto_embeddings));
+        const containerSets = Array.from(containerProtos.keys()).map(container => Doc.GetEmbeddings(container));
         const containers = containerSets.reduce((p, set) => {
             set.map(s => p.add(s));
             return p;
         }, new Set<Doc>());
-        const doclayoutSets = Array.from(containers.keys()).map(dp => DocListCast(dp.proto_embeddings));
+        const doclayoutSets = Array.from(containers.keys()).map(dp => Doc.GetEmbeddings(dp));
         const doclayouts = Array.from(
             doclayoutSets
                 .reduce((p, set) => {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 208ed56c9..a7a065a95 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -173,7 +173,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         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));
-            docs.forEach(doc => Object.keys(doc).forEach(key => doc[key] !== ComputedField.undefined && ids.add(key)));
+            docs.forEach(doc => Object.keys(doc).forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key)));
 
             // prettier-ignore
             Array.from(ids).filter(filter).sort().map(key => {
@@ -263,8 +263,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @computed get contextCount() {
         if (this.selectedDocumentView) {
             const target = this.selectedDocumentView.Document;
-            const embeddings = DocListCast(target.proto_embeddings);
-            return embeddings.length - 1;
+            return Doc.GetEmbeddings(target).length - 1;
         } else {
             return 0;
         }
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index 4d3096f71..00bf503f5 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -1,5 +1,6 @@
 .styleProvider-filter,
 .styleProvider-audio,
+.styleProvider-paint,
 .styleProvider-lock {
     font-size: 10;
     width: 15;
@@ -28,6 +29,9 @@
 .styleProvider-audio {
     right: 30;
 }
+.styleProvider-paint {
+    top: 15;
+}
 .styleProvider-lock:hover,
 .styleProvider-audio:hover,
 .styleProvider-filter:hover {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 5a0167338..d3d13988f 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -9,7 +9,7 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
 import { FaFilter } from 'react-icons/fa';
 import { Doc, Opt, StrListCast } from '../../fields/Doc';
 import { DocViews } from '../../fields/DocSymbols';
-import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
 import { DashColor, lightOrDark, Utils } from '../../Utils';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager';
@@ -53,6 +53,16 @@ export enum StyleProp {
 function toggleLockedPosition(doc: Doc) {
     UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
 }
+function togglePaintView(e: React.MouseEvent, doc: Opt<Doc>, props: Opt<FieldViewProps & DocumentViewProps>) {
+    const scriptProps = {
+        this: doc,
+        _readOnly_: false,
+        documentView: props?.DocumentView?.(),
+        value: undefined,
+    };
+    e.stopPropagation();
+    UndoManager.RunInBatch(() => doc && ScriptCast(doc.onPaint).script.run(scriptProps), 'togglePaintView');
+}
 
 export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
     return `M ${pw * 0.5} ${ph * inset}   C ${pw * 0.6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset}   C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * 0.25} ${pw * (1 - inset)} ${ph * 0.3}   C ${
@@ -267,6 +277,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                             <FontAwesomeIcon icon='lock' size="lg" />
                         </div>
                     );
+            const paint = () => !doc?.onPaint ? null : (
+                        <div className="styleProvider-paint" onClick={e => togglePaintView(e, doc, props)}>
+                            <FontAwesomeIcon icon='pen' size="lg" />
+                        </div>
+                    );
             const filter = () => {
                 const dashView = untracked(() => DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard));
                 const showFilterIcon =
@@ -329,6 +344,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             };
             return (
                 <>
+                    {paint()}
                     {lock()}
                     {filter()}
                     {audio()}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 87973fd81..31ca86f0f 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -490,7 +490,7 @@ export class CollectionDockingView extends CollectionSubView() {
             // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed
             if (tab.DashDoc.embedContainer === this.Document) tab.DashDoc.embedContainer = undefined;
             if (!tab.DashDoc.embedContainer) Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
-            Doc.RemoveDocFromList(tab.DashDoc[DocData], 'proto_embeddings', tab.DashDoc);
+            Doc.RemoveEmbedding(tab.DashDoc, tab.DashDoc);
         }
         if (CollectionDockingView.Instance) {
             const dview = CollectionDockingView.Instance.Document;
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 0a1946f09..09701ddb5 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -128,6 +128,10 @@
     position: relative;
     z-index: 1;
 
+    .treeView-rightButtons > .iconButton-container {
+        min-height: unset;
+    }
+
     .treeView-background {
         width: 100%;
         height: 100%;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 54e8b08b6..9368560e9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -54,6 +54,7 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
 import { RichTextField } from '../../../../fields/RichTextField';
+import { CompileScript } from '../../../util/Scripting';
 
 export interface collectionFreeformViewProps {
     NativeWidth?: () => number;
@@ -80,8 +81,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim();
         return !paintFunc
             ? ''
-            : `const dashDiv = document.querySelector('#${this._paintedId}'); 
-        (async () => {  ${paintFunc} })()`;
+            : paintFunc.includes('dashDiv')
+              ? `const dashDiv = document.querySelector('#${this._paintedId}');
+                    (async () => {  ${paintFunc} })()`
+              : paintFunc;
     }
     constructor(props: any) {
         super(props);
@@ -1496,7 +1499,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         this._disposers.paintFunc = reaction(
             () => ({ code: this.paintFunc, first: this._firstRender, width: this.Document._width, height: this.Document._height }),
-            ({ code, first }) => code && !first && eval(code),
+            ({ code, first }) => {
+                if (!code.includes('dashDiv')) {
+                    const script = CompileScript(code, { params: { docView: 'any' }, typecheck: false, editable: true });
+                    if (script.compiled) script.run({ this: this.Document, docView: this.DocumentView?.() });
+                } else code && !first && eval(code);
+            },
             { fireImmediately: true }
         );
 
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 29d121974..29491569a 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -128,7 +128,7 @@
 
     .row-menu {
         display: flex;
-        justify-content: flex-end;
+        justify-content: center;
     }
 }
 
@@ -224,7 +224,10 @@
         display: flex;
         flex-direction: row;
         min-width: 50px;
-        justify-content: flex-end;
+        justify-content: center;
+        .iconButton-container {
+            min-width: unset !important;
+        }
     }
 
     .row-cells {
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index f2fe0dde7..39fea2d2e 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -121,7 +121,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
                         pointerEvents: !this._props.isContentActive() ? 'none' : undefined,
                     }}>
                     <IconButton
-                        tooltip="whether document interations are enabled"
+                        tooltip="whether document interactions are enabled"
                         icon={this.Document._lockedPosition ? <CgLockUnlock size="12px" /> : <CgLock size="12px" />}
                         size={Size.XSMALL}
                         onPointerDown={e =>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 51f4b1a68..b5355fb99 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -456,8 +456,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
     deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
     setToggleDetail = undoable(
-        (defaultLayout: string) =>
-            (this.Document.onClick = ScriptField.MakeScript(
+        (defaultLayout: string, scriptFieldKey: 'onClick') =>
+            (this.Document[scriptFieldKey] = ScriptField.MakeScript(
                 `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
                     .replace('layout_', '')
                     .replace(/^layout$/, 'detail')}", "${defaultLayout}")`,
@@ -1212,7 +1212,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     };
     public noOnClick = () => this._docViewInternal?.noOnClick();
     public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle);
-    public setToggleDetail = (defaultLayout = '') => this._docViewInternal?.setToggleDetail(defaultLayout);
+    public setToggleDetail = (defaultLayout = '', scriptFieldKey = 'onClick') => this._docViewInternal?.setToggleDetail(defaultLayout, scriptFieldKey);
     public onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY);
     public cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents();
     public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 973f90501..b82ab4219 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -67,8 +67,6 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
 import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
-import { CollectionView } from '../../collections/CollectionView';
-import { PaintButtonView } from './PaintButtonView';
 // import * as applyDevTools from 'prosemirror-dev-tools';
 @observer
 export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -488,14 +486,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     // creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@'
+    /**
+     * Searches the text for occurences of any strings that match the names of 'published' documents.  These document
+     * names will begin with an '@' prefix.  However, valid matches within the text can have any of the following formats:
+     *    name, @<name>, or ^@<name>
+     * The last of these is interpreted as an include directive when converting the text into evaluated code in the paint
+     * function of a freeform view that is driven by the text box's text.  The include directive will copy the code of the published
+     * document into the code being evaluated.
+     */
     hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
         const editorView = this._editorView;
         if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
             const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
             var alink: Doc | undefined;
             this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
-                const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
-                if (!sel.$anchor.pos || [autoLinkTerm, StrCast(target.title)].includes(editorView.state.doc.textBetween(sel.$anchor.pos - 1, sel.$to.pos).trim())) {
+                if (
+                    !sel.$anchor.pos ||
+                    autoLinkTerm ===
+                        editorView.state.doc
+                            .textBetween(sel.$anchor.pos - 1, sel.$to.pos)
+                            .trim()
+                            .replace(/[\^@]+/, '')
+                ) {
+                    const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
                     tr = tr.addMark(sel.from, sel.to, splitter);
                     tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
                         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)) {
@@ -668,12 +681,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             let index = 0,
                 foundAt;
             const ep = this.getNodeEndpoints(pm.state.doc, node);
-            const regexp = new RegExp(find.replace('*', ''), 'i');
+            const regexp = new RegExp(find, 'i');
             if (regexp) {
-                while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) {
-                    const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
-                    ret.push(sel);
-                    index = index + foundAt + find.length;
+                var blockOffset = 0;
+                for (var i = 0; i < node.childCount; i++) {
+                    var textContent = '';
+                    while (i < node.childCount && node.child(i).type === pm.state.schema.nodes.text) {
+                        textContent += node.child(i).textContent;
+                        i++;
+                    }
+                    while (ep && (foundAt = textContent.slice(index).search(regexp)) > -1) {
+                        const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + 1), pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + find.length + 1));
+                        ret.push(sel);
+                        index = index + foundAt + find.length;
+                    }
+                    blockOffset += textContent.length;
+                    if (i < node.childCount) blockOffset += node.child(i).nodeSize;
                 }
             }
         } else {
@@ -934,17 +957,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR),
                 icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars',
             });
-        optionItems.push({
-            description: 'Make Paint Function',
-            event: () => {
-                this.dataDoc.layout_painted = CollectionView.LayoutString('painted');
-                this.layoutDoc.layout_fieldKey = 'layout_painted';
-                this.layoutDoc.type_collection = CollectionViewType.Freeform;
-                this.DocumentView?.().setToggleDetail();
-                this.dataDoc.paintFunc = ComputedField.MakeFunction(`toJavascriptString(this['${this.fieldKey}']?.Text)`);
-            },
-            icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
-        });
         !Doc.noviceMode &&
             optionItems.push({
                 description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`,
@@ -1411,9 +1423,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     dashField(node: any, view: any, getPos: any) {
                         return new DashFieldView(node, view, getPos, self);
                     },
-                    paintButton(node: any, view: any, getPos: any) {
-                        return new PaintButtonView(node, view, getPos, self);
-                    },
                     equation(node: any, view: any, getPos: any) {
                         return new EquationView(node, view, getPos, self);
                     },
diff --git a/src/client/views/nodes/formattedText/PaintButtonView.tsx b/src/client/views/nodes/formattedText/PaintButtonView.tsx
deleted file mode 100644
index 74423c772..000000000
--- a/src/client/views/nodes/formattedText/PaintButtonView.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import { action, computed, IReactionDisposer, makeObservable } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import * as ReactDOM from 'react-dom/client';
-import { Doc } from '../../../../fields/Doc';
-import { ObservableReactComponent } from '../../ObservableReactComponent';
-import './DashFieldView.scss';
-import { FormattedTextBox } from './FormattedTextBox';
-import { CollectionViewType } from '../../../documents/DocumentTypes';
-import { CollectionView } from '../../collections/CollectionView';
-import { StrCast } from '../../../../fields/Types';
-
-export class PaintButtonView {
-    dom: HTMLDivElement; // container for label and value
-    root: any;
-    node: any;
-    tbox: FormattedTextBox;
-
-    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-        this.node = node;
-        this.tbox = tbox;
-        this.dom = document.createElement('div');
-        this.dom.style.width = node.attrs.width;
-        this.dom.style.height = node.attrs.height;
-        this.dom.style.position = 'relative';
-        this.dom.style.display = 'inline-block';
-        this.dom.onkeypress = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onkeydown = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onkeyup = function (e: any) {
-            e.stopPropagation();
-        };
-        this.dom.onmousedown = function (e: any) {
-            e.stopPropagation();
-        };
-
-        this.root = ReactDOM.createRoot(this.dom);
-        this.root.render(<PaintButtonViewInternal node={node} getPos={getPos} width={node.attrs.width} height={node.attrs.height} tbox={tbox} />);
-    }
-    destroy() {
-        setTimeout(() => {
-            try {
-                this.root.unmount();
-            } catch {}
-        });
-    }
-    deselectNode() {
-        this.dom.classList.remove('ProseMirror-selectednode');
-    }
-    selectNode() {
-        this.dom.classList.add('ProseMirror-selectednode');
-    }
-}
-
-interface IPaintButtonViewInternal {
-    tbox: FormattedTextBox;
-    width: number;
-    height: number;
-    node: any;
-    getPos: any;
-}
-
-@observer
-export class PaintButtonViewInternal extends ObservableReactComponent<IPaintButtonViewInternal> {
-    _reactionDisposer: IReactionDisposer | undefined;
-    _textBoxDoc: Doc;
-
-    constructor(props: IPaintButtonViewInternal) {
-        super(props);
-        makeObservable(this);
-        this._textBoxDoc = this._props.tbox.Document;
-    }
-
-    return100 = () => 100;
-    @computed get _checked() {
-        return this._props.tbox.Document.onClick ? true : false;
-    }
-
-    onCheckClick = () => {
-        const textView = this._props.tbox.DocumentView?.();
-        if (textView) {
-            const paintedField = 'layout_' + this._props.tbox.fieldKey + 'Painted';
-            const layoutFieldKey = StrCast(textView.layoutDoc.layout_fieldKey, 'layout');
-            if (textView.layoutDoc.onClick) {
-                textView.layoutDoc[paintedField] = undefined;
-                textView.layoutDoc.onClick = undefined;
-            } else {
-                textView.layoutDoc.type_collection = CollectionViewType.Freeform;
-                textView.dataDoc[paintedField] = CollectionView.LayoutString(this._props.tbox.fieldKey);
-                textView.layoutDoc.layout_fieldKey = paintedField;
-                textView.setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', ''));
-                textView.layoutDoc.layout_fieldKey = layoutFieldKey;
-            }
-        }
-    };
-
-    render() {
-        return (
-            <div
-                className="dashFieldView"
-                style={{
-                    width: this._props.width,
-                    height: this._props.height,
-                    pointerEvents: this._props.tbox._props.isSelected() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
-                }}>
-                <input type="checkbox" value="paint" checked={this._checked} onChange={e => this.onCheckClick()} />
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 8f7bc5282..ce2c33fb4 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -68,40 +68,21 @@ export class RichTextRules {
             ),
 
             // ``` create code block
-            textblockTypeInputRule(/^```$/, schema.nodes.code_block),
-            new InputRule(
-                new RegExp(/(^|\n)\^@paint/), // for code blocks '^' means the beginning of the block, not the line, so need to test for \n
-                (state, match, start, end) => {
-                    const { dataDoc, layoutDoc, fieldKey } = this.TextBox;
-                    layoutDoc.type_collection = CollectionViewType.Freeform;
-                    const paintedField = 'layout_' + this.TextBox.fieldKey + 'Painted';
-                    dataDoc[paintedField] = CollectionView.LayoutString(this.TextBox.fieldKey);
-                    const layoutFieldKey = StrCast(layoutDoc.layout_fieldKey);
-                    layoutDoc.layout_fieldKey = paintedField;
-                    this.TextBox.DocumentView?.().setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', ''));
-                    layoutDoc.layout_fieldKey = layoutFieldKey;
-                    const comment = '/* enable as paint function ';
-                    const endComment = ' */\n';
-                    const inCode = state.tr.selection.$anchor.node().type === schema.nodes.code_block;
-                    if (inCode) {
-                        const tr = state.tr
-                            .deleteRange(start, end)
-                            .insertText(comment)
-                            .insert(start + comment.length, schema.nodes.paintButton.create())
-                            .insertText(endComment);
-                        return tr.setSelection(new TextSelection(tr.doc.resolve(start + comment.length + endComment.length + 1)));
-                    } else {
-                        const tr = state.tr
-                            .deleteRange(start, end)
-                            .insertText(comment)
-                            .insert(start + comment.length, schema.nodes.paintButton.create())
-                            .insertText(endComment)
-                            .insert(start + comment.length + endComment.length + 1, schema.nodes.code_block.create());
-                        return tr.setSelection(new TextSelection(tr.doc.resolve(start + comment.length + endComment.length + 3)));
-                    }
-                },
-                { inCode: true }
-            ),
+            new InputRule(/^```$/, (state, match, start, end) => {
+                let $start = state.doc.resolve(start);
+                if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), schema.nodes.code_block)) return null;
+
+                // this enables text with code blocks to be used as a 'paint' function via a styleprovider button that is added to Docs that have an onPaint script
+                this.TextBox.layoutDoc.type_collection = CollectionViewType.Freeform; // make it a freeform when rendered as a collection since those are the only views that know about the paint function
+                const paintedField = 'layout_' + this.TextBox.fieldKey + 'Painted'; // make a layout field key for storing the CollectionView jsx string pointing to the textbox's text
+                this.TextBox.dataDoc[paintedField] = CollectionView.LayoutString(this.TextBox.fieldKey);
+                const layoutFieldKey = StrCast(this.TextBox.layoutDoc.layout_fieldKey); // save the current layout fieldkey
+                this.TextBox.layoutDoc.layout_fieldKey = paintedField; // setup the paint layout field key
+                this.TextBox.DocumentView?.().setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', ''), 'onPaint'); // create the script to toggle between the painted and regular view
+                this.TextBox.layoutDoc.layout_fieldKey = layoutFieldKey; // restore the layout field key to text
+
+                return state.tr.delete(start, end).setBlockType(start, start, schema.nodes.code_block);
+            }),
 
             // %<font-size> set the font size
             new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => {
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 30e3aa5f0..f3fc51671 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -572,6 +572,16 @@ export namespace Doc {
         return false;
     }
 
+    export function RemoveEmbedding(doc: Doc, embedding: Doc) {
+        Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', embedding);
+    }
+    export function AddEmbedding(doc: Doc, embedding: Doc) {
+        Doc.AddDocToList(doc[DocData], 'proto_embeddings', embedding, undefined, undefined, undefined, undefined, undefined, true);
+    }
+    export function GetEmbeddings(doc: Doc) {
+        return DocListCast(Doc.Get(doc[DocData], 'proto_embeddings', true));
+    }
+
     export function MakeEmbedding(doc: Doc, id?: string) {
         const embedding = (!GetT(doc, 'isDataDoc', 'boolean', true) && doc.proto) || doc.type === DocumentType.CONFIG ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
         const layout = Doc.LayoutField(embedding);
@@ -579,20 +589,18 @@ export namespace Doc {
             Doc.SetLayout(embedding, Doc.MakeEmbedding(layout));
         }
         embedding.createdFrom = doc;
-        embedding.proto_embeddingId = doc[DocData].proto_embeddingId = DocListCast(doc[DocData].proto_embeddings).length - 1;
+        embedding.proto_embeddingId = doc[DocData].proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
         !Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`));
         embedding.author = Doc.CurrentUserEmail;
 
-        Doc.AddDocToList(doc[DocData], 'proto_embeddings', embedding);
-
         return embedding;
     }
 
     export function BestEmbedding(doc: Doc) {
         const dataDoc = doc[DocData];
-        const availableEmbeddings = DocListCast(dataDoc.proto_embeddings);
+        const availableEmbeddings = Doc.GetEmbeddings(dataDoc);
         const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail);
-        bestEmbedding && Doc.AddDocToList(dataDoc, 'protoEmbeddings', doc);
+        bestEmbedding && Doc.AddDocToList(dataDoc, 'protoEmbeddings', doc, undefined, undefined, undefined, undefined, undefined, true);
         return bestEmbedding ?? Doc.MakeEmbedding(doc);
     }
 
@@ -951,7 +959,7 @@ export namespace Doc {
             Doc.GetProto(copy).embedContainer = undefined;
             Doc.GetProto(copy).proto_embeddings = new List<Doc>([copy]);
         } else {
-            Doc.AddDocToList(copy[DocData], 'proto_embeddings', copy);
+            Doc.AddEmbedding(copy, copy);
         }
         copy.embedContainer = undefined;
         if (retitle) {
@@ -972,9 +980,10 @@ export namespace Doc {
             Object.keys(doc)
                 .filter(key => key.startsWith('acl'))
                 .forEach(key => (delegate[key] = doc[key]));
-            if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DocData], 'proto_embeddings', delegate);
+            if (!Doc.IsSystem(doc)) Doc.AddEmbedding(doc, delegate);
             title && (delegate.title = title);
             delegate[Initializing] = false;
+            Doc.AddEmbedding(doc, delegate);
             return delegate;
         }
         return undefined;
@@ -995,7 +1004,7 @@ export namespace Doc {
         delegate[Initializing] = true;
         delegate.proto = delegateProto;
         delegate.author = Doc.CurrentUserEmail;
-        Doc.AddDocToList(delegateProto[DocData], 'proto_embeddings', delegate);
+        Doc.AddEmbedding(delegateProto, delegate);
         delegate[Initializing] = false;
         delegateProto[Initializing] = false;
         return delegate;
-- 
cgit v1.2.3-70-g09d2


From 9e0de1f1ee32511cf5c9b3b19accad354c3fda92 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 1 Feb 2024 10:41:35 -0500
Subject: enabled lists to be entered via dashfieldview. changed code/text
 import to allow data to be inserted into template.

---
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx     | 2 +-
 src/client/views/nodes/formattedText/RichTextRules.ts             | 8 ++++++--
 src/fields/Doc.ts                                                 | 5 ++++-
 3 files changed, 11 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9368560e9..bd046d6ff 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1502,7 +1502,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             ({ code, first }) => {
                 if (!code.includes('dashDiv')) {
                     const script = CompileScript(code, { params: { docView: 'any' }, typecheck: false, editable: true });
-                    if (script.compiled) script.run({ this: this.Document, docView: this.DocumentView?.() });
+                    if (script.compiled) script.run({ this: this.DocumentView?.() });
                 } else code && !first && eval(code);
             },
             { fireImmediately: true }
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index ce2c33fb4..2fdd6374a 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -289,7 +289,7 @@ export class RichTextRules {
             // [[fieldKey=value]] => show field and also set its value
             // [[fieldKey:docTitle]] => show field of doc
             new InputRule(
-                new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
+                new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-z,A-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
                 (state, match, start, end) => {
                     const fieldKey = match[1];
                     const docTitle = match[3]?.replace(':', '');
@@ -325,7 +325,11 @@ export class RichTextRules {
                         }
                         return state.tr;
                     }
-                    if (value !== '' && value !== undefined) {
+                    if (value?.includes(',')) {
+                        const values = value.split(',');
+                        const strs = values.some(v => !v.match(/^[-]?[0-9.]$/));
+                        this.Document[DocData][fieldKey] = strs ? new List<string>(values) : new List<number>(values.map(v => Number(v)));
+                    } else if (value !== '' && value !== undefined) {
                         const num = value.match(/^[0-9.]$/);
                         this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
                     }
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index f3fc51671..ab6d0390b 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -67,7 +67,10 @@ export namespace Field {
         // as a kind of macro to include the content of those documents
         Doc.MyPublishedDocs.forEach(doc => {
             const regex = new RegExp(`^\\^${doc.title}\\s`, 'm');
-            script = script.replace(regex, Cast(doc.text, RichTextField, null)?.Text ?? '');
+            const sections = (Cast(doc.text, RichTextField, null)?.Text ?? '').split('--DOCDATA--');
+            if (script.match(regex)) {
+                script = script.replace(regex, sections[0]) + (sections.length > 1 ? sections[1] : '');
+            }
         });
         return script;
     }
-- 
cgit v1.2.3-70-g09d2


From a888150f2e220eff4629551aa03813f92aa0b12f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 5 Feb 2024 22:56:04 -0500
Subject: changed backgroundColor to set on dataDocs. fixed pivoting on tags. 
 fixed link description editing popup.  fixed showing link editor in property
 view - still some weirdness in what is selected.  fixed dragging tree view
 items to set dragData.treeview and be able to drop at bottom of tree. fixed
 addFolder menu option for TreeViews to add locally..  added a function to
 collect all docs of a given tag into a collection.  fixed setting default
 font size to update autolayouts.  changed dropping link onto same collection
 to not leave pushpin. fixed minimap thumb updating.  added fieldvalue
 dropdown for dashFieldViews in text.

---
 src/client/documents/Documents.ts                  |  4 +-
 src/client/util/CurrentUserUtils.ts                | 22 ++-----
 src/client/util/DragManager.ts                     |  2 +-
 src/client/util/LinkManager.ts                     |  4 +-
 src/client/util/SearchUtil.ts                      |  7 +-
 src/client/util/SelectionManager.ts                |  4 +-
 src/client/util/SettingsManager.tsx                | 12 +++-
 src/client/views/DocComponent.tsx                  |  4 +-
 src/client/views/DocumentDecorations.tsx           |  3 +
 src/client/views/FilterPanel.tsx                   |  6 +-
 .../views/PropertiesDocBacklinksSelector.tsx       |  2 +-
 src/client/views/PropertiesSection.scss            | 26 ++++----
 src/client/views/PropertiesView.tsx                | 74 ++++++++++++----------
 src/client/views/StyleProvider.tsx                 |  2 +-
 .../views/collections/CollectionTimeView.tsx       |  7 +-
 .../views/collections/CollectionTreeView.tsx       | 73 ++++++++++++++-------
 src/client/views/collections/TabDocView.tsx        |  1 +
 src/client/views/collections/TreeView.scss         |  6 +-
 src/client/views/collections/TreeView.tsx          | 34 +++++-----
 .../CollectionFreeFormLinkView.tsx                 |  6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 30 +++------
 src/client/views/global/globalScripts.ts           |  7 +-
 src/client/views/linking/LinkMenuItem.tsx          |  6 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 34 ++++++++--
 src/client/views/nodes/LabelBox.tsx                |  2 +-
 src/client/views/nodes/LinkBox.scss                | 25 ++++++++
 src/client/views/nodes/LinkBox.tsx                 | 69 +++++++++++++-------
 src/client/views/nodes/LinkDescriptionPopup.tsx    | 18 ++++--
 src/client/views/nodes/LinkDocPreview.tsx          |  4 +-
 .../views/nodes/formattedText/DashFieldView.scss   | 10 +++
 .../views/nodes/formattedText/DashFieldView.tsx    | 27 ++++++--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 11 ++--
 .../views/nodes/formattedText/RichTextRules.ts     |  2 +-
 src/fields/Doc.ts                                  |  2 +-
 src/fields/List.ts                                 |  2 +-
 36 files changed, 343 insertions(+), 207 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index cc983ffa7..95058da22 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -437,7 +437,7 @@ export class DocumentOptions {
     clickFactory?: DOCt = new DocInfo('document to create when clicking on a button with a suitable onClick script', false);
     onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
     target?: Doc; // available for use in scripts.  used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script)
-
+    tags?: LISTt = new ListInfo('hashtags added to document, typically using a text view', true);
     treeView_HideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view');
     treeView_HideUnrendered?: BOOLt = new BoolInfo("tells tree view not to display documents that have an 'layout_unrendered' tag unless they also have a treeView_FieldKey tag (presBox)");
     treeView_HideHeaderIfTemplate?: BOOLt = new BoolInfo('whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)');
@@ -1459,7 +1459,7 @@ export namespace DocUtils {
 
         const makeLink = action((linkDoc: Doc, showPopup?: number[]) => {
             if (showPopup) {
-                LinkManager.currentLink = linkDoc;
+                LinkManager.Instance.currentLink = linkDoc;
 
                 TaskCompletionBox.textDisplayed = 'Link Created';
                 TaskCompletionBox.popupX = showPopup[0];
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 31f0308b7..714e33d25 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -17,7 +17,7 @@ import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
 import { DocUtils, Docs, DocumentOptions, FInfo } from "../documents/Documents";
 import { DashboardView } from "../views/DashboardView";
 import { OverlayView } from "../views/OverlayView";
-import { TreeViewType } from "../views/collections/CollectionTreeView";
+import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView";
 import { Colors } from "../views/global/globalEnums";
 import { media_state } from "../views/nodes/AudioBox";
 import { OpenWhere } from "../views/nodes/DocumentView";
@@ -78,7 +78,7 @@ export class CurrentUserUtils {
         const tempClicks = DocCast(doc[field]);
         const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, isSystem: true};
         const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
-            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCastAsync(documentView?.containerViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([self])))"},
+            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCastAsync(documentView?.containerViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([this])))"},
             { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
@@ -502,29 +502,21 @@ export class CurrentUserUtils {
     static setupFilesystem(doc: Doc, field:string) {
         var myFilesystem = DocCast(doc[field]);
         
-        const newFolder = `TreeView_addNewFolder()`;
         const newFolderOpts: DocumentOptions = {
-            _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
+            _forceActive: true, _dragOnlyWithinContainer: true, _embedContainer: Doc.MyFilesystem, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
             title: "New folder", color: Colors.BLACK, btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", isSystem: true
         };
-        const newFolderScript = { onClick: newFolder};
+        const newFolderScript = { onClick: CollectionTreeView.AddTreeFunc};
         const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.layout_headerButton), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript);
         
         const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _forceActive: true, 
             title: "My Documents", layout_headerButton: newFolderButton, treeView_HideTitle: true, dropAction: 'add', isSystem: true,
             isFolder: true, treeView_Type: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true, 
             treeView_TruncateTitleWidth: 350, ignoreClick: true, childDragAction: "embed",
-            childContextMenuLabels: new List<string>(["Create new folder"]),
-            childContextMenuIcons: new List<string>(["plus"]),
             layout_explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
         };
         const fileFolders = new Set(DocListCast(DocCast(doc[field])?.data));
-        myFilesystem = DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, Array.from(fileFolders));
-        const childContextMenuScripts = [newFolder];
-        if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) {
-            myFilesystem.childContextMenuScripts = new List<ScriptField>(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!));
-        }
-        return myFilesystem;
+        return  DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, Array.from(fileFolders));
     }
 
     /// initializes the panel displaying docs that have been recently closed
@@ -544,8 +536,8 @@ export class CurrentUserUtils {
             toolTip: "Empty recently closed",};
         DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("this.target")});
 
-        if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script?.script.originalScript === clearAll("self"))) {
-            recentlyClosed.contextMenuScripts = new List<ScriptField>([ScriptField.MakeScript(clearAll("self"))!])
+        if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script?.script.originalScript === clearAll("this"))) {
+            recentlyClosed.contextMenuScripts = new List<ScriptField>([ScriptField.MakeScript(clearAll("this"))!])
         }
         return recentlyClosed;
     }
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index a6ad0f1b3..071b25a0e 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -243,7 +243,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.draggedViews.forEach(view => view.props.dragStarting?.(dragData));
         return true;
     }
 
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 353f28a92..dd3b9bd07 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -23,8 +23,8 @@ import { ScriptingGlobals } from './ScriptingGlobals';
 export class LinkManager {
     @observable static _instance: LinkManager;
     @observable.shallow userLinkDBs: Doc[] = [];
-    @observable public static currentLink: Opt<Doc> = undefined;
-    @observable public static currentLinkAnchor: Opt<Doc> = undefined;
+    @observable public currentLink: Opt<Doc> = undefined;
+    @observable public currentLinkAnchor: Opt<Doc> = undefined;
     public static get Instance() {
         return LinkManager._instance;
     }
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 218667d3e..fff2737b6 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -8,7 +8,7 @@ import { DocOptions, FInfo } from '../documents/Documents';
 export namespace SearchUtil {
     export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
 
-    export function SearchCollection(collectionDoc: Opt<Doc>, query: string, matchKeyNames: boolean) {
+    export function SearchCollection(collectionDoc: Opt<Doc>, query: string, matchKeyNames: boolean, onlyKeys?: string[]) {
         const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
         const blockedKeys = matchKeyNames
             ? []
@@ -27,8 +27,9 @@ export namespace SearchUtil {
                 const dtype = StrCast(doc.type) as DocumentType;
                 if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) {
                     const hlights = new Set<string>();
-                    SearchUtil.documentKeys(doc).forEach(
-                        key => (val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))(
+                    (onlyKeys ?? SearchUtil.documentKeys(doc)).forEach(
+                        key =>
+                            (val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))(
                             matchKeyNames ? key : Field.toString(doc[key] as Field)) 
                         && hlights.add(key)
                     ); // prettier-ignore
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index f2a327445..b79281802 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -51,8 +51,8 @@ export class SelectionManager {
 
     public static DeselectAll = (except?: Doc): void => {
         const found = this.Instance.SelectedViews.find(dv => dv.Document === except);
-        LinkManager.currentLink = undefined;
-        LinkManager.currentLinkAnchor = undefined;
+        LinkManager.Instance.currentLink = undefined;
+        LinkManager.Instance.currentLinkAnchor = undefined;
         runInAction(() => (this.Instance.SelectedSchemaDocument = undefined));
         this.Instance.SelectedViews.forEach(dv => {
             dv.IsSelected = false;
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 5bf9e5b00..682704770 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -317,7 +317,17 @@ export class SettingsManager extends React.Component<{}> {
                     <div className="tab-column-content">
                         {/* <NumberInput/> */}
                         <Group formLabel={'Default Font'}>
-                            <NumberDropdown color={SettingsManager.userColor} numberDropdownType={'input'} min={0} max={50} step={2} type={Type.TERT} number={0} unit={'px'} setNumber={() => {}} />
+                            <NumberDropdown
+                                color={SettingsManager.userColor}
+                                numberDropdownType="slider"
+                                min={0}
+                                max={50}
+                                step={2}
+                                type={Type.PRIM}
+                                number={NumCast(Doc.UserDoc().fontSize, Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')))}
+                                unit={'px'}
+                                setNumber={val => (Doc.UserDoc().fontSize = val + 'px')}
+                            />
                             <Dropdown
                                 items={fontFamilies.map(val => {
                                     return {
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 58be41f53..3c772bd42 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -47,7 +47,7 @@ export interface ViewBoxInterface {
     setData?: (data: Field | Promise<RefField | undefined>) => boolean;
     componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
     dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
-    dragConfig?: (dragData: DragManager.DocumentDragData) => void;
+    dragConfig?: (dragData: DragManager.DocumentDragData) => void; // function to setup dragData in custom way (see TreeViews which add a tree view flag)
     incrementalRendering?: () => void;
     infoUI?: () => JSX.Element | null;
     screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
@@ -57,7 +57,7 @@ export interface ViewBoxInterface {
     search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
 }
 /**
- * DocComponent returns a React base class used by Doc views with accessors for unpacking he Document,layoutDoc, and dataDoc's
+ * DocComponent returns a React base class used by Doc views with accessors for unpacking the Document,layoutDoc, and dataDoc's
  *  (note: this should not be used for the 'Box' views that render the contents of Doc views)
  *  Example derived views: CollectionFreeFormDocumentView, DocumentView, DocumentViewInternal)
  * */
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index dec109d7b..e9c4d9cc5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -114,6 +114,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) {
             this._titleControlString = this._accumulatedTitle;
         } else if (this._titleControlString.startsWith('#')) {
+            if (this._accumulatedTitle.startsWith('-->#')) {
+                SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`)));
+            }
             const titleFieldKey = this._titleControlString.substring(1);
             UndoManager.RunInBatch(
                 () =>
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index c4f65a5ca..818c81c9a 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -117,8 +117,8 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
     //     return this.activeFilters.map(filter => filter.split(Doc.FilterSep)[0]);
     // }
 
-    gatherFieldValues(childDocs: Doc[], facetKey: string) {
-        const valueSet = new Set<string>(StrListCast(this.targetDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]));
+    static gatherFieldValues(childDocs: Doc[], facetKey: string, childFilters: string[]) {
+        const valueSet = new Set<string>(childFilters.map(filter => filter.split(Doc.FilterSep)[1]));
         let rtFields = 0;
         let subDocs = childDocs;
         if (subDocs.length > 0) {
@@ -165,7 +165,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
     @computed get activeRenderedFacetInfos() {
         return new Set(
             Array.from(new Set(Array.from(this._selectedFacetHeaders).concat(this.activeFacetHeaders))).map(facetHeader => {
-                const facetValues = this.gatherFieldValues(this.targetDocChildren, facetHeader);
+                const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.targetDoc.childFilters));
 
                 let nonNumbers = 0;
                 let minVal = Number.MAX_VALUE,
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index 244cd4aa0..cf5105efc 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -25,7 +25,7 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
         const linkAnchor = this.props.Document;
         const other = LinkManager.getOppositeAnchor(link, linkAnchor);
         const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? Cast(other.annotationOn, Doc, null) : other;
-        LinkManager.currentLink = link;
+        LinkManager.Instance.currentLink = link;
         if (otherdoc) {
             otherdoc.hidden = false;
             this.props.addDocTab(Doc.IsDataProto(otherdoc) ? Doc.MakeDelegate(otherdoc) : otherdoc, OpenWhere.toggleRight);
diff --git a/src/client/views/PropertiesSection.scss b/src/client/views/PropertiesSection.scss
index 3f92a70f8..d32da1bf1 100644
--- a/src/client/views/PropertiesSection.scss
+++ b/src/client/views/PropertiesSection.scss
@@ -4,20 +4,20 @@
     .propertiesView-content {
         padding: 10px;
     }
+}
 
-    .propertiesView-sectionTitle {
-        text-align: center;
-        display: flex;
-        padding: 3px 10px;
-        font-size: 14px;
-        font-weight: bold;
-        justify-content: space-between;
-        align-items: center;
+.propertiesView-sectionTitle {
+    text-align: center;
+    display: flex;
+    padding: 3px 10px;
+    font-size: 14px;
+    font-weight: bold;
+    justify-content: space-between;
+    align-items: center;
 
-        .propertiesView-sectionTitle-icon {
-            width: 20px;
-            height: 20px;
-            align-items: flex-end;
-        }
+    .propertiesView-sectionTitle-icon {
+        width: 20px;
+        height: 20px;
+        align-items: flex-end;
     }
 }
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index a7a065a95..07f285eaf 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -110,7 +110,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     componentDidMount() {
         this._disposers.link = reaction(
-            () => LinkManager.currentLink,
+            () => LinkManager.Instance.currentLink,
             link => {
                 link && this.CloseAll();
                 link && (this.openLinks = true);
@@ -173,10 +173,14 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         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));
-            docs.forEach(doc => Object.keys(doc).forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key)));
+            docs.forEach(doc =>
+                Object.keys(doc)
+                    .filter(filter)
+                    .forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key))
+            );
 
             // prettier-ignore
-            Array.from(ids).filter(filter).sort().map(key => {
+            Array.from(ids).sort().map(key => {
                 const multiple = Array.from(docs.reduce((set,doc) => set.add(doc[key]), new Set<FieldResult>()).keys()).length > 1;
                 const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key);
                 const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as Field);
@@ -270,12 +274,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
 
     @computed get links() {
-        const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc;
+        const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc;
         return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this._props.addDocTab} />;
     }
 
     @computed get linkCount() {
-        const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc;
+        const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc;
         var counter = 0;
 
         LinkManager.Links(selAnchor).forEach((l, i) => counter++);
@@ -571,19 +575,19 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         return (
             <div>
                 <EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel={'Title'} fillWidth />
-                {LinkManager.currentLinkAnchor ? (
+                {LinkManager.Instance.currentLinkAnchor ? (
                     <p className="propertiesView-titleExtender">
                         <>
                             <b>Anchor:</b>
-                            {LinkManager.currentLinkAnchor.title}
+                            {LinkManager.Instance.currentLinkAnchor.title}
                         </>
                     </p>
                 ) : null}
-                {LinkManager.currentLink?.title ? (
+                {LinkManager.Instance.currentLink?.title ? (
                     <p className="propertiesView-titleExtender">
                         <>
                             <b>Link:</b>
-                            {LinkManager.currentLink.title}
+                            {LinkManager.Instance.currentLink.title}
                         </>
                     </p>
                 ) : null}
@@ -1222,10 +1226,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
 
     @computed get description() {
-        return Field.toString(LinkManager.currentLink?.link_description as any as Field);
+        return Field.toString(LinkManager.Instance.currentLink?.link_description as any as Field);
     }
     @computed get relationship() {
-        return StrCast(LinkManager.currentLink?.link_relationship);
+        return StrCast(LinkManager.Instance.currentLink?.link_relationship);
     }
     @observable private relationshipButtonColor: string = '';
 
@@ -1235,7 +1239,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     handleDescriptionChange = undoable(
         action((value: string) => {
-            if (LinkManager.currentLink && this.selectedDoc) {
+            if (LinkManager.Instance.currentLink && this.selectedDoc) {
                 this.setDescripValue(value);
             }
         }),
@@ -1244,7 +1248,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     handlelinkRelationshipChange = undoable(
         action((value: string) => {
-            if (LinkManager.currentLink && this.selectedDoc) {
+            if (LinkManager.Instance.currentLink && this.selectedDoc) {
                 this.setlinkRelationshipValue(value);
             }
         }),
@@ -1253,17 +1257,17 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     @undoBatch
     setDescripValue = action((value: string) => {
-        if (LinkManager.currentLink) {
-            Doc.GetProto(LinkManager.currentLink).link_description = value;
+        if (LinkManager.Instance.currentLink) {
+            Doc.GetProto(LinkManager.Instance.currentLink).link_description = value;
         }
     });
 
     @undoBatch
     setlinkRelationshipValue = action((value: string) => {
-        if (LinkManager.currentLink) {
-            const prevRelationship = StrCast(LinkManager.currentLink.link_relationship);
-            LinkManager.currentLink.link_relationship = value;
-            Doc.GetProto(LinkManager.currentLink).link_relationship = value;
+        if (LinkManager.Instance.currentLink) {
+            const prevRelationship = StrCast(LinkManager.Instance.currentLink.link_relationship);
+            LinkManager.Instance.currentLink.link_relationship = value;
+            Doc.GetProto(LinkManager.Instance.currentLink).link_relationship = value;
             const linkRelationshipList = StrListCast(Doc.UserDoc().link_relationshipList);
             const linkRelationshipSizes = NumListCast(Doc.UserDoc().link_relationshipSizes);
             const linkColorList = StrListCast(Doc.UserDoc().link_ColorList);
@@ -1347,20 +1351,20 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     };
 
     toggleLinkProp = (e: React.PointerEvent, prop: string) => {
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.currentLink && (LinkManager.currentLink[prop] = !LinkManager.currentLink[prop]))));
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink[prop] = !LinkManager.Instance.currentLink[prop]))));
     };
 
     @computed get destinationAnchor() {
-        const ldoc = LinkManager.currentLink;
-        const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor;
+        const ldoc = LinkManager.Instance.currentLink;
+        const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor;
         if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch;
         return ldoc ? DocCast(ldoc.link_anchor_2) : ldoc;
     }
 
     @computed get sourceAnchor() {
-        const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor;
+        const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor;
 
-        return selAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink);
+        return selAnchor ?? (LinkManager.Instance.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.Instance.currentLink, this.destinationAnchor) : LinkManager.Instance.currentLink);
     }
 
     toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc, value: any = true, ovalue: any = false, cb: (val: any) => any = val => val) => {
@@ -1386,7 +1390,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                 style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
                 autoComplete={'off'}
                 id="link_relationship_input"
-                value={StrCast(LinkManager.currentLink?.link_relationship)}
+                value={StrCast(LinkManager.Instance.currentLink?.link_relationship)}
                 onKeyDown={this.onRelationshipKey}
                 onBlur={this.onSelectOutRelationship}
                 onChange={e => this.handlelinkRelationshipChange(e.currentTarget.value)}
@@ -1403,7 +1407,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                 autoComplete="off"
                 style={{ textAlign: 'left', color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
                 id="link_description_input"
-                value={StrCast(LinkManager.currentLink?.link_description)}
+                value={StrCast(LinkManager.Instance.currentLink?.link_description)}
                 onKeyDown={this.onDescriptionKey}
                 onBlur={this.onSelectOutDesc}
                 onChange={e => this.handleDescriptionChange(e.currentTarget.value)}
@@ -1425,7 +1429,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 = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!);
+        const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.Instance.currentLink!);
 
         return (
             <>
@@ -1441,7 +1445,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                     <div className="propertiesView-input inline">
                         <p>Show link</p>
                         <button
-                            style={{ background: !LinkManager.currentLink?.link_displayLine ? '' : '#4476f7', borderRadius: 3 }}
+                            style={{ background: !LinkManager.Instance.currentLink?.link_displayLine ? '' : '#4476f7', borderRadius: 3 }}
                             onPointerDown={e => this.toggleLinkProp(e, 'link_displayLine')}
                             onClick={e => e.stopPropagation()}
                             className="propertiesButton">
@@ -1451,7 +1455,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                     <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
                         <p>Auto-move anchors</p>
                         <button
-                            style={{ background: !LinkManager.currentLink?.link_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }}
+                            style={{ background: !LinkManager.Instance.currentLink?.link_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }}
                             onPointerDown={e => this.toggleLinkProp(e, 'link_autoMoveAnchors')}
                             onClick={e => e.stopPropagation()}
                             className="propertiesButton">
@@ -1461,7 +1465,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                     <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
                         <p>Display arrow</p>
                         <button
-                            style={{ background: !LinkManager.currentLink?.link_displayArrow ? '' : '#4476f7', borderRadius: 3 }}
+                            style={{ background: !LinkManager.Instance.currentLink?.link_displayArrow ? '' : '#4476f7', borderRadius: 3 }}
                             onPointerDown={e => this.toggleLinkProp(e, 'link_displayArrow')}
                             onClick={e => e.stopPropagation()}
                             className="propertiesButton">
@@ -1486,7 +1490,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                                 <option value={OpenWhere.add}>Opening in new tab</option>
                                 <option value={OpenWhere.replace}>Replacing current tab</option>
                                 <option value={OpenWhere.inParent}>Opening in same collection</option>
-                                {LinkManager.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
+                                {LinkManager.Instance.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
                             </select>
                         </div>
                         <div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 134px) 50px' }}>
@@ -1671,7 +1675,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
     render() {
         const isNovice = Doc.noviceMode;
-        const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!);
+        const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.Instance.currentLink!);
         if (!this.selectedDoc && !this.isPres) {
             return (
                 <div className="propertiesView" style={{ width: this._props.width }}>
@@ -1692,7 +1696,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                             minWidth: this._props.width,
                         }}>
                         <div className="propertiesView-propAndInfoGrouping">
-                            <div className="propertiesView-title" style={{ width: this._props.width }}>
+                            <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
                                 Properties
                                 <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties')}>
                                     <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} />
@@ -1702,12 +1706,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
 
                         <div className="propertiesView-name">{this.editableTitle}</div>
                         <div className="propertiesView-type"> {this.currentType} </div>
+                        {this.fieldsSubMenu}
                         {this.optionsSubMenu}
                         {this.linksSubMenu}
-                        {!LinkManager.currentLink || !this.openLinks ? null : this.linkProperties}
+                        {!LinkManager.Instance.currentLink || !this.openLinks ? null : this.linkProperties}
                         {this.inkSubMenu}
                         {this.contextsSubMenu}
-                        {this.fieldsSubMenu}
                         {isNovice ? null : this.sharingSubMenu}
                         {this.filtersSubMenu}
                         {isNovice ? null : this.layoutSubMenu}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index fa0be225e..0e38790b6 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -191,7 +191,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 : 0;
         case StyleProp.BackgroundColor: {
             if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item
-            let docColor: Opt<string> = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
+            let docColor: Opt<string> = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?.backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
             // prettier-ignore
             switch (doc?.type) {
                 case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index ee5147428..38f6aa3e7 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -200,8 +200,7 @@ export class CollectionTimeView extends CollectionSubView() {
     }
     menuCallback = (x: number, y: number) => {
         ContextMenu.Instance.clearItems();
-        const docItems: ContextMenuProps[] = [];
-        const keySet: Set<string> = new Set();
+        const keySet: Set<string> = new Set(['tags']);
 
         this.childLayoutPairs.map(pair =>
             this._allFacets
@@ -209,7 +208,9 @@ export class CollectionTimeView extends CollectionSubView() {
                 .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0]))
                 .map(fieldKey => keySet.add(fieldKey))
         );
-        Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' }));
+
+        const docItems: ContextMenuProps[] = Array.from(keySet).map(fieldKey => 
+                     ({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' })); // prettier-ignore
         docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' });
         ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' });
         ContextMenu.Instance.displayMenu(x, y, ':');
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 741013148..786301136 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -8,8 +8,8 @@ import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils';
-import { DocUtils } from '../../documents/Documents';
+import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero, Utils } from '../../../Utils';
+import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
@@ -27,6 +27,7 @@ import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionSubView } from './CollectionSubView';
 import './CollectionTreeView.scss';
 import { TreeView } from './TreeView';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export type collectionTreeViewProps = {
@@ -51,6 +52,7 @@ export enum TreeViewType {
 
 @observer
 export class CollectionTreeView extends CollectionSubView<Partial<collectionTreeViewProps>>() {
+    public static AddTreeFunc = 'addTreeFolder(this.embedContainer)';
     private _treedropDisposer?: DragManager.DragDropDisposer;
     private _mainEle?: HTMLDivElement;
     private _titleRef?: HTMLDivElement | HTMLInputElement | null;
@@ -140,6 +142,17 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this));
     };
 
+    protected onInternalDrop(e: Event, de: DragManager.DropEvent) {
+        const res = super.onInternalDrop(e, de);
+        if (res && de.complete.docDragData) {
+            if (this.Document !== Doc.MyRecentlyClosed)
+                de.complete.docDragData.droppedDocuments.forEach(doc => {
+                    if (this.Document !== Doc.MyRecentlyClosed) Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc);
+                });
+        }
+        return res;
+    }
+
     protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => {
         const dragData = de.complete.docDragData;
         if (dragData) {
@@ -150,9 +163,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         }
     };
 
-    configDrag = (dragData: DragManager.DocumentDragData) => {
-        dragData.treeViewDoc = this.Document;
-    };
+    dragConfig = (dragData: DragManager.DocumentDragData) => (dragData.treeViewDoc = this.Document);
 
     screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight);
 
@@ -163,34 +174,41 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         const value = DocListCast(targetDataDoc[this._props.fieldKey]);
         const result = value.filter(v => !docs.includes(v));
         if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
-        if (result.length !== value.length && doc instanceof Doc) {
-            const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc);
-            const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1];
-            this._props.removeDocument?.(doc);
-            if (ind > 0 && prev) {
-                FormattedTextBox.SetSelectOnLoad(prev);
-                DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false);
+        if (result.length !== value.length) {
+            if (doc instanceof Doc) {
+                const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc);
+                const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1];
+                this._props.removeDocument?.(doc);
+                if (ind > 0 && prev) {
+                    FormattedTextBox.SetSelectOnLoad(prev);
+                    DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false);
+                }
+                return true;
             }
-            return true;
+            return this._props.removeDocument?.(doc) ?? false;
         }
         return false;
     };
 
     @action
     addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
-        const doAddDoc = (doc: Doc | Doc[]) =>
-            (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => {
-                const res = flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before);
-                res && Doc.SetContainer(doc, this.Document);
-                return res;
-            }, true);
+        const doclist = docs instanceof Doc ? [docs] : docs;
+        const addDocRelativeTo = (doc: Doc | Doc[]) => doclist.reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true);
         if (this.Document.resolvedDataDoc instanceof Promise) return false;
-        return relativeTo === undefined ? this._props.addDocument?.(docs) || false : doAddDoc(docs);
+        const res = relativeTo === undefined ? this._props.addDocument?.(docs) || false : addDocRelativeTo(docs);
+        res &&
+            doclist.forEach(doc => {
+                Doc.SetContainer(doc, this.Document);
+                if (this.Document !== Doc.MyRecentlyClosed) Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc);
+            });
+        return res;
     };
     onContextMenu = (e: React.MouseEvent): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
+        const layoutItems: ContextMenuProps[] = [];
+        const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick).script.originalScript === CollectionTreeView.AddTreeFunc;
+        menuDoc && layoutItems.push({ description: 'Create new folder', event: () => CollectionTreeView.addTreeFolder(this.Document), icon: 'paint-brush' });
         if (!Doc.noviceMode) {
-            const layoutItems: ContextMenuProps[] = [];
             layoutItems.push({
                 description: 'Make tree state ' + (this.Document.treeView_OpenIsTransient ? 'persistent' : 'transient'),
                 event: () => (this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient),
@@ -198,7 +216,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             });
             layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields), icon: 'paint-brush' });
             layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle), icon: 'paint-brush' });
-            ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
+        }
+        ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
+        if (!Doc.noviceMode) {
             const existingOnClick = ContextMenu.Instance.findByDescription('OnClick...');
             const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
             onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' });
@@ -467,4 +487,13 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             </div>
         );
     }
+    static addTreeFolder(container: Doc) {
+        TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined };
+        const opts = { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true };
+        return Doc.AddDocToList(container, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id));
+    }
 }
+
+ScriptingGlobals.add(function addTreeFolder(doc: Doc) {
+    CollectionTreeView.addTreeFolder(doc);
+});
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 9bc3ef822..02aa76d82 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -520,6 +520,7 @@ interface TabMiniThumbProps {
     miniLeft: () => number;
 }
 
+@observer
 class TabMiniThumb extends React.Component<TabMiniThumbProps> {
     render() {
         return <div className="miniThumb" style={{ width: `${this.props.miniWidth()}% `, height: `${this.props.miniHeight()}% `, left: `${this.props.miniLeft()}% `, top: `${this.props.miniTop()}% ` }} />;
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 09701ddb5..2ab1a5ac1 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -224,13 +224,13 @@
 }
 
 .treeView-header-above {
-    border-top: black 1px solid;
+    border-top: red 1px solid;
 }
 
 .treeView-header-below {
-    border-bottom: black 1px solid;
+    border-bottom: red 1px solid;
 }
 
 .treeView-header-inside {
-    border: black 1px solid;
+    border: red 1px solid;
 }
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index be5737a25..85f7cf7fe 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -383,7 +383,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     makeFolder = () => {
         const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true });
         TreeView._editTitleOnLoad = { id: folder[Id], parent: this._props.parentTreeView };
-        return this._props.addDocument(folder);
+        return this.localAdd(folder);
     };
 
     preTreeDrop = (e: Event, de: DragManager.DropEvent, docDropAction: dropActionType) => {
@@ -424,6 +424,16 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         return false;
     };
 
+    localAdd = (doc: Doc | Doc[]) => {
+        const innerAdd = (doc: Doc) => {
+            const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
+            const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
+            dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
+            return added;
+        };
+        return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
+    };
+
     dropping: boolean = false;
     dropDocuments(
         droppedDocuments: Doc[],
@@ -436,16 +446,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         canEmbed?: boolean
     ) {
         const parentAddDoc = (doc: Doc | Doc[]) => this._props.addDocument(doc, undefined, undefined, before);
-        const localAdd = (doc: Doc | Doc[]) => {
-            const innerAdd = (doc: Doc) => {
-                const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
-                const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
-                dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
-                return added;
-            };
-            return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
-        };
-        const addDoc = inside ? localAdd : parentAddDoc;
+
+        const addDoc = inside ? this.localAdd : parentAddDoc;
         const canAdd = !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add') || forceAdd;
         if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) {
             const move = (!dropAction || canEmbed || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument;
@@ -839,14 +841,13 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     };
     contextMenuItems = () => {
         const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' };
-        const folderOp = this.childDocs?.length ? [makeFolder] : [];
         const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(this), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' };
         const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Focus or Open' };
         const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Reopen' };
         return [
             ...(this._props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.Document })?.result)),
             ...(this.Document.isFolder
-                ? folderOp
+                ? [makeFolder]
                 : Doc.IsSystem(this.Document)
                   ? []
                   : this.treeView.fileSysMode && this.Document === this.Document[DocData]
@@ -993,6 +994,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 onClickScript={this.onChildClick}
                 onDoubleClickScript={this.onChildDoubleClick}
                 dragAction={this._props.dragAction}
+                dragConfig={this.treeView.dragConfig}
                 moveDocument={this.move}
                 removeDocument={this._props.removeDoc}
                 ScreenToLocalTransform={this.getTransform}
@@ -1328,9 +1330,3 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             });
     }
 }
-
-ScriptingGlobals.add(function TreeView_addNewFolder() {
-    TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined };
-    const opts = { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true };
-    return Doc.AddDocToList(Doc.MyFilesystem, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id));
-});
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f0a31a8c6..a45a1fb0f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -127,7 +127,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
             action(() => {
                 SelectionManager.DeselectAll();
                 SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true);
-                LinkManager.currentLink = this._props.LinkDocs[0];
+                LinkManager.Instance.currentLink = this._props.LinkDocs[0];
                 this.toggleProperties();
                 // OverlayView.Instance.addElement(
                 //     <LinkEditor sourceDoc={this._props.A.Document} linkDoc={this._props.LinkDocs[0]}
@@ -184,7 +184,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
     onClickLine = () => {
         SelectionManager.DeselectAll();
         SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true);
-        LinkManager.currentLink = this._props.LinkDocs[0];
+        LinkManager.Instance.currentLink = this._props.LinkDocs[0];
         this.toggleProperties();
     };
 
@@ -295,7 +295,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
                     </filter>
                 </defs>
                 <path
-                    filter={LinkManager.currentLink === link ? 'url(#outline)' : ''}
+                    filter={LinkManager.Instance.currentLink === link ? 'url(#outline)' : ''}
                     fill="pink"
                     stroke="antiquewhite"
                     strokeWidth="4"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bd046d6ff..b08221f99 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -424,27 +424,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             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 =
-                !linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.Document
-                    ? Docs.Create.TextDocument('', { _width: 200, _height: 75, x, y, title: 'dropped annotation' })
-                    : Docs.Create.FontIconDocument({
-                          title: 'anchor',
-                          icon_label: '',
-                          followLinkToggle: true,
-                          icon: 'map-pin',
-                          x,
-                          y,
-                          backgroundColor: '#ACCEF7',
-                          layout_hideAllLinks: true,
-                          layout_hideLinkButton: true,
-                          _width: 15,
-                          _height: 15,
-                          _xPadding: 0,
-                          onClick: FollowLinkScript(),
-                      });
+            const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x, y, title: 'dropped annotation' });
             added = this._props.addDocument?.(source) ? true : false;
             de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
-
+            if (de.complete.linkDocument) {
+                de.complete.linkDocument.layout_isSvg = true;
+                this.addDocument(de.complete.linkDocument);
+            }
             e.stopPropagation();
             !added && e.preventDefault();
             return added;
@@ -1651,9 +1637,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     toggleResetView = () => {
         this.dataDoc[this.autoResetFieldKey] = !this.dataDoc[this.autoResetFieldKey];
         if (this.dataDoc[this.autoResetFieldKey]) {
-            this.dataDoc[this.panXFieldKey + '_reset'] = this.dataDoc[this.panXFieldKey];
-            this.dataDoc[this.panYFieldKey + '_reset'] = this.dataDoc[this.panYFieldKey];
-            this.dataDoc[this.scaleFieldKey + '_reset'] = this.dataDoc[this.scaleFieldKey];
+            this.dataDoc[this.panXFieldKey + '_reset'] = this.layoutDoc[this.panXFieldKey];
+            this.dataDoc[this.panYFieldKey + '_reset'] = this.layoutDoc[this.panYFieldKey];
+            this.dataDoc[this.scaleFieldKey + '_reset'] = this.layoutDoc[this.scaleFieldKey];
         }
     };
 
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 3084a7972..813cb9338 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -20,6 +20,7 @@ import { DocumentView } from '../nodes/DocumentView';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 import { WebBox } from '../nodes/WebBox';
 import { VideoBox } from '../nodes/VideoBox';
+import { DocData } from '../../../fields/DocSymbols';
 
 ScriptingGlobals.add(function IsNoneSelected() {
     return SelectionManager.Views.length <= 0;
@@ -57,16 +58,16 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
                 obj[fieldKey] = color;
                 CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj);
             } else {
-                dv.Document['_' + fieldKey] = color;
+                dv.Document[DocData][fieldKey] = color;
             }
         });
     } else {
-        const selected = SelectionManager.Docs.length ? SelectionManager.Docs : LinkManager.currentLink ? [LinkManager.currentLink] : [];
+        const selected = SelectionManager.Docs.length ? SelectionManager.Docs : LinkManager.Instance.currentLink ? [LinkManager.Instance.currentLink] : [];
         if (checkResult) {
             return selected.lastElement()?._backgroundColor ?? 'transparent';
         }
         SetActiveFillColor(color ?? 'transparent');
-        selected.forEach(doc => (doc._backgroundColor = color));
+        selected.forEach(doc => (doc[DocData].backgroundColor = color));
     }
 });
 
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index ad6deeefb..7427f4310 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -92,8 +92,8 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                     DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight);
                 } else {
                     SelectionManager.SelectView(this._props.docView, false);
-                    LinkManager.currentLink = this._props.linkDoc === LinkManager.currentLink ? undefined : this._props.linkDoc;
-                    LinkManager.currentLinkAnchor = LinkManager.currentLink ? this.sourceAnchor : undefined;
+                    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) {
                         setTimeout(action(() => (SettingsManager.Instance.propertiesWidth = 250)));
@@ -159,7 +159,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                 style={{
                     fontSize: this._hover ? 'larger' : undefined,
                     fontWeight: this._hover ? 'bold' : undefined,
-                    background: LinkManager.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+                    background: LinkManager.Instance.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
                 }}>
                 <div className="linkMenu-item-content expand-two">
                     <div
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index d1805308d..2a68d2bf6 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -157,7 +157,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
                 startLink = DocumentLinksButton.StartLinkView?.ComponentView?.getAnchor?.(true) || startLink;
                 const linkDoc = DocUtils.MakeLink(startLink, endLink, { link_relationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined });
 
-                LinkManager.currentLink = linkDoc;
+                LinkManager.Instance.currentLink = linkDoc;
 
                 if (linkDoc) {
                     if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b5355fb99..862dae83a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,7 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { Dropdown, DropdownType, Type } from 'browndash-components';
 import { Howl } from 'howler';
-import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
@@ -20,13 +20,14 @@ import { DocServer } from '../../DocServer';
 import { Networking } from '../../Network';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DocOptions, DocUtils, Docs, FInfo } from '../../documents/Documents';
+import { DocOptions, DocUtils, Docs } from '../../documents/Documents';
 import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { FollowLinkScript } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SearchUtil } from '../../util/SearchUtil';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { SharingManager } from '../../util/SharingManager';
@@ -106,6 +107,7 @@ export interface DocumentViewProps extends FieldViewSharedProps {
     NativeWidth?: () => number;
     NativeHeight?: () => number;
     contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
+    dragConfig?: (data: DragManager.DocumentDragData) => void;
     dragStarting?: () => void;
     dragEnding?: () => void;
 }
@@ -288,7 +290,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             dragData.moveDocument = this._props.moveDocument;
             dragData.draggedViews = [docView];
             dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
-            this._componentView?.dragConfig?.(dragData);
+            (this._props.dragConfig ?? this._componentView?.dragConfig)?.(dragData);
             DragManager.StartDocumentDrag(
                 selected.map(dv => dv.ContentDiv!),
                 dragData,
@@ -1466,7 +1468,7 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff
 });
 
 ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
-    const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
+    const collectedLinks = DocListCast(linkCollection[DocData].data);
     let wid = NumCast(linkSource._width);
     let embedding: Doc | undefined;
     const links = LinkManager.Links(linkSource);
@@ -1485,3 +1487,27 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
     embedding && DocServer.UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise
     return links;
 });
+ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
+    const tag = StrCast(collection.title).split('-->')[1];
+    const matchedTags = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, tag, false, ['tags']).keys());
+    const collectionDocs = DocListCast(collection[DocData].data).concat(collection);
+    let wid = 100;
+    let created = false;
+    const matchedDocs = matchedTags
+        .filter(tagDoc => !Doc.AreProtosEqual(collection, tagDoc))
+        .map(tagDoc => {
+            let embedding = collectionDocs.find(doc => Doc.AreProtosEqual(tagDoc, doc));
+            if (!embedding) {
+                embedding = Doc.MakeEmbedding(tagDoc);
+                embedding.x = wid;
+                embedding.y = 0;
+                embedding._lockedPosition = false;
+                wid += NumCast(tagDoc._width);
+                created = true;
+            }
+            return embedding;
+        });
+
+    created && (collection[DocData].data = new List<Doc>(matchedDocs));
+    return true;
+});
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 10eeff08d..fd3074a88 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -88,7 +88,7 @@ export class LabelBox extends ViewBoxBaseComponent<LabelBoxProps>() {
 
     @observable _mouseOver = false;
     @computed get hoverColor() {
-        return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
+        return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor);
     }
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
index 767f0291b..484fb301e 100644
--- a/src/client/views/nodes/LinkBox.scss
+++ b/src/client/views/nodes/LinkBox.scss
@@ -5,3 +5,28 @@
 .linkBox-container {
     width: 100%;
 }
+
+.linkBox {
+    transition: inherit;
+    pointer-events: none;
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    path {
+        transition: inherit;
+        fill: transparent;
+    }
+    svg {
+        transition: inherit;
+        overflow: visible;
+    }
+    text {
+        cursor: default;
+        text-anchor: middle;
+        font-size: 12;
+        stroke: black;
+    }
+    circle {
+        cursor: default;
+    }
+}
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 8b6293806..7ade846e7 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils';
+import { aggregateBounds, emptyFunction, returnAlways, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
 import { DocumentManager } from '../../util/DocumentManager';
 import { Transform } from '../../util/Transform';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm';
@@ -13,6 +13,8 @@ import { StyleProp } from '../StyleProvider';
 import { ComparisonBox } from './ComparisonBox';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LinkBox.scss';
+import { LinkDescriptionPopup } from './LinkDescriptionPopup';
+import { LinkManager } from '../../util/LinkManager';
 
 @observer
 export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -97,8 +99,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     ) {
                         this.layoutDoc.x = params?.lx;
                         this.layoutDoc.y = params?.ty;
-                        this.layoutDoc._width = params.rx - params?.lx;
-                        this.layoutDoc._height = params?.by - params?.ty;
+                        this.layoutDoc._width = Math.max(1, params.rx - params?.lx);
+                        this.layoutDoc._height = Math.max(1, params?.by - params?.ty);
                     }
                 } else {
                     this.layoutDoc._width = Math.max(50, NumCast(this.layoutDoc._width));
@@ -108,6 +110,22 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             { fireImmediately: true }
         );
     }
+    descriptionDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, returnFalse, () => {
+            LinkManager.Instance.currentLink = this.Document;
+            LinkDescriptionPopup.Instance.popupX = e.clientX;
+            LinkDescriptionPopup.Instance.popupY = e.clientY;
+            LinkDescriptionPopup.Instance.display = true;
+
+            const rect = document.body.getBoundingClientRect();
+            if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
+                LinkDescriptionPopup.Instance.popupX -= 190;
+            }
+            if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
+                LinkDescriptionPopup.Instance.popupY -= 40;
+            }
+        });
+    };
     componentWillUnmount(): void {
         this.disposer?.();
     }
@@ -117,19 +135,21 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
             const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
 
-            const bez = new Bezier(this.renderProps.pts.map(p => ({ x: p[0], y: p[1] })));
-            const text = bez.get(0.5);
-            const linkDesc = StrCast(this.dataDoc.link_description) || 'description';
+            const { pts, lx, ty, rx, by } = this.renderProps;
+            const bez = new Bezier(pts.map(p => ({ x: p[0] - lx, y: p[1] - ty })));
+            const { x, y } = bez.get(0.5);
+            const linkDesc = StrCast(this.dataDoc.link_description) || ' ';
             const strokeWidth = NumCast(this.dataDoc.stroke_width, 4);
             const dash = StrCast(this.Document.stroke_dash);
             const strokeDasharray = dash && Number(dash) ? String(strokeWidth * Number(dash)) : undefined;
-            const { pts, lx, ty, rx, by } = this.renderProps;
+            const pointerEvents = this._props.pointerEvents?.() === 'none' ? 'none' : 'all';
+            const stroke = highlightColor ?? 'lightblue';
             return (
-                <div style={{ transition: 'inherit', pointerEvents: 'none', position: 'absolute', width: '100%', height: '100%' }}>
-                    <svg width={Math.max(100, rx - lx)} height={Math.max(100, by - ty)} style={{ transition: 'inherit', overflow: 'visible' }}>
+                <div className="linkBox">
+                    <svg width={Math.max(100, rx - lx)} height={Math.max(100, by - ty)}>
                         <defs>
                             <filter x="0" y="0" width="1" height="1" id={`${this.Document[Id] + 'background'}`}>
-                                <feFlood floodColor={`${StrCast(this.layoutDoc._backgroundColor, 'lightblue')}`} result="bg" />
+                                <feFlood floodColor={`${highlightColor ?? StrCast(this.layoutDoc._backgroundColor, 'lightblue')}`} result="bg" />
                                 <feMerge>
                                     <feMergeNode in="bg" />
                                     <feMergeNode in="SourceGraphic" />
@@ -137,26 +157,27 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             </filter>
                         </defs>
                         <path
-                            className="collectionfreeformlinkview-linkLine"
                             style={{
                                 pointerEvents: this._props.pointerEvents?.() === 'none' ? 'none' : 'visibleStroke', //
-                                stroke: highlightColor ?? 'lightblue',
+                                stroke,
                                 strokeDasharray,
                                 strokeWidth,
-                                transition: 'inherit',
                             }}
-                            d={`M ${pts[1][0] - lx} ${pts[1][1] - ty} C ${pts[1][0] + pts[1][0] - pts[0][0] - lx} ${pts[1][1] + pts[1][1] - pts[0][1] - ty}, 
-                            ${pts[2][0] + pts[2][0] - pts[3][0] - lx} ${pts[2][1] + pts[2][1] - pts[3][1] - ty}, ${pts[2][0] - lx} ${pts[2][1] - ty}`}
+                            d={`M ${bez.points[0].x} ${bez.points[0].y} C ${bez.points[1].x} ${bez.points[1].y}, 
+                            ${bez.points[2].x} ${bez.points[2].y}, ${bez.points[3].x} ${bez.points[3].y}`}
                         />
-                        <text
-                            filter={`url(#${this.Document[Id] + 'background'})`}
-                            style={{ pointerEvents: this._props.pointerEvents?.() === 'none' ? 'none' : 'all', textAnchor: 'middle', fontSize: '12', stroke: 'black' }}
-                            x={text.x - lx}
-                            y={text.y - ty}>
-                            <tspan>&nbsp;</tspan>
-                            <tspan dy="2">{linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')}</tspan>
-                            <tspan dy="2">&nbsp;</tspan>
-                        </text>
+                        {!linkDesc.trim().length ? (
+                            <circle r={5} onPointerDown={this.descriptionDown} style={{ fill: stroke, pointerEvents }} cx={x} cy={y} />
+                        ) : (
+                            <text
+                                onPointerDown={this.descriptionDown} //
+                                filter={`url(#${this.Document[Id] + 'background'})`}
+                                style={{ pointerEvents, background: stroke }}
+                                x={x}
+                                y={y}>
+                                <tspan style={{ background: stroke }}>&nbsp;{linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')}&nbsp;</tspan>
+                            </text>
+                        )}
                     </svg>
                 </div>
             );
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 13f0ac4fc..1645d0813 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,10 +1,11 @@
-import { action, makeObservable, observable } from 'mobx';
+import { action, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DocData } from '../../../fields/DocSymbols';
 import { LinkManager } from '../../util/LinkManager';
 import './LinkDescriptionPopup.scss';
 import { TaskCompletionBox } from './TaskCompletedBox';
+import { StrCast } from '../../../fields/Types';
 
 @observer
 export class LinkDescriptionPopup extends React.Component<{}> {
@@ -31,21 +32,26 @@ export class LinkDescriptionPopup extends React.Component<{}> {
     onDismiss = (add: boolean) => {
         this.display = false;
         if (add) {
-            LinkManager.currentLink && (LinkManager.currentLink[DocData].link_description = this.description);
+            LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink[DocData].link_description = this.description);
         }
+        this.description = '';
     };
 
     @action
     onClick = (e: PointerEvent) => {
         if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
             this.display = false;
+            this.description = '';
             TaskCompletionBox.taskCompleted = false;
         }
     };
 
-    @action
     componentDidMount() {
         document.addEventListener('pointerdown', this.onClick, true);
+        reaction(
+            () => this.display,
+            display => display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description))
+        );
     }
 
     componentWillUnmount() {
@@ -65,8 +71,10 @@ export class LinkDescriptionPopup extends React.Component<{}> {
                     className="linkDescriptionPopup-input"
                     onKeyDown={e => e.stopPropagation()}
                     onKeyPress={e => e.key === 'Enter' && this.onDismiss(true)}
-                    placeholder={'(Optional) Enter link description...'}
-                    onChange={e => this.descriptionChanged(e)}></input>
+                    value={this.description}
+                    placeholder={this.description || '(Optional) Enter link description...'}
+                    onChange={e => this.descriptionChanged(e)}
+                />
                 <div className="linkDescriptionPopup-btn">
                     <div className="linkDescriptionPopup-btn-dismiss" onPointerDown={e => this.onDismiss(false)}>
                         {' '}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 3f793b85e..ae25ff179 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -152,8 +152,8 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
             returnFalse,
             emptyFunction,
             action(() => {
-                LinkManager.currentLink = this._linkDoc;
-                LinkManager.currentLinkAnchor = this._linkSrc;
+                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;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 3426ba1a7..7a0ff8776 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -5,6 +5,16 @@
     display: inline-flex;
     align-items: center;
 
+    select {
+        display: none;
+    }
+
+    &:hover {
+        select {
+            display: unset;
+        }
+    }
+
     .dashFieldView-enumerables {
         width: 10px;
         height: 10px;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 18286267a..ec0b76aa8 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -4,21 +4,24 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
-import { Doc, Field } from '../../../../fields/Doc';
+import Select from 'react-select';
+import { Doc, DocListCast, Field } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast } from '../../../../fields/Types';
+import { Cast, StrCast } from '../../../../fields/Types';
 import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { Transform } from '../../../util/Transform';
+import { undoable, undoBatch } from '../../../util/UndoManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { OpenWhere } from '../DocumentView';
 import './DashFieldView.scss';
 import { FormattedTextBox } from './FormattedTextBox';
+import { FilterPanel } from '../../FilterPanel';
 
 export class DashFieldView {
     dom: HTMLDivElement; // container for label and value
@@ -97,7 +100,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
     _reactionDisposer: IReactionDisposer | undefined;
     _textBoxDoc: Doc;
     _fieldKey: string;
-    _fieldStringRef = React.createRef<HTMLSpanElement>();
+    _fieldRef = React.createRef<HTMLDivElement>();
     @observable _dashDoc: Doc | undefined = undefined;
     @observable _expanded = false;
 
@@ -180,10 +183,22 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
         });
     };
 
+    @undoBatch
+    selectVal = (event: React.ChangeEvent<HTMLSelectElement> | undefined) => {
+        event && this._dashDoc && (this._dashDoc[this._fieldKey] = event.target.value);
+    };
+
+    @computed get values() {
+        const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []);
+
+        return vals.strings.map(facet => ({ value: facet, label: facet }));
+    }
+
     render() {
         return (
             <div
                 className="dashFieldView"
+                ref={this._fieldRef}
                 style={{
                     width: this._props.width,
                     height: this._props.height,
@@ -194,8 +209,12 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                         {(this._textBoxDoc === this._dashDoc ? '' : this._dashDoc?.title + ':') + this._fieldKey}
                     </span>
                 )}
-
                 {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
+                <select onChange={this.selectVal} style={{ height: '10px', width: '15px', fontSize: '12px', background: 'transparent' }}>
+                    {this.values.map(val => (
+                        <option value={val.value}>{val.label}</option>
+                    ))}
+                </select>
             </div>
         );
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b82ab4219..f2c4c6c8f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorView } from 'prosemirror-view';
 import * as React from 'react';
 import { BsMarkdownFill } from 'react-icons/bs';
 import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, 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';
@@ -67,6 +67,7 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
 import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
+import Select from 'react-select';
 // import * as applyDevTools from 'prosemirror-dev-tools';
 @observer
 export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -360,7 +361,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         accumTags.push(node.attrs.fieldKey);
                     }
                 });
-                dataDoc.tags = accumTags.length ? new List<string>(Array.from(new Set<string>(accumTags))) : undefined;
+                if (accumTags.some(atag => !StrListCast(dataDoc.tags).includes(atag))) {
+                    dataDoc.tags = new List<string>(Array.from(new Set<string>(accumTags)));
+                }
 
                 let unchanged = true;
                 if (this._applyingChange !== this.fieldKey && (force || removeSelection(newJson) !== removeSelection(prevData?.Data))) {
@@ -1189,8 +1192,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this._cachedLinks = LinkManager.Links(this.Document);
         this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
         this._disposers.layout_autoHeight = reaction(
-            () => this.layout_autoHeight,
-            layout_autoHeight => layout_autoHeight && this.tryUpdateScrollHeight()
+            () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize }),
+            (autoHeight, fontSize) => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
         );
         this._disposers.highlights = reaction(
             () => Array.from(FormattedTextBox._globalHighlights).slice(),
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 2fdd6374a..9bd41f42c 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -264,7 +264,7 @@ export class RichTextRules {
                 return null;
             }),
 
-            // stop using active style
+            // toggle alternate text UI  %/
             new InputRule(new RegExp(/%\//), (state, match, start, end) => {
                 setTimeout(this.TextBox.cycleAlternateText);
                 return state.tr.deleteRange(start, end);
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ab6d0390b..3cd4efcf7 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -390,7 +390,7 @@ export class Doc extends RefField {
 
 export namespace Doc {
     export function SetContainer(doc: Doc, container: Doc) {
-        doc.embedContainer = container;
+        container !== Doc.MyRecentlyClosed && (doc.embedContainer = container);
     }
     export function RunCachedUpdate(doc: Doc, field: string) {
         const update = doc[CachedUpdates][field];
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 9458a9611..ec31f7dae 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -317,7 +317,7 @@ class ListImpl<T extends Field> extends ObjectField {
         return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`;
     }
     [ToString]() {
-        return `List(${(this as any).length})`;
+        return `[${(this as any).map((field: any) => Field.toString(field))}]`;
     }
 }
 export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
-- 
cgit v1.2.3-70-g09d2


From e3fde25014d523c5f43a138093718899fe17d108 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 7 Feb 2024 16:18:16 -0500
Subject: made various render methods in DocumentView computed getters for
 efficiency and to avoid artifacts (LInkanchorBox dragging) when something
 else invalidates causing components to regenerate.  fixed linklines to
 animate when doing a zoom transition and to be able to target texts
 hyperlinks.  fixed link lines to share properties with ink and updated the
 properties panel / menus to allow editing of either.   addding toggling link
 lines on and off from linkitemmenu

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/views/DocComponent.tsx                  |   1 +
 src/client/views/PropertiesButtons.tsx             |   2 +-
 src/client/views/PropertiesView.tsx                | 104 ++---------
 src/client/views/StyleProvider.tsx                 |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 +
 src/client/views/global/globalScripts.ts           |  26 +--
 src/client/views/linking/LinkMenuItem.tsx          |  24 ++-
 src/client/views/nodes/DocumentView.tsx            |  35 ++--
 src/client/views/nodes/LinkBox.tsx                 | 197 +++++++++++++--------
 src/client/views/nodes/formattedText/marks_rts.ts  |   5 +-
 src/fields/Doc.ts                                  |   3 +-
 src/fields/DocSymbols.ts                           |   1 +
 13 files changed, 201 insertions(+), 202 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1978c144b..355a4c937 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -599,7 +599,7 @@ export namespace Docs {
                         _width: 1,
                         link: '',
                         link_description: '',
-                        backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area
+                        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/DocComponent.tsx b/src/client/views/DocComponent.tsx
index dacd359c5..99b9c3045 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -35,6 +35,7 @@ export interface ViewBoxInterface {
     addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
     select?: (ctrlKey: boolean, shiftKey: boolean) => void;
     focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt<number>;
+    viewTransition?: () => Opt<string>; // duration of a view transition animation
     isAnyChildContentActive?: () => boolean; // is any child content of the document active
     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)
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index bba6285c2..cb38ab602 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -411,7 +411,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             docView.noOnClick();
             switch (onClick) {
                 case 'enterPortal':
-                    docView.makeIntoPortal();
+                    DocUtils.makeIntoPortal(docView.Document, docView.layoutDoc, docView.allLinks);
                     break;
                 case 'toggleDetail':
                     docView.setToggleDetail();
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 07f285eaf..3ae2362a1 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -741,7 +741,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         switch (field) {
             case 'Xps': selDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10); break;
             case 'Yps': selDoc.y = NumCast(this.selectedDoc?.y) + (dirs === 'up' ? 10 : -10); break;
-            case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break;
+            case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.[DocData].stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break;
             case 'wid':
                 const oldWidth = NumCast(selDoc._width);
                 const oldHeight = NumCast(selDoc._height);
@@ -786,7 +786,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     };
 
     getField(key: string) {
-        return Field.toString(this.selectedDoc?.[key] as Field);
+        return Field.toString(this.selectedDoc?.[DocData][key] as Field);
     }
 
     @computed get shapeXps() {
@@ -801,6 +801,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @computed get shapeWid() {
         return NumCast(this.selectedDoc?._width);
     }
+    @computed get strokeThk() {
+        return NumCast(this.selectedDoc?.[DocData].stroke_width);
+    }
     set shapeXps(value) {
         this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100);
     }
@@ -813,6 +816,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     set shapeHgt(value) {
         this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100);
     }
+    set strokeThk(value) {
+        this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Math.round(value * 100) / 100);
+    }
 
     @computed get hgtInput() {
         return this.inputBoxDuo(
@@ -845,16 +851,16 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     private _lastDash: any = '2';
 
     @computed get colorFil() {
-        return StrCast(this.selectedDoc?.fillColor);
+        return StrCast(this.selectedDoc?.[DocData].fillColor);
     }
     @computed get colorStk() {
-        return StrCast(this.selectedDoc?.color);
+        return StrCast(this.selectedDoc?.[DocData].color);
     }
     set colorFil(value) {
-        this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined);
+        this.selectedDoc && (this.selectedDoc[DocData].fillColor = value ? value : undefined);
     }
     set colorStk(value) {
-        this.selectedDoc && (this.selectedDoc.color = value ? value : undefined);
+        this.selectedDoc && (this.selectedDoc[DocData].color = value ? value : undefined);
     }
 
     colorButton(value: string, type: string, setter: () => void) {
@@ -943,19 +949,19 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
     set dashdStk(value) {
         value && (this._lastDash = value);
-        this.selectedDoc && (this.selectedDoc.stroke_dash = value ? this._lastDash : undefined);
+        this.selectedDoc && (this.selectedDoc[DocData].stroke_dash = value ? this._lastDash : undefined);
     }
     set markScal(value) {
-        this.selectedDoc && (this.selectedDoc.stroke_markerScale = Number(value));
+        this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value));
     }
     set widthStk(value) {
-        this.selectedDoc && (this.selectedDoc.stroke_width = Number(value));
+        this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Number(value));
     }
     set markHead(value) {
-        this.selectedDoc && (this.selectedDoc.stroke_startMarker = value);
+        this.selectedDoc && (this.selectedDoc[DocData].stroke_startMarker = value);
     }
     set markTail(value) {
-        this.selectedDoc && (this.selectedDoc.stroke_endMarker = value);
+        this.selectedDoc && (this.selectedDoc[DocData].stroke_endMarker = value);
     }
 
     @computed get stkInput() {
@@ -996,52 +1002,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @computed get widthAndDash() {
         return (
             <div className="widthAndDash">
-                <div className="width">
-                    <div className="width-top">
-                        <div className="width-title">Width:</div>
-                        <div className="width-input">{this.stkInput}</div>
-                    </div>
-                    <input
-                        className="width-range"
-                        type="range"
-                        style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
-                        defaultValue={Number(this.widthStk)}
-                        min={1}
-                        max={100}
-                        onChange={action(e => (this.widthStk = e.target.value))}
-                        onMouseDown={e => {
-                            this._widthUndo = UndoManager.StartBatch('width undo');
-                        }}
-                        onMouseUp={e => {
-                            this._widthUndo?.end();
-                            this._widthUndo = undefined;
-                        }}
-                    />
-                </div>
+                <div className="width">{this.getNumber('Thickness', '', 0, Math.max(50, this.strokeThk), this.strokeThk, (val: number) => !isNaN(val) && (this.strokeThk = val), 50, 1)}</div>
+                <div className="width">{this.getNumber('Arrow Scale', '', 0, Math.max(10, this.markScal), this.markScal, (val: number) => !isNaN(val) && (this.markScal = val), 10, 1)}</div>
 
                 <div className="arrows">
-                    <div className="arrows-head">
-                        <div className="width-top">
-                            <div className="width-title">Arrow Scale:</div>
-                            {/* <div className="width-input">{this.markScalInput}</div> */}
-                        </div>
-                        <input
-                            className="width-range"
-                            type="range"
-                            defaultValue={this.markScal}
-                            style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
-                            min={0}
-                            max={10}
-                            onChange={action(e => (this.markScal = +e.target.value))}
-                            onMouseDown={e => {
-                                this._widthUndo = UndoManager.StartBatch('scale undo');
-                            }}
-                            onMouseUp={e => {
-                                this._widthUndo?.end();
-                                this._widthUndo = undefined;
-                            }}
-                        />
-                    </div>
                     <div className="arrows-head">
                         <div className="arrows-head-title">Arrow Head: </div>
                         <input key="markHead" className="arrows-head-input" type="checkbox" checked={this.markHead !== ''} onChange={undoBatch(action(() => (this.markHead = this.markHead ? '' : 'arrow')))} />
@@ -1442,36 +1406,6 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                         <p>Description</p>
                         {this.editDescription}
                     </div>
-                    <div className="propertiesView-input inline">
-                        <p>Show link</p>
-                        <button
-                            style={{ background: !LinkManager.Instance.currentLink?.link_displayLine ? '' : '#4476f7', borderRadius: 3 }}
-                            onPointerDown={e => this.toggleLinkProp(e, 'link_displayLine')}
-                            onClick={e => e.stopPropagation()}
-                            className="propertiesButton">
-                            <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
-                        </button>
-                    </div>
-                    <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
-                        <p>Auto-move anchors</p>
-                        <button
-                            style={{ background: !LinkManager.Instance.currentLink?.link_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }}
-                            onPointerDown={e => this.toggleLinkProp(e, 'link_autoMoveAnchors')}
-                            onClick={e => e.stopPropagation()}
-                            className="propertiesButton">
-                            <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
-                        </button>
-                    </div>
-                    <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
-                        <p>Display arrow</p>
-                        <button
-                            style={{ background: !LinkManager.Instance.currentLink?.link_displayArrow ? '' : '#4476f7', borderRadius: 3 }}
-                            onPointerDown={e => this.toggleLinkProp(e, 'link_displayArrow')}
-                            onClick={e => e.stopPropagation()}
-                            className="propertiesButton">
-                            <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
-                        </button>
-                    </div>
                 </div>
                 {!hasSelectedAnchor ? null : (
                     <div className="propertiesView-section">
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index b7e64d9a8..0794efe4c 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -134,7 +134,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         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, 'transparent'));
+        case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
         case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
         case StyleProp.TitleHeight:return (props?.ScreenToLocalTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30);
         case StyleProp.ShowTitle:
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b08221f99..61fd5fd05 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -238,6 +238,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
     onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     elementFunc = () => this._layoutElements;
+    viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined);
     fitContentOnce = () => {
         const vals = this.fitToContentVals;
         this.layoutDoc._freeform_panX = vals.bounds.cx;
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 813cb9338..0541a9ca7 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -43,14 +43,14 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
     } else if (selectedViews.length) {
         if (checkResult) {
             const selView = selectedViews.lastElement();
-            const fieldKey = selView.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
+            const fieldKey = selView.Document._layout_isSvg ? 'fillColor' : 'backgroundColor';
             const layoutFrameNumber = Cast(selView.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent';
         }
         selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent');
         selectedViews.forEach(dv => {
-            const fieldKey = dv.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
+            const fieldKey = dv.Document._layout_isSvg ? 'fillColor' : 'backgroundColor';
             const layoutFrameNumber = Cast(dv.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(dv.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             if (contentFrameNumber !== undefined) {
@@ -344,24 +344,24 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | '
     // prettier-ignore
     const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
         ['inkMask', {
-            checkResult: () => ((selected?.type === DocumentType.INK ? BoolCast(selected.stroke_isInkMask) : ActiveIsInkMask())),
-            setInk: (doc: Doc) => (doc.stroke_isInkMask = !doc.stroke_isInkMask),
+            checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())),
+            setInk: (doc: Doc) => (doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask),
             setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
         }],
         ['fillColor', {
-            checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ?? "transparent"),
-            setInk: (doc: Doc) => (doc.fillColor = StrCast(value)),
+            checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"),
+            setInk: (doc: Doc) => (doc[DocData].fillColor = StrCast(value)),
             setMode: () => SetActiveFillColor(StrCast(value)),
         }],
         [ 'strokeWidth', {
-            checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.stroke_width) : ActiveInkWidth()),
-            setInk: (doc: Doc) => (doc.stroke_width = NumCast(value)),
-            setMode: () => { SetActiveInkWidth(value.toString()); setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
+            checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()),
+            setInk: (doc: Doc) => (doc[DocData].stroke_width = NumCast(value)),
+            setMode: () => { SetActiveInkWidth(value.toString()); selected?.type === DocumentType.INK && setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
         }],
         ['strokeColor', {
-            checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()),
-            setInk: (doc: Doc) => (doc.color = String(value)),
-            setMode: () => { SetActiveInkColor(StrCast(value));  setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
+            checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()),
+            setInk: (doc: Doc) => (doc[DocData].color = String(value)),
+            setMode: () => { SetActiveInkColor(StrCast(value)); selected?.type === DocumentType.INK &&  setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
         }],
     ]);
 
@@ -369,7 +369,7 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | '
         return map.get(option)?.checkResult();
     }
     map.get(option)?.setMode();
-    SelectionManager.Docs.filter(doc => doc.type === DocumentType.INK).map(doc => map.get(option)?.setInk(doc));
+    SelectionManager.Docs.filter(doc => doc._layout_isSvg).map(doc => map.get(option)?.setInk(doc));
 });
 
 /** WEB
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 7427f4310..92c63cd56 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -74,6 +74,14 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
         return this._props.sourceDoc;
     }
 
+    onIconDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, returnFalse, () => {
+            if (!this._props.docView._props.removeDocument?.(this._props.linkDoc)) {
+                this._props.docView._props.addDocument?.(this._props.linkDoc);
+            }
+        });
+    };
+
     onEdit = (e: React.PointerEvent) => {
         setupMoveUpEvents(
             this,
@@ -196,12 +204,16 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                                 </p>
                             ) : null}
                             <div className="linkMenu-title-wrapper">
-                                <div className="destination-icon-wrapper">
-                                    <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" />
-                                </div>
-                                <p className="linkMenu-destination-title">
-                                    {this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
-                                </p>
+                                <Tooltip title={<div className="dash-tooltip">Show/Hide Link</div>}>
+                                    <div title="click to show link" className="destination-icon-wrapper" onPointerDown={this.onIconDown}>
+                                        <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" />
+                                    </div>
+                                </Tooltip>
+                                <Tooltip title={<div className="dash-tooltip">Follow Link</div>}>
+                                    <p className="linkMenu-destination-title">
+                                        {this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
+                                    </p>
+                                </Tooltip>
                             </div>
                             {!this._props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this._props.linkDoc.link_description).split('\n')[0].substring(0, 50)}</p>}
                         </div>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5efa028d1..042ae6e55 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -48,6 +48,7 @@ import { KeyValueBox } from './KeyValueBox';
 import { LinkAnchorBox } from './LinkAnchorBox';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { PresEffect, PresEffectDirection } from './trails';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 interface Window {
     MediaRecorder: MediaRecorder;
 }
@@ -726,7 +727,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     };
 
     removeLinkByHiding = (link: Doc) => () => (link.link_displayLine = false);
-    allLinkEndpoints = () => {
+    @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 => (
@@ -750,9 +751,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 />
             </div>
         ));
-    };
+    }
 
-    viewBoxContents = () => {
+    @computed get viewBoxContents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
         const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
@@ -778,10 +779,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     setTitleFocus={this.setTitleFocus}
                     hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)}
                 />
-                {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints()}
+                {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
             </div>
         );
-    };
+    }
 
     captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
     fieldsDropdown = (reqdFields: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
@@ -814,7 +815,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
      * setting layout_showTitle using the format:   field1[;field2[...][:hover]]
      * from the UI, this is done by clicking the title field and prefixin the format with '#'.  eg.,  #field1[;field2;...][:hover]
      **/
-    titleView = () => {
+    @computed get titleView() {
         const showTitle = this.layout_showTitle?.split(':')[0];
         const showTitleHover = this.layout_showTitle?.includes(':hover');
 
@@ -888,9 +889,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 </div>
             </div>
         );
-    };
+    }
 
-    captionView = () => {
+    @computed get captionView() {
         return !this.layout_showCaption ? null : (
             <div
                 className="documentView-captionWrapper"
@@ -913,7 +914,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 />
             </div>
         );
-    };
+    }
 
     renderDoc = (style: object) => {
         TraceMobx();
@@ -933,15 +934,15 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                           fontFamily: StrCast(this.Document._text_fontFamily, 'inherit'),
                           fontSize: Cast(this.Document._text_fontSize, 'string', null),
                           transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
-                          transition: !this._animateScalingTo ? this._props.DataTransition?.() || StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime() / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
+                          transition: !this._animateScalingTo ? this._props.DataTransition?.() : `transform ${this.animateScaleTime() / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
                       }}>
                       {this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
-                          this.viewBoxContents()
+                          this.viewBoxContents
                       ) : (
                           <div className="documentView-styleWrapper">
-                              {this.titleView()}
-                              {this.viewBoxContents()}
-                              {this.captionView()}
+                              {this.titleView}
+                              {this.viewBoxContents}
+                              {this.captionView}
                           </div>
                       )}
                       {this.widgetDecorations ?? null}
@@ -1191,8 +1192,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
             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._props.DataTransition?.() || StrCast(parent.Document.dataTransition));
-        return { left, top, right, bottom, transition: transition?._props.DataTransition?.() || StrCast(transition?.Document.dataTransition) };
+        const transition = this.docViewPath().find((parent: DocumentView) => parent.DataTransition?.() || parent.ComponentView?.viewTransition?.());
+        return { left, top, right, bottom, transition: transition?.DataTransition?.() || transition?.ComponentView?.viewTransition?.() };
     }
 
     @computed get nativeWidth() {
@@ -1337,6 +1338,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
             }
         }
     };
+    DataTransition = () => this._props.DataTransition?.() || StrCast(this.Document.dataTransition);
     ShouldNotScale = () => this.shouldNotScale;
     NativeWidth = () => this.effectiveNativeWidth;
     NativeHeight = () => this.effectiveNativeHeight;
@@ -1402,6 +1404,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                         <DocumentViewInternal
                             {...this._props}
                             fieldKey={this.LayoutFieldKey}
+                            DataTransition={this.DataTransition}
                             DocumentView={this.selfView}
                             docViewPath={this.docViewPath}
                             PanelWidth={this.PanelWidth}
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 85d22abfd..0788e5adc 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,18 +1,21 @@
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import Xarrow from 'react-xarrows';
+import { DocData } from '../../../fields/DocSymbols';
+import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, lightOrDark, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
 import { DocumentManager } from '../../util/DocumentManager';
 import { LinkManager } from '../../util/LinkManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
+import { EditableView } from '../EditableView';
+import { LightboxView } from '../LightboxView';
 import { StyleProp } from '../StyleProvider';
 import { ComparisonBox } from './ComparisonBox';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LinkBox.scss';
-import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 
 @observer
 export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -43,19 +46,20 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.disposer = reaction(
             () => ({ drag: SnappingManager.IsDragging, a: this.anchor1, b: this.anchor2 }),
             ({ drag, a, b }) => {
-                setTimeout(
-                    // need to wait for drag manager to set 'hidden' flag on dragged elements
-                    action(() => {
-                        let a1 = a && document.getElementById(a.Guid);
-                        let a2 = b && document.getElementById(b.Guid);
-                        if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true;
-                        else {
-                            for (; a1 && !a1.hidden; a1 = a1.parentElement);
-                            for (; a2 && !a2.hidden; a2 = a2.parentElement);
-                            this._hide = a1 || a2 ? true : false;
-                        }
-                    })
-                );
+                !LightboxView.Contains(this.DocumentView?.()) &&
+                    setTimeout(
+                        // need to wait for drag manager to set 'hidden' flag on dragged elements
+                        action(() => {
+                            let a1 = a && document.getElementById(a.Guid);
+                            let a2 = b && document.getElementById(b.Guid);
+                            if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true;
+                            else {
+                                for (; a1 && !a1.hidden; a1 = a1.parentElement);
+                                for (; a2 && !a2.hidden; a2 = a2.parentElement);
+                                this._hide = a1 || a2 ? true : false;
+                            }
+                        })
+                    );
             },
             { fireImmediately: true }
         );
@@ -63,89 +67,130 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     select = (ctrlKey: boolean, shiftKey: boolean) => (LinkManager.Instance.currentLink = this.Document);
 
-    descriptionDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(
-            this,
-            e,
-            returnFalse,
-            returnFalse,
-            action(() => {
-                LinkManager.Instance.currentLink = this.Document;
-                LinkDescriptionPopup.Instance.popupX = e.clientX;
-                LinkDescriptionPopup.Instance.popupY = e.clientY;
-                LinkDescriptionPopup.Instance.display = true;
-
-                const rect = document.body.getBoundingClientRect();
-                if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
-                    LinkDescriptionPopup.Instance.popupX -= 190;
-                }
-                if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
-                    LinkDescriptionPopup.Instance.popupY -= 40;
-                }
-            }),
-            false
-        );
-    };
     render() {
-        trace();
+        if (this._hide) return null;
         const a = this.anchor1;
         const b = this.anchor2;
         this._forceAnimate;
 
-        if (a && b && !this._hide) {
+        if (a && b && !LightboxView.Contains(this.DocumentView?.())) {
+            // text selection bounds are not directly observable, so we have to
+            // force an update when anything that could affect them changes (text edits causing reflow, scrolling)
+            a.Document[a.LayoutFieldKey];
+            b.Document[b.LayoutFieldKey];
+            a.Document.layout_scrollTop;
+            b.Document.layout_scrollTop;
+
             const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove)
             const bxf = b.screenToViewTransform();
             const scale = a.CollectionFreeFormView === this.DocumentView?.().CollectionFreeFormView ? axf.Scale : bxf.Scale;
             const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
-            const bt = b.getBounds?.transition;
+            const bt = b.getBounds?.transition; //    inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
+
+            //   if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>),
+            //   then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
+            //   otherwise, we just use the computed nearest point on the document boundary to the target Document
+            const targetAhyperlink = Array.from(window.document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement();
+            const targetBhyperlink = Array.from(window.document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement();
+
+            const aid = targetAhyperlink?.id || a.Document[Id];
+            const bid = targetBhyperlink?.id || b.Document[Id];
+            if (!document.getElementById(aid) || !document.getElementById(bid)) {
+                setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
+                return null;
+            }
+
             if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
             const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
             const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
+            const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
             const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
             const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
-            const color = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor));
-            const { strokeWidth, stroke_startMarker, stroke_endMarker } = this.Document;
-            const dash = StrCast(this.Document.stroke_dash);
-            const stroke = highlightColor ?? 'lightblue';
+            const fontColor = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor));
+            const { stroke_markerScale, stroke_width, stroke_startMarker, stroke_endMarker, stroke_dash } = this.Document;
 
+            const strokeWidth = NumCast(stroke_width, 4);
             const linkDesc = StrCast(this.dataDoc.link_description) || ' ';
             const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '');
             return (
-                <Xarrow
-                    divContainerStyle={{ transform: `scale(${scale})` }}
-                    start={a.Guid}
-                    end={b.Guid} //
-                    strokeWidth={NumCast(strokeWidth, 4)}
-                    dashness={dash ? true : false}
-                    showHead={stroke_startMarker ? true : false}
-                    showTail={stroke_endMarker ? true : false}
-                    tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
-                    headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
-                    color={stroke}
-                    labels={
-                        <div
-                            onPointerDown={this.descriptionDown} //
-                            style={{
-                                borderRadius: '20%', //
-                                padding: '3',
-                                pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
-                                background: stroke,
-                                color: color || lightOrDark(stroke),
-                                fontSize,
-                                fontFamily /*, fontStyle: 'italic'*/,
-                            }}>
-                            {labelText}
-                        </div>
-                    }
-                    passProps={{ onPointerDown: this.descriptionDown }}
-                />
+                <>
+                    {!highlightColor ? null : (
+                        <Xarrow
+                            divContainerStyle={{ transform: `scale(${scale})` }}
+                            start={aid}
+                            end={bid} //
+                            strokeWidth={strokeWidth + Math.max(2, strokeWidth * 0.1)}
+                            showHead={stroke_startMarker ? true : false}
+                            showTail={stroke_endMarker ? true : false}
+                            headSize={NumCast(stroke_markerScale, 3)}
+                            tailSize={NumCast(stroke_markerScale, 3)}
+                            tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
+                            headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+                            color={highlightColor}
+                        />
+                    )}
+                    <Xarrow
+                        divContainerStyle={{ transform: `scale(${scale})` }}
+                        start={aid}
+                        end={bid} //
+                        strokeWidth={strokeWidth}
+                        dashness={Number(stroke_dash) ? true : false}
+                        showHead={stroke_startMarker ? true : false}
+                        showTail={stroke_endMarker ? true : false}
+                        headSize={NumCast(stroke_markerScale, 3)}
+                        tailSize={NumCast(stroke_markerScale, 3)}
+                        tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
+                        headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+                        color={color}
+                        labels={
+                            <div
+                                style={{
+                                    borderRadius: '8px',
+                                    pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
+                                    fontSize,
+                                    fontFamily /*, fontStyle: 'italic'*/,
+                                    color: fontColor || lightOrDark(DashColor(color).fade(0.5).toString()),
+                                    paddingLeft: 4,
+                                    paddingRight: 4,
+                                    paddingTop: 3,
+                                    paddingBottom: 3,
+                                    background: DashColor(highlightColor || color)
+                                        .fade(0.5)
+                                        .toString(),
+                                }}>
+                                <EditableView
+                                    key="editableView"
+                                    oneLine
+                                    contents={labelText}
+                                    height={fontSize + 4}
+                                    fontSize={fontSize}
+                                    GetValue={() => linkDesc}
+                                    SetValue={action(val => {
+                                        this.Document[DocData].link_description = val;
+                                        return true;
+                                    })}
+                                />
+
+                                {/* <EditableText
+                                    placeholder={labelText}
+                                    background={color}
+                                    color={fontColor || lightOrDark(DashColor(color).fade(0.5).toString())}
+                                    type={Type.PRIM}
+                                    val={StrCast(this.Document[DocData].link_description)}
+                                    setVal={action(val => (this.Document[DocData].link_description = val))}
+                                    fillWidth
+                                /> */}
+                            </div>
+                        }
+                        passProps={{}}
+                    />
+                </>
             );
         }
-        return null;
         return (
             <div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
                 <ComparisonBox
-                    {...this._props} //
+                    {...this.props} //
                     fieldKey="link_anchor"
                     setHeight={emptyFunction}
                     dontRegisterView={true}
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index a342285b0..a141ef041 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,6 +1,7 @@
 import * as React from 'react';
 import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model';
 import { Doc } from '../../../../fields/Doc';
+import { Utils } from '../../../../Utils';
 
 const emDOM: DOMOutputSpec = ['em', 0];
 const strongDOM: DOMOutputSpec = ['strong', 0];
@@ -44,7 +45,7 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM(node: any) {
             const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
             const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
-            return ['a', { class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
+            return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
         },
     },
     noAutoLinkAnchor: {
@@ -104,7 +105,7 @@ export const marks: { [index: string]: MarkSpec } = {
                           node.attrs.title,
                       ],
                   ]
-                : ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0];
+                : ['a', { id: '' + Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0];
         },
     },
 
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 3cd4efcf7..56d50846a 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -16,7 +16,7 @@ import { DateField } from './DateField';
 import {
     AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks,
     DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
-    Initializing, Self, SelfProxy, UpdatingFromServer, Width
+    Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width
 } from './DocSymbols'; // prettier-ignore
 import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
 import { InkField, InkTool } from './InkField';
@@ -309,6 +309,7 @@ export class Doc extends RefField {
     public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
     public [Width] = () => NumCast(this[SelfProxy]._width);
     public [Height] = () => NumCast(this[SelfProxy]._height);
+    public [TransitionTimer]: any = undefined;
     public [ToJavascriptString] = () => `idToDoc("${this[Self][Id]}")`; // what should go here?
     public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
     public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index 9c563abbf..f8a57acd5 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -15,6 +15,7 @@ export const DocLayout = Symbol('DocLayout');
 export const DocFields = Symbol('DocFields');
 export const DocCss = Symbol('DocCss');
 export const DocAcl = Symbol('DocAcl');
+export const TransitionTimer = Symbol('DocTransitionTimer');
 export const DirectLinks = Symbol('DocDirectLinks');
 export const AclPrivate = Symbol('DocAclOwnerOnly');
 export const AclReadonly = Symbol('DocAclReadOnly');
-- 
cgit v1.2.3-70-g09d2


From 15f23f8b99b2e5ce48e7146c61d4d61b451875ad Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 7 Feb 2024 18:32:22 -0500
Subject: added back shiftkey to drag and embed. got rid of overlayplane link
 view.  got rid of auto arrange. fixed text edit color for linkBox's

---
 src/client/util/DocumentManager.ts                 |  37 +--
 src/client/util/DragManager.ts                     |   2 +-
 src/client/views/MainView.tsx                      |   4 +-
 .../CollectionFreeFormLinkView.scss                |  12 -
 .../CollectionFreeFormLinkView.tsx                 | 318 ---------------------
 .../CollectionFreeFormLinksView.scss               |  13 -
 .../CollectionFreeFormLinksView.tsx                |  25 --
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  35 +--
 .../views/collections/collectionFreeForm/index.ts  |  12 +-
 src/client/views/global/globalScripts.ts           |   5 -
 src/client/views/nodes/LinkBox.tsx                 |   2 +-
 11 files changed, 17 insertions(+), 448 deletions(-)
 delete mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
 delete mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
 delete mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
 delete mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 53d472c66..eada5af75 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,6 +1,6 @@
 import { Howl } from 'howler';
 import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, Opt } from '../../fields/Doc';
 import { AclAdmin, AclEdit, Animation, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { listSpec } from '../../fields/Schema';
@@ -28,8 +28,6 @@ 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.shallow public LinkAnchorBoxViews: DocumentView[] = [];
-    @observable.shallow public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = [];
     @computed public get DocumentViews() {
         return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.Contains(view)));
     }
@@ -90,37 +88,14 @@ export class DocumentManager {
 
     @action
     public AddView = (view: DocumentView) => {
-        if (view._props.LayoutTemplateString?.includes(KeyValueBox.name)) return;
-        if (view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
-            const viewAnchorIndex = view._props.LayoutTemplateString.includes('link_anchor_2') ? 'link_anchor_2' : 'link_anchor_1';
-            const link = view.Document;
-            this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.Document, link) && !dv._props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView =>
-                this.LinkedDocumentViews.push({
-                    a: viewAnchorIndex === 'link_anchor_2' ? otherView : view,
-                    b: viewAnchorIndex === 'link_anchor_2' ? view : otherView,
-                    l: link,
-                })
-            );
-            this.LinkAnchorBoxViews.push(view);
-        } else {
+        if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) &&
+            !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {      
             this.AddDocumentView(view);
-        }
-        this.callAddViewFuncs(view);
+            this.callAddViewFuncs(view);
+        } // prettier-ignore
     };
     public RemoveView = action((view: DocumentView) => {
-        this.LinkedDocumentViews.slice().forEach(
-            action(pair => {
-                if (pair.a === view || pair.b === view) {
-                    const li = this.LinkedDocumentViews.indexOf(pair);
-                    li !== -1 && this.LinkedDocumentViews.splice(li, 1);
-                }
-            })
-        );
-
-        if (view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
-            const index = this.LinkAnchorBoxViews.indexOf(view);
-            this.LinkAnchorBoxViews.splice(index, 1);
-        } else {
+        if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) && !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
             this.DeleteDocumentView(view);
         }
         SelectionManager.DeselectView(view);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index a6ad0f1b3..78356888a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -504,7 +504,7 @@ export namespace DragManager {
         const moveHandler = (e: PointerEvent) => {
             e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
             if (dragData instanceof DocumentDragData) {
-                dragData.userDropAction = e.ctrlKey && e.altKey ? 'copy' : e.ctrlKey ? 'embed' : dragData.defaultDropAction;
+                dragData.userDropAction = e.ctrlKey && e.altKey ? 'copy' : e.shiftKey ? 'move' : e.ctrlKey ? 'embed' : dragData.defaultDropAction;
             }
             if (((e.target as any)?.className === 'lm_tabs' || (e.target as any)?.className === 'lm_header') && dragData.draggedDocuments.length === 1) {
                 if (!startWindowDragTimer) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index eca0aca4c..efe906981 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -10,6 +10,7 @@ import * as React from 'react';
 import '../../../node_modules/browndash-components/dist/styles/global.min.css';
 import { Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../Utils';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
 import { DocCast, StrCast } from '../../fields/Types';
 import { DocServer } from '../DocServer';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
@@ -50,7 +51,6 @@ import { CollectionDockingView } from './collections/CollectionDockingView';
 import { CollectionMenu } from './collections/CollectionMenu';
 import { TabDocView } from './collections/TabDocView';
 import './collections/TreeView.scss';
-import { CollectionFreeFormLinksView } from './collections/collectionFreeForm';
 import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
 import { CollectionLinearView } from './collections/collectionLinear';
 import { LinkMenu } from './linking/LinkMenu';
@@ -72,7 +72,6 @@ import { PresBox } from './nodes/trails';
 import { AnchorMenu } from './pdf/AnchorMenu';
 import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
 import { TopBar } from './topbar/TopBar';
-import { DocData } from '../../fields/DocSymbols';
 const { default: { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 const _global = (window /* browser */ || global) /* node */ as any;
 
@@ -1037,7 +1036,6 @@ export class MainView extends ObservableReactComponent<{}> {
                 {/* <InkTranscription /> */}
                 {this.snapLines}
                 <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
-                <CollectionFreeFormLinksView />
                 <OverlayView />
                 <GPTPopup key="gptpopup" />
                 <SchemaCSVPopUp key="schemacsvpopup" />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
deleted file mode 100644
index b44acfce8..000000000
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-.collectionfreeformlinkview-linkLine {
-    stroke: black;
-    opacity: 0.8;
-    stroke-width: 3px;
-    transition: opacity 0.5s ease-in;
-    fill: transparent;
-}
-.collectionfreeformlinkview-linkText {
-    stroke: rgb(0, 0, 0);
-    pointer-events: all;
-    cursor: move;
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
deleted file mode 100644
index a45a1fb0f..000000000
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ /dev/null
@@ -1,318 +0,0 @@
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc, Field } from '../../../../fields/Doc';
-import { Brushed, DocCss } from '../../../../fields/DocSymbols';
-import { Id } from '../../../../fields/FieldSymbols';
-import { List } from '../../../../fields/List';
-import { Cast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { LinkManager } from '../../../util/LinkManager';
-import { SelectionManager } from '../../../util/SelectionManager';
-import { SettingsManager } from '../../../util/SettingsManager';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { Colors } from '../../global/globalEnums';
-import { DocumentView } from '../../nodes/DocumentView';
-import { ObservableReactComponent } from '../../ObservableReactComponent';
-import './CollectionFreeFormLinkView.scss';
-
-export interface CollectionFreeFormLinkViewProps {
-    A: DocumentView;
-    B: DocumentView;
-    LinkDocs: Doc[];
-}
-
-@observer
-export class CollectionFreeFormLinkView extends ObservableReactComponent<CollectionFreeFormLinkViewProps> {
-    @observable _opacity: number = 0;
-    @observable _start = 0;
-    _anchorDisposer: IReactionDisposer | undefined;
-    _timeout: NodeJS.Timeout | undefined;
-    constructor(props: any) {
-        super(props);
-        makeObservable(this);
-    }
-
-    componentWillUnmount() {
-        this._anchorDisposer?.();
-    }
-    @action timeout: any = action(() => Date.now() < this._start++ + 1000 && (this._timeout = setTimeout(this.timeout, 25)));
-    componentDidMount() {
-        this._anchorDisposer = reaction(
-            () => [
-                this._props.A.screenToViewTransform(),
-                Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
-                Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
-                this._props.B.screenToViewTransform(),
-                Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
-                Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
-            ],
-            action(() => {
-                this._start = Date.now();
-                this._timeout && clearTimeout(this._timeout);
-                this._timeout = setTimeout(this.timeout, 25);
-                setTimeout(this.placeAnchors, 10); // when docs are dragged, their transforms will update before a render has been performed.  placeanchors needs to come after a render to find things in the dom.  a 0 timeout will still come before the render
-            }),
-            { fireImmediately: true }
-        );
-    }
-    placeAnchors = () => {
-        const { A, B, LinkDocs } = this._props;
-        const linkDoc = LinkDocs[0];
-        if (SnappingManager.IsDragging || !A.ContentDiv || !B.ContentDiv) return;
-        setTimeout(
-            action(() => (this._opacity = 0.75)),
-            0
-        ); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
-        setTimeout(
-            action(() => (!LinkDocs.length || !(linkDoc.link_displayLine || Doc.UserDoc().showLinkLines)) && (this._opacity = 0.05)),
-            750
-        ); // this will unhighlight the link line.
-        const a = A.ContentDiv.getBoundingClientRect();
-        const b = B.ContentDiv.getBoundingClientRect();
-        const { left: aleft, top: atop, width: awidth, height: aheight } = A.ContentDiv.parentElement!.getBoundingClientRect();
-        const { left: bleft, top: btop, width: bwidth, height: bheight } = B.ContentDiv.parentElement!.getBoundingClientRect();
-        const apt = Utils.closestPtBetweenRectangles(aleft, atop, awidth, aheight, bleft, btop, bwidth, bheight, a.left + a.width / 2, a.top + a.height / 2);
-        const bpt = Utils.closestPtBetweenRectangles(bleft, btop, bwidth, bheight, aleft, atop, awidth, aheight, apt.point.x, apt.point.y);
-
-        // really hacky stuff to make the LinkAnchorBox display where we want it to:
-        //   if there's an element in the DOM with a classname containing a link anchor's id,
-        //   then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
-        //   otherwise, we just use the computed nearest point on the document boundary to the target Document
-        const targetAhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_1 as Doc)[Id])).lastElement();
-        const targetBhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_2 as Doc)[Id])).lastElement();
-        if ((!targetAhyperlink && !a.width) || (!targetBhyperlink && !b.width)) return;
-        if (!targetAhyperlink) {
-            if (linkDoc.link_autoMoveAnchors) {
-                linkDoc.link_anchor_1_x = ((apt.point.x - aleft) / awidth) * 100;
-                linkDoc.link_anchor_1_y = ((apt.point.y - atop) / aheight) * 100;
-            }
-        } else {
-            const m = targetAhyperlink.getBoundingClientRect();
-            const mp = A.screenToViewTransform().transformPoint(m.right, m.top + 5);
-            const mpx = mp[0] / A._props.PanelWidth();
-            const mpy = mp[1] / A._props.PanelHeight();
-            if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_1_x = mpx * 100;
-            if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_1_y = mpy * 100;
-            if (getComputedStyle(targetAhyperlink).fontSize === '0px') linkDoc.opacity = 0;
-            else linkDoc.opacity = 1;
-        }
-        if (!targetBhyperlink) {
-            if (linkDoc.link_autoMoveAnchors) {
-                linkDoc.link_anchor_2_x = ((bpt.point.x - bleft) / bwidth) * 100;
-                linkDoc.link_anchor_2_y = ((bpt.point.y - btop) / bheight) * 100;
-            }
-        } else {
-            const m = targetBhyperlink.getBoundingClientRect();
-            const mp = B.screenToViewTransform().transformPoint(m.right, m.top + 5);
-            const mpx = mp[0] / B._props.PanelWidth();
-            const mpy = mp[1] / B._props.PanelHeight();
-            if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_2_x = mpx * 100;
-            if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_2_y = mpy * 100;
-            if (getComputedStyle(targetBhyperlink).fontSize === '0px') linkDoc.opacity = 0;
-            else linkDoc.opacity = 1;
-        }
-    };
-
-    pointerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(
-            this,
-            e,
-            (e, down, delta) => {
-                this._props.LinkDocs[0].link_relationship_OffsetX = NumCast(this._props.LinkDocs[0].link_relationship_OffsetX) + delta[0];
-                this._props.LinkDocs[0].link_relationship_OffsetY = NumCast(this._props.LinkDocs[0].link_relationship_OffsetY) + delta[1];
-                return false;
-            },
-            emptyFunction,
-            action(() => {
-                SelectionManager.DeselectAll();
-                SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true);
-                LinkManager.Instance.currentLink = this._props.LinkDocs[0];
-                this.toggleProperties();
-                // OverlayView.Instance.addElement(
-                //     <LinkEditor sourceDoc={this._props.A.Document} linkDoc={this._props.LinkDocs[0]}
-                //         showLinks={action(() => { })}
-                //     />, { x: 300, y: 300 });
-            })
-        );
-    };
-
-    visibleY = (el: any) => {
-        let rect = el.getBoundingClientRect();
-        const top = rect.top,
-            height = rect.height;
-        var el = el.parentNode;
-        while (el && el !== document.body) {
-            if (el.className === 'tabDocView-content') break;
-            rect = el.getBoundingClientRect?.();
-            if (rect?.width) {
-                if (top <= rect.bottom === false && getComputedStyle(el).overflow === 'hidden') return rect.bottom;
-                // Check if the element is out of view due to a container scrolling
-                if (top + height <= rect.top && getComputedStyle(el).overflow === 'hidden') return rect.top;
-            }
-            el = el.parentNode;
-        }
-        // Check its within the document viewport
-        return top; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden";
-    };
-    visibleX = (el: any) => {
-        let rect = el.getBoundingClientRect();
-        const left = rect.left,
-            width = rect.width;
-        var el = el.parentNode;
-        while (el && el !== document.body) {
-            rect = el?.getBoundingClientRect();
-            if (rect?.width) {
-                if (left <= rect.right === false && getComputedStyle(el).overflow === 'hidden') return rect.right;
-                // Check if the element is out of view due to a container scrolling
-                if (left + width <= rect.left && getComputedStyle(el).overflow === 'hidden') return rect.left;
-            }
-            el = el.parentNode;
-        }
-        // Check its within the document viewport
-        return left; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden";
-    };
-
-    @action
-    toggleProperties = () => {
-        if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
-            SettingsManager.Instance.propertiesWidth = 250;
-        }
-    };
-
-    @action
-    onClickLine = () => {
-        SelectionManager.DeselectAll();
-        SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true);
-        LinkManager.Instance.currentLink = this._props.LinkDocs[0];
-        this.toggleProperties();
-    };
-
-    @computed.struct get renderData() {
-        this._start;
-        SnappingManager.IsDragging;
-        const { A, B, LinkDocs } = this._props;
-        if (!A.ContentDiv || !B.ContentDiv || !LinkDocs.length) return undefined;
-        const acont = A.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
-        const bcont = B.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
-        const adiv = acont.length ? acont[0] : A.ContentDiv;
-        const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
-        for (let apdiv = adiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return;
-        for (let bpdiv = bdiv; bpdiv; bpdiv = bpdiv.parentElement as any) if ((bpdiv as any).hidden) return;
-        const a = adiv.getBoundingClientRect();
-        const b = bdiv.getBoundingClientRect();
-        const atop = this.visibleY(adiv);
-        const btop = this.visibleY(bdiv);
-        if (!a.width || !b.width) return undefined;
-        const aDocBounds = (A._props as any).DocumentView?.().getBounds || { left: 0, right: 0, top: 0, bottom: 0 };
-        const bDocBounds = (B._props as any).DocumentView?.().getBounds || { left: 0, right: 0, top: 0, bottom: 0 };
-        const aleft = this.visibleX(adiv);
-        const bleft = this.visibleX(bdiv);
-        const aclipped = aleft !== a.left || atop !== a.top;
-        const bclipped = bleft !== b.left || btop !== b.top;
-        if (aclipped && bclipped) return undefined;
-        const clipped = aclipped || bclipped;
-        const pt1inside = NumCast(LinkDocs[0].link_anchor_1_x) % 100 !== 0 && NumCast(LinkDocs[0].link_anchor_1_y) % 100 !== 0;
-        const pt2inside = NumCast(LinkDocs[0].link_anchor_2_x) % 100 !== 0 && NumCast(LinkDocs[0].link_anchor_2_y) % 100 !== 0;
-        const pt1 = [aleft + a.width / 2, atop + a.height / 2];
-        const pt2 = [bleft + b.width / 2, btop + b.width / 2];
-        const pt2vec = pt2inside ? [-0.7071, 0.7071] : [(bDocBounds.left + bDocBounds.right) / 2 - pt2[0], (bDocBounds.top + bDocBounds.bottom) / 2 - pt2[1]];
-        const pt1vec = pt1inside ? [-0.7071, 0.7071] : [(aDocBounds.left + aDocBounds.right) / 2 - pt1[0], (aDocBounds.top + aDocBounds.bottom) / 2 - pt1[1]];
-        const pt1len = Math.sqrt(pt1vec[0] * pt1vec[0] + pt1vec[1] * pt1vec[1]);
-        const pt2len = Math.sqrt(pt2vec[0] * pt2vec[0] + pt2vec[1] * pt2vec[1]);
-        const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2;
-        const pt1norm = clipped ? [0, 0] : [-(pt1vec[0] / pt1len) * ptlen, -(pt1vec[1] / pt1len) * ptlen];
-        const pt2norm = clipped ? [0, 0] : [-(pt2vec[0] / pt2len) * ptlen, -(pt2vec[1] / pt2len) * ptlen];
-        const pt1normlen = Math.sqrt(pt1norm[0] * pt1norm[0] + pt1norm[1] * pt1norm[1]) || 1;
-        const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1;
-        const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen];
-        const pt2normalized = [pt2norm[0] / pt2normlen, pt2norm[1] / pt2normlen];
-        const aActive = A.IsSelected || A.Document[Brushed];
-        const bActive = B.IsSelected || B.Document[Brushed];
-
-        const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetX);
-        const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetY);
-        const link = this._props.LinkDocs[0];
-        return {
-            a,
-            b,
-            pt1norm,
-            pt2norm,
-            aActive,
-            bActive,
-            textX,
-            textY,
-            // fully connected
-            // pt1,
-            // pt2,
-            // this code adds space between links
-            pt1: link.link_displayArrow ? [pt1[0] + pt1normalized[0] * 3 * NumCast(link.link_displayArrow_scale, 4), pt1[1] + pt1normalized[1] * 3 * NumCast(link.link_displayArrow_scale, 3)] : pt1,
-            pt2: link.link_displayArrow ? [pt2[0] + pt2normalized[0] * 3 * NumCast(link.link_displayArrow_scale, 4), pt2[1] + pt2normalized[1] * 3 * NumCast(link.link_displayArrow_scale, 3)] : pt2,
-        };
-    }
-
-    render() {
-        if (!this.renderData) return null;
-
-        const link = this._props.LinkDocs[0];
-        const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
-        const linkRelationship = Field.toString(link?.link_relationship as any as Field); //get string representing relationship
-        const linkRelationshipList = Doc.UserDoc().link_relationshipList as List<string>;
-        const linkColorList = Doc.UserDoc().link_ColorList as List<string>;
-        const linkRelationshipSizes = Doc.UserDoc().link_relationshipSizes as List<number>;
-        const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship);
-        const linkDescription = Field.toString(link.link_description as any as Field).split('\n')[0];
-
-        const linkSize = Doc.noviceMode || currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex];
-
-        //access stroke color using index of the relationship in the color list (default black)
-        const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? StrCast(link._backgroundColor, 'black') : linkColorList[currRelationshipIndex];
-        // const hexStroke = this.rgbToHex(stroke)
-
-        //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has)
-        //thickness varies linearly from 3px to 12px for increasing link count
-        const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px';
-
-        const arrowScale = NumCast(link.link_displayArrow_scale, 3);
-        return link.opacity === 0 || !a.width || !b.width || (!(Doc.UserDoc().showLinkLines || link.link_displayLine) && !aActive && !bActive) ? null : (
-            <>
-                <defs>
-                    <marker id={`${link[Id] + 'arrowhead'}`} markerWidth={`${4 * arrowScale}`} markerHeight={`${3 * arrowScale}`} refX="0" refY={`${1.5 * arrowScale}`} orient="auto">
-                        <polygon points={`0 0, ${3 * arrowScale} ${1.5 * arrowScale}, 0 ${3 * arrowScale}`} fill={stroke} />
-                    </marker>
-                    <filter id="outline">
-                        <feMorphology in="SourceAlpha" result="expanded" operator="dilate" radius="1" />
-                        <feFlood floodColor={`${Colors.DARK_GRAY}`} />
-                        <feComposite in2="expanded" operator="in" />
-                        <feComposite in="SourceGraphic" />
-                    </filter>
-                    <filter x="0" y="0" width="1" height="1" id={`${link[Id] + 'background'}`}>
-                        <feFlood floodColor={`${StrCast(link._backgroundColor, 'white')}`} result="bg" />
-                        <feMerge>
-                            <feMergeNode in="bg" />
-                            <feMergeNode in="SourceGraphic" />
-                        </feMerge>
-                    </filter>
-                </defs>
-                <path
-                    filter={LinkManager.Instance.currentLink === link ? 'url(#outline)' : ''}
-                    fill="pink"
-                    stroke="antiquewhite"
-                    strokeWidth="4"
-                    className="collectionfreeformlinkview-linkLine"
-                    style={{ pointerEvents: 'visibleStroke', opacity: this._opacity, stroke, strokeWidth }}
-                    onClick={this.onClickLine}
-                    d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`}
-                    markerEnd={link.link_displayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''}
-                />
-                {textX === undefined || !linkDescription ? null : (
-                    <text filter={`url(#${link[Id] + 'background'})`} className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}>
-                        <tspan>&nbsp;</tspan>
-                        <tspan dy="2">{linkDescription.substring(0, 50) + (linkDescription.length > 50 ? '...' : '')}</tspan>
-                        <tspan dy="2">&nbsp;</tspan>
-                    </text>
-                )}
-            </>
-        );
-    }
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
deleted file mode 100644
index 4ada1731f..000000000
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-// TODO: change z-index to -1 when a modal is active?
-
-.collectionfreeformlinksview-svgCanvas {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    pointer-events: none;
-}
-.collectionfreeformlinksview-container {
-    pointer-events: none;
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
deleted file mode 100644
index e5b6c366f..000000000
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { computed } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Id } from '../../../../fields/FieldSymbols';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { LightboxView } from '../../LightboxView';
-import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView';
-import './CollectionFreeFormLinksView.scss';
-
-@observer
-export class CollectionFreeFormLinksView extends React.Component {
-    @computed get uniqueConnections() {
-        return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews))
-            .filter(c => !LightboxView.LightboxDoc || (LightboxView.Contains(c.a) && LightboxView.Contains(c.b)))
-            .map(c => <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />);
-    }
-
-    render() {
-        return (
-            <div className="collectionfreeformlinksview-container">
-                <svg className="collectionfreeformlinksview-svgCanvas">{this.uniqueConnections}</svg>
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 61fd5fd05..e4c71a086 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -10,6 +10,7 @@ import { DocData, Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
+import { RichTextField } from '../../../../fields/RichTextField';
 import { listSpec } from '../../../../fields/Schema';
 import { ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
@@ -22,8 +23,8 @@ import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../../util/DragManager';
-import { FollowLinkScript } from '../../../util/LinkFollower';
 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';
@@ -53,8 +54,6 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable
 import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
-import { RichTextField } from '../../../../fields/RichTextField';
-import { CompileScript } from '../../../util/Scripting';
 
 export interface collectionFreeformViewProps {
     NativeWidth?: () => number;
@@ -377,28 +376,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             !d._keepZWhenDragged && (d.zIndex = zsorted.length + 1 + i); // bringToFront
         }
 
-        if (this.layoutDoc._autoArrange || de.metaKey) {
-            const sorted = this.childLayoutPairs.slice().sort((a, b) => NumCast(a.layout.y) - NumCast(b.layout.y));
-            sorted.splice(
-                sorted.findIndex(pair => pair.layout === refDoc),
-                1
-            );
-            if (sorted.length && refDoc && NumCast(sorted[0].layout.y) < NumCast(refDoc.y)) {
-                const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height) / 2;
-                const deltay = sorted.length > 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0;
-                const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0;
-
-                let lastx = NumCast(refDoc.x);
-                let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height));
-                runInAction(() =>
-                    sorted.slice(1).forEach((pair, i) => {
-                        lastx = pair.layout.x = lastx + deltax;
-                        lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height));
-                    })
-                );
-            }
-        }
-
         (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
         return true;
     }
@@ -1000,7 +977,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         if (!this.isAnnotationOverlay && clamp) {
             // 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);
+            const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc && doc.type !== DocumentType.LINK);
             const measuredDocs = docs
                 .map(doc => ({ pos: { x: NumCast(doc.x), y: NumCast(doc.y) }, size: { width: NumCast(doc._width), height: NumCast(doc._height) } }))
                 .filter(({ pos, size }) => pos && size)
@@ -1651,12 +1628,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
         !this.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
         !this.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
-        !Doc.noviceMode &&
-            appearanceItems.push({
-                description: 'Toggle auto arrange',
-                event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
-                icon: 'compress-arrows-alt',
-            });
         if (this._props.setContentViewBox === emptyFunction) {
             !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
             return;
diff --git a/src/client/views/collections/collectionFreeForm/index.ts b/src/client/views/collections/collectionFreeForm/index.ts
index 702dc8d42..9a54ce63a 100644
--- a/src/client/views/collections/collectionFreeForm/index.ts
+++ b/src/client/views/collections/collectionFreeForm/index.ts
@@ -1,7 +1,5 @@
-export * from "./CollectionFreeFormLayoutEngines";
-export * from "./CollectionFreeFormLinkView";
-export * from "./CollectionFreeFormLinksView";
-export * from "./CollectionFreeFormRemoteCursors";
-export * from "./CollectionFreeFormView";
-export * from "./MarqueeOptionsMenu";
-export * from "./MarqueeView";
\ No newline at end of file
+export * from './CollectionFreeFormLayoutEngines';
+export * from './CollectionFreeFormRemoteCursors';
+export * from './CollectionFreeFormView';
+export * from './MarqueeOptionsMenu';
+export * from './MarqueeView';
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 0541a9ca7..51672513b 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -127,11 +127,6 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
             checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false),
             setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters,
         }],
-        ['arrange', {
-            waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
-            checkResult: (doc:Doc) => BoolCast(doc?._autoArrange, false),
-            setDoc: (doc:Doc,dv:DocumentView) => doc._autoArrange = !doc._autoArrange,
-        }],
         ['flashcards', {
             checkResult: (doc:Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false),
             setDoc: (doc:Doc,dv:DocumentView) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 0788e5adc..4f1d12c9b 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -154,7 +154,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     paddingRight: 4,
                                     paddingTop: 3,
                                     paddingBottom: 3,
-                                    background: DashColor(highlightColor || color)
+                                    background: DashColor((!this.DocumentView?.().isSelected() && highlightColor) || color)
                                         .fade(0.5)
                                         .toString(),
                                 }}>
-- 
cgit v1.2.3-70-g09d2


From b33ff1abdeab12d977c86f18c567ba3e84a49297 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 22 Feb 2024 16:34:32 -0500
Subject: replaced freeform document view wrapper with more direct caching of
 props.

---
 package-lock.json                                  | 3625 ++++++++++----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |    4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  175 +-
 3 files changed, 1835 insertions(+), 1969 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 1f1c994c3..cf8e8ce29 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -304,66 +304,66 @@
             }
         },
         "node_modules/@adobe/react-spectrum": {
-            "version": "3.34.0",
-            "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.34.0.tgz",
-            "integrity": "sha512-FI+kUI/SefYXCBdmKTwASijMuyaXONgU6uuBt0rddaasiHWytTVzxWHMRAZlxixQHks9nfvD11+DXvI58rNZNw==",
-            "dependencies": {
-                "@internationalized/string": "^3.2.0",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/ssr": "^3.9.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-aria/visually-hidden": "^3.8.9",
-                "@react-spectrum/actionbar": "^3.4.2",
-                "@react-spectrum/actiongroup": "^3.10.2",
-                "@react-spectrum/avatar": "^3.0.9",
-                "@react-spectrum/badge": "^3.1.10",
-                "@react-spectrum/breadcrumbs": "^3.9.4",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/buttongroup": "^3.6.10",
-                "@react-spectrum/calendar": "^3.4.6",
-                "@react-spectrum/checkbox": "^3.9.3",
-                "@react-spectrum/combobox": "^3.12.2",
-                "@react-spectrum/contextualhelp": "^3.6.8",
-                "@react-spectrum/datepicker": "^3.9.3",
-                "@react-spectrum/dialog": "^3.8.8",
-                "@react-spectrum/divider": "^3.5.10",
-                "@react-spectrum/dnd": "^3.3.7",
-                "@react-spectrum/form": "^3.7.3",
-                "@react-spectrum/icon": "^3.7.10",
-                "@react-spectrum/illustratedmessage": "^3.4.10",
-                "@react-spectrum/image": "^3.4.10",
-                "@react-spectrum/inlinealert": "^3.2.2",
-                "@react-spectrum/labeledvalue": "^3.1.11",
-                "@react-spectrum/layout": "^3.6.2",
-                "@react-spectrum/link": "^3.6.4",
-                "@react-spectrum/list": "^3.7.7",
-                "@react-spectrum/listbox": "^3.12.6",
-                "@react-spectrum/menu": "^3.18.0",
-                "@react-spectrum/meter": "^3.4.10",
-                "@react-spectrum/numberfield": "^3.9.0",
-                "@react-spectrum/overlays": "^5.5.4",
-                "@react-spectrum/picker": "^3.14.2",
-                "@react-spectrum/progress": "^3.7.4",
-                "@react-spectrum/provider": "^3.9.4",
-                "@react-spectrum/radio": "^3.7.3",
-                "@react-spectrum/searchfield": "^3.8.3",
-                "@react-spectrum/slider": "^3.6.6",
-                "@react-spectrum/statuslight": "^3.5.10",
-                "@react-spectrum/switch": "^3.5.2",
-                "@react-spectrum/table": "^3.12.7",
-                "@react-spectrum/tabs": "^3.8.7",
-                "@react-spectrum/tag": "^3.2.3",
-                "@react-spectrum/text": "^3.5.2",
-                "@react-spectrum/textfield": "^3.11.3",
-                "@react-spectrum/theme-dark": "^3.5.7",
-                "@react-spectrum/theme-default": "^3.5.7",
-                "@react-spectrum/theme-light": "^3.4.7",
-                "@react-spectrum/tooltip": "^3.6.4",
-                "@react-spectrum/view": "^3.6.7",
-                "@react-spectrum/well": "^3.4.10",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/data": "^3.11.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "client-only": "^0.0.1"
             },
             "peerDependencies": {
@@ -2499,18 +2499,6 @@
                 "node": "*"
             }
         },
-        "node_modules/@eslint/eslintrc/node_modules/type-fest": {
-            "version": "0.20.2",
-            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
-            "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
-            "dev": true,
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
         "node_modules/@eslint/js": {
             "version": "8.56.0",
             "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
@@ -2709,30 +2697,30 @@
             }
         },
         "node_modules/@fullcalendar/core": {
-            "version": "6.1.10",
-            "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.10.tgz",
-            "integrity": "sha512-oTXGJSAGpCf1oY+CKp5qYjMHkJCPBkJ3SHitl63n8Q6xKeiwQ4EF6Au451euUovREwJpLmD1AyZrCnWmtB9AVg==",
+            "version": "6.1.11",
+            "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.11.tgz",
+            "integrity": "sha512-TjG7c8sUz+Vkui2FyCNJ+xqyu0nq653Ibe99A66LoW95oBo6tVhhKIaG1Wh0GVKymYiqAQN/OEdYTuj4ay27kA==",
             "dependencies": {
                 "preact": "~10.12.1"
             }
         },
         "node_modules/@fullcalendar/daygrid": {
-            "version": "6.1.10",
-            "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.10.tgz",
-            "integrity": "sha512-Z4GRm1IyHKgxXFTWGcEI0nTsvYOIkpE0aMt3/o3ER2SZkF+hfwcDFhtj0c9+WhMjXFIWYeoTnA9rUOY7Zl/nxA==",
+            "version": "6.1.11",
+            "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.11.tgz",
+            "integrity": "sha512-hF5jJB7cgUIxWD5MVjj8IU407HISyLu7BWXcEIuTytkfr8oolOXeCazqnnjmRbnFOncoJQVstTtq6SIhaT32Xg==",
             "peerDependencies": {
-                "@fullcalendar/core": "~6.1.10"
+                "@fullcalendar/core": "~6.1.11"
             }
         },
         "node_modules/@fullcalendar/multimonth": {
-            "version": "6.1.10",
-            "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.10.tgz",
-            "integrity": "sha512-mLGEgD+sv8rNfKphyOCWRQapX7/nAvHgQmDTjuy4STlhHsddCYVmFlsSgF373ph1tXh41wAJUVdNO/pDmCvUfA==",
+            "version": "6.1.11",
+            "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.11.tgz",
+            "integrity": "sha512-7DbPC+AAlaKnquGVdw1Z85Q3nSZ4GZ1NcVIk4k7bLnqDlntwHPPsrDlSIzUWKcN0q5/u7jQHm4PU1m3LAl70Sg==",
             "dependencies": {
-                "@fullcalendar/daygrid": "~6.1.10"
+                "@fullcalendar/daygrid": "~6.1.11"
             },
             "peerDependencies": {
-                "@fullcalendar/core": "~6.1.10"
+                "@fullcalendar/core": "~6.1.11"
             }
         },
         "node_modules/@googlemaps/js-api-loader": {
@@ -2816,34 +2804,34 @@
             }
         },
         "node_modules/@internationalized/date": {
-            "version": "3.5.1",
-            "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.1.tgz",
-            "integrity": "sha512-LUQIfwU9e+Fmutc/DpRTGXSdgYZLBegi4wygCWDSVmUdLTaMHsQyASDiJtREwanwKuQLq0hY76fCJ9J/9I2xOQ==",
+            "version": "3.5.2",
+            "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.2.tgz",
+            "integrity": "sha512-vo1yOMUt2hzp63IutEaTUxROdvQg1qlMRsbCvbay2AK2Gai7wIgCyK5weEX3nHkiLgo4qCXHijFNC/ILhlRpOQ==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0"
             }
         },
         "node_modules/@internationalized/message": {
-            "version": "3.1.1",
-            "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.1.tgz",
-            "integrity": "sha512-ZgHxf5HAPIaR0th+w0RUD62yF6vxitjlprSxmLJ1tam7FOekqRSDELMg4Cr/DdszG5YLsp5BG3FgHgqquQZbqw==",
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.2.tgz",
+            "integrity": "sha512-MHAWsZWz8jf6jFPZqpTudcCM361YMtPIRu9CXkYmKjJ/0R3pQRScV5C0zS+Qi50O5UAm8ecKhkXx6mWDDcF6/g==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0",
                 "intl-messageformat": "^10.1.0"
             }
         },
         "node_modules/@internationalized/number": {
-            "version": "3.5.0",
-            "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.5.0.tgz",
-            "integrity": "sha512-ZY1BW8HT9WKYvaubbuqXbbDdHhOUMfE2zHHFJeTppid0S+pc8HtdIxFxaYMsGjCb4UsF+MEJ4n2TfU7iHnUK8w==",
+            "version": "3.5.1",
+            "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.5.1.tgz",
+            "integrity": "sha512-N0fPU/nz15SwR9IbfJ5xaS9Ss/O5h1sVXMZf43vc9mxEG48ovglvvzBjF53aHlq20uoR6c+88CrIXipU/LSzwg==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0"
             }
         },
         "node_modules/@internationalized/string": {
-            "version": "3.2.0",
-            "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.0.tgz",
-            "integrity": "sha512-Xx3Sy3f2c9ctT+vh8c7euEaEHQZltp0euZ3Hy4UfT3E13r6lxpUS3kgKyumEjboJZSnaZv7JhqWz3D75v+IxQg==",
+            "version": "3.2.1",
+            "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.1.tgz",
+            "integrity": "sha512-vWQOvRIauvFMzOO+h7QrdsJmtN1AXAFVcaLWP9AseRN2o7iHceZ6bIXhBD4teZl8i91A3gxKnWBlGgjCwU6MFQ==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0"
             }
@@ -3615,9 +3603,9 @@
             }
         },
         "node_modules/@mui/core-downloads-tracker": {
-            "version": "5.15.10",
-            "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.10.tgz",
-            "integrity": "sha512-qPv7B+LeMatYuzRjB3hlZUHqinHx/fX4YFBiaS19oC02A1e9JFuDKDvlyRQQ5oRSbJJt0QlaLTlr0IcauVcJRQ==",
+            "version": "5.15.11",
+            "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.11.tgz",
+            "integrity": "sha512-JVrJ9Jo4gyU707ujnRzmE8ABBWpXd6FwL9GYULmwZRtfPg89ggXs/S3MStQkpJ1JRWfdLL6S5syXmgQGq5EDAw==",
             "funding": {
                 "type": "opencollective",
                 "url": "https://opencollective.com/mui-org"
@@ -3693,12 +3681,12 @@
             }
         },
         "node_modules/@mui/private-theming": {
-            "version": "5.15.9",
-            "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.9.tgz",
-            "integrity": "sha512-/aMJlDOxOTAXyp4F2rIukW1O0anodAMCkv1DfBh/z9vaKHY3bd5fFf42wmP+0GRmwMinC5aWPpNfHXOED1fEtg==",
+            "version": "5.15.11",
+            "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.11.tgz",
+            "integrity": "sha512-jY/696SnSxSzO1u86Thym7ky5T9CgfidU3NFJjguldqK4f3Z5S97amZ6nffg8gTD0HBjY9scB+4ekqDEUmxZOA==",
             "dependencies": {
                 "@babel/runtime": "^7.23.9",
-                "@mui/utils": "^5.15.9",
+                "@mui/utils": "^5.15.11",
                 "prop-types": "^15.8.1"
             },
             "engines": {
@@ -3719,9 +3707,9 @@
             }
         },
         "node_modules/@mui/styled-engine": {
-            "version": "5.15.9",
-            "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.9.tgz",
-            "integrity": "sha512-NRKtYkL5PZDH7dEmaLEIiipd3mxNnQSO+Yo8rFNBNptY8wzQnQ+VjayTq39qH7Sast5cwHKYFusUrQyD+SS4Og==",
+            "version": "5.15.11",
+            "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.11.tgz",
+            "integrity": "sha512-So21AhAngqo07ces4S/JpX5UaMU2RHXpEA6hNzI6IQjd/1usMPxpgK8wkGgTe3JKmC2KDmH8cvoycq5H3Ii7/w==",
             "dependencies": {
                 "@babel/runtime": "^7.23.9",
                 "@emotion/cache": "^11.11.0",
@@ -3828,9 +3816,9 @@
             }
         },
         "node_modules/@mui/utils": {
-            "version": "5.15.9",
-            "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.9.tgz",
-            "integrity": "sha512-yDYfr61bCYUz1QtwvpqYy/3687Z8/nS4zv7lv/ih/6ZFGMl1iolEvxRmR84v2lOYxlds+kq1IVYbXxDKh8Z9sg==",
+            "version": "5.15.11",
+            "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.11.tgz",
+            "integrity": "sha512-D6bwqprUa9Stf8ft0dcMqWyWDKEo7D+6pB1k8WajbqlYIRA8J8Kw9Ra7PSZKKePGBGWO+/xxrX1U8HpG/aXQCw==",
             "dependencies": {
                 "@babel/runtime": "^7.23.9",
                 "@types/prop-types": "^15.7.11",
@@ -3972,9 +3960,9 @@
             }
         },
         "node_modules/@octokit/types": {
-            "version": "12.4.0",
-            "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.4.0.tgz",
-            "integrity": "sha512-FLWs/AvZllw/AGVs+nJ+ELCDZZJk+kY0zMen118xhL2zD0s1etIUHm1odgjP7epxYU1ln7SZxEUWYop5bhsdgQ==",
+            "version": "12.5.0",
+            "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.5.0.tgz",
+            "integrity": "sha512-YJEKcb0KkJlIUNU/zjnZwHEP8AoVh/OoIcP/1IyR4UHxExz7fzpe/a8IG4wBtQi7QDEqiomVLX88S6FpxxAJtg==",
             "dependencies": {
                 "@octokit/openapi-types": "^19.1.0"
             }
@@ -4018,17 +4006,17 @@
             }
         },
         "node_modules/@react-aria/actiongroup": {
-            "version": "3.7.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/actiongroup/-/actiongroup-3.7.2.tgz",
-            "integrity": "sha512-H8VLxkelhppaFGuPCOuZVryKBUeKVhCtkRIlR3ZnVcNR5jV9Qlc1J3rWOmMNJxcVTszXEbOcJL+/xX1fpBhA5g==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/list": "^3.10.2",
-                "@react-types/actiongroup": "^3.4.6",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4037,15 +4025,15 @@
             }
         },
         "node_modules/@react-aria/breadcrumbs": {
-            "version": "3.5.10",
-            "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.10.tgz",
-            "integrity": "sha512-Z6UMVvobmBg4uLewAXkp9/X6AFN+S/4uHk3IShHCaI8ONNZrIewbPYR1B9qNsgMMnYWmDfaOp40krdJSgYdGUw==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/link": "^3.6.4",
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/breadcrumbs": "^3.7.2",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4053,16 +4041,16 @@
             }
         },
         "node_modules/@react-aria/button": {
-            "version": "3.9.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/button/-/button-3.9.2.tgz",
-            "integrity": "sha512-7ZVUASmdOdi1FNP528oDggxO1SNoAhyuMutaJd7nNBjAJ4Zagi/T9qaAFoVha1hyejfLY/ZzXRdWhnkUYljSbA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/toggle": "^3.7.1",
-                "@react-types/button": "^3.9.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4070,19 +4058,19 @@
             }
         },
         "node_modules/@react-aria/calendar": {
-            "version": "3.5.5",
-            "resolved": "https://registry.npmjs.org/@react-aria/calendar/-/calendar-3.5.5.tgz",
-            "integrity": "sha512-9CmW7mwIJwuM8wsw4W8ads3mxUftaSSfYaWH1h+m0C9ycNJ1FP2QO+re4I821+LhuuyJTj1GOUJ7Tzk+c9yWJA==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/live-announcer": "^3.3.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/calendar": "^3.4.3",
-                "@react-types/button": "^3.9.1",
-                "@react-types/calendar": "^3.4.3",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4091,20 +4079,20 @@
             }
         },
         "node_modules/@react-aria/checkbox": {
-            "version": "3.14.0",
-            "resolved": "https://registry.npmjs.org/@react-aria/checkbox/-/checkbox-3.14.0.tgz",
-            "integrity": "sha512-eOIeIIhnoQkin+1kmz+pv/YjRSkMWAF4uyi1THwkTlLZmpJvxUqEbSzQhfBEH30tKAlFLAcpY3+XOUCm74tHng==",
-            "dependencies": {
-                "@react-aria/form": "^3.0.2",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/label": "^3.7.5",
-                "@react-aria/toggle": "^3.10.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/checkbox": "^3.6.2",
-                "@react-stately/form": "^3.0.0",
-                "@react-stately/toggle": "^3.7.1",
-                "@react-types/checkbox": "^3.7.0",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4112,24 +4100,24 @@
             }
         },
         "node_modules/@react-aria/combobox": {
-            "version": "3.8.3",
-            "resolved": "https://registry.npmjs.org/@react-aria/combobox/-/combobox-3.8.3.tgz",
-            "integrity": "sha512-7SteX1MjDV0077IZVMuaXAuIiSaQeGYh1sT5Y6eDOCiOArkPaEmXWzCjBDkLYex0jl6z51gl0gLU3dwq9kB6Gw==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/listbox": "^3.11.4",
-                "@react-aria/live-announcer": "^3.3.1",
-                "@react-aria/menu": "^3.13.0",
-                "@react-aria/overlays": "^3.21.0",
-                "@react-aria/selection": "^3.17.4",
-                "@react-aria/textfield": "^3.14.2",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/combobox": "^3.8.1",
-                "@react-stately/form": "^3.0.0",
-                "@react-types/button": "^3.9.1",
-                "@react-types/combobox": "^3.10.0",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4138,27 +4126,27 @@
             }
         },
         "node_modules/@react-aria/datepicker": {
-            "version": "3.9.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/datepicker/-/datepicker-3.9.2.tgz",
-            "integrity": "sha512-8iTfhHEfPPmf7DmqnzCttvzZms15CstAVdvbOVqUuBqH8Ai693aIuVfOEkyqL5Vqohy4/+ViA5LZ3WM4m7QLiQ==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.1",
-                "@internationalized/number": "^3.5.0",
-                "@internationalized/string": "^3.2.0",
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/form": "^3.0.2",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/label": "^3.7.5",
-                "@react-aria/spinbutton": "^3.6.2",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/datepicker": "^3.9.1",
-                "@react-stately/form": "^3.0.0",
-                "@react-types/button": "^3.9.1",
-                "@react-types/calendar": "^3.4.3",
-                "@react-types/datepicker": "^3.7.1",
-                "@react-types/dialog": "^3.5.7",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4167,15 +4155,15 @@
             }
         },
         "node_modules/@react-aria/dialog": {
-            "version": "3.5.11",
-            "resolved": "https://registry.npmjs.org/@react-aria/dialog/-/dialog-3.5.11.tgz",
-            "integrity": "sha512-oT+FBOtPZRWVBxPt1K8F5XaKGYpi+ZV3oFFzub8w+D6m+9WN4pktUx7YBz95Kunw7M1HcAsyQZX0fsAuDPL7Rw==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/overlays": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/dialog": "^3.5.7",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4184,19 +4172,19 @@
             }
         },
         "node_modules/@react-aria/dnd": {
-            "version": "3.5.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/dnd/-/dnd-3.5.2.tgz",
-            "integrity": "sha512-isEYONbkZMvx1DUmeAo/NwazFz9ZEKdosszWDhzxSk6a38C5kAHBIV2GBntwsL3W9EQn1fzUY/X13ykcHvjJAA==",
-            "dependencies": {
-                "@internationalized/string": "^3.2.0",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/live-announcer": "^3.3.1",
-                "@react-aria/overlays": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/dnd": "^3.2.7",
-                "@react-types/button": "^3.9.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4205,13 +4193,13 @@
             }
         },
         "node_modules/@react-aria/focus": {
-            "version": "3.16.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.16.1.tgz",
-            "integrity": "sha512-3ZEYc+hWqDQX7fA54ZOTkED8OGXs9+K9fYmjD1IdjZJAJS/2/AJ95PgIQ29zBkl9D9TAi4Nb3tJ/3+H/02UzoA==",
+            "version": "3.16.2",
+            "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.16.2.tgz",
+            "integrity": "sha512-Rqo9ummmgotESfypzFjI3uh58yMpL+E+lJBbQuXkBM0u0cU2YYzu0uOrFrq3zcHk997udZvq1pGK/R+2xk9B7g==",
             "dependencies": {
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/interactions": "^3.21.1",
+                "@react-aria/utils": "^3.23.2",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0",
                 "clsx": "^2.0.0"
             },
@@ -4220,14 +4208,14 @@
             }
         },
         "node_modules/@react-aria/form": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/form/-/form-3.0.2.tgz",
-            "integrity": "sha512-iQBbol9vSPd+s2rNlkmu646wGlrDNr/u+Uf/NRXNl5MNvDIWAQ3mliGL1ERGtT5GGq0WzSka4hjbzwf67BdQKw==",
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/@react-aria/form/-/form-3.0.3.tgz",
+            "integrity": "sha512-5Q2BHE4TTPDzGY2npCzpRRYshwWUb3SMUA/Cbz7QfEtBk+NYuVaq3KjvqLqgUUdyKtqLZ9Far0kIAexloOC4jw==",
             "dependencies": {
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/form": "^3.0.0",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/interactions": "^3.21.1",
+                "@react-aria/utils": "^3.23.2",
+                "@react-stately/form": "^3.0.1",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4235,23 +4223,23 @@
             }
         },
         "node_modules/@react-aria/grid": {
-            "version": "3.8.7",
-            "resolved": "https://registry.npmjs.org/@react-aria/grid/-/grid-3.8.7.tgz",
-            "integrity": "sha512-gpWiZptCA+65jVbCu9vzkfBfzfgLucSOZFy9P+fVrGB6fE6pDGj13oyyr+Nmr6jihQmOv+y9eJsU7u9Xlyl2Xg==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/live-announcer": "^3.3.1",
-                "@react-aria/selection": "^3.17.4",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/grid": "^3.8.4",
-                "@react-stately/selection": "^3.14.2",
-                "@react-stately/virtualizer": "^3.6.7",
-                "@react-types/checkbox": "^3.7.0",
-                "@react-types/grid": "^3.2.3",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4260,18 +4248,18 @@
             }
         },
         "node_modules/@react-aria/gridlist": {
-            "version": "3.7.4",
-            "resolved": "https://registry.npmjs.org/@react-aria/gridlist/-/gridlist-3.7.4.tgz",
-            "integrity": "sha512-9GeKXfMd4WqyaRs7PMvuL9ryLND0EBkgf14dycbZCb/QKxgNYRLPFZN6fV3LiMh2CiqAXEpmDOo60PxSxh618A==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/grid": "^3.8.7",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/selection": "^3.17.4",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/list": "^3.10.2",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4280,17 +4268,17 @@
             }
         },
         "node_modules/@react-aria/i18n": {
-            "version": "3.10.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.10.1.tgz",
-            "integrity": "sha512-o05AozIXhropvN8vg0YD0Z0vajYuaALxB1+Km00FE5uGim4u2UKeBXqAk+8QsOs4CHg91paa3C15DcTDDEMJSg==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.1",
-                "@internationalized/message": "^3.1.1",
-                "@internationalized/number": "^3.5.0",
-                "@internationalized/string": "^3.2.0",
-                "@react-aria/ssr": "^3.9.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4298,13 +4286,13 @@
             }
         },
         "node_modules/@react-aria/interactions": {
-            "version": "3.21.0",
-            "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.21.0.tgz",
-            "integrity": "sha512-sPuzEl4Xq/BR5gbYr2R/sDzwlX9NdJ02i8Ew2rEy2hLMlf1jAeUAdTg/G+K9baWJ8acV9fZv6h/mdV3dXGLPSg==",
+            "version": "3.21.1",
+            "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.21.1.tgz",
+            "integrity": "sha512-AlHf5SOzsShkHfV8GLLk3v9lEmYqYHURKcXWue0JdYbmquMRkUsf/+Tjl1+zHVAQ8lKqRnPYbTmc4AcZbqxltw==",
             "dependencies": {
-                "@react-aria/ssr": "^3.9.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/ssr": "^3.9.2",
+                "@react-aria/utils": "^3.23.2",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4312,12 +4300,12 @@
             }
         },
         "node_modules/@react-aria/label": {
-            "version": "3.7.5",
-            "resolved": "https://registry.npmjs.org/@react-aria/label/-/label-3.7.5.tgz",
-            "integrity": "sha512-1hlTer4X9zlzC2PHC9SlAeY/E55dYyzxxpM75kqhtma8XFgDUB0Rztvi+R/Uenl0VDuy1Ny95OdhBM0j70qBnA==",
+            "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==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/utils": "^3.23.2",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4325,15 +4313,15 @@
             }
         },
         "node_modules/@react-aria/link": {
-            "version": "3.6.4",
-            "resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.6.4.tgz",
-            "integrity": "sha512-oCUWAvnJYD7IUR6ujQJqCChiSFJMsBncoInMvfbnlD3ZX5AqLWEPkJUZ7aM8XB6fGEapiYoY1IUHzOM63XsmjQ==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/link": "^3.5.2",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4341,18 +4329,18 @@
             }
         },
         "node_modules/@react-aria/listbox": {
-            "version": "3.11.4",
-            "resolved": "https://registry.npmjs.org/@react-aria/listbox/-/listbox-3.11.4.tgz",
-            "integrity": "sha512-ml2dUy1R0kSuDmFrW0xtAnI04nZBfzLLmINSl1k9N5Tr/PVZ/TVGjFimHfpG7g3uvTxGXpjFfA9DR+2Nyuj/SA==",
-            "dependencies": {
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/label": "^3.7.5",
-                "@react-aria/selection": "^3.17.4",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/list": "^3.10.2",
-                "@react-types/listbox": "^3.4.6",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4361,30 +4349,30 @@
             }
         },
         "node_modules/@react-aria/live-announcer": {
-            "version": "3.3.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/live-announcer/-/live-announcer-3.3.1.tgz",
-            "integrity": "sha512-hsc77U7S16trM86d+peqJCOCQ7/smO1cybgdpOuzXyiwcHQw8RQ4GrXrS37P4Ux/44E9nMZkOwATQRT2aK8+Ew==",
+            "version": "3.3.2",
+            "resolved": "https://registry.npmjs.org/@react-aria/live-announcer/-/live-announcer-3.3.2.tgz",
+            "integrity": "sha512-aOyPcsfyY9tLCBhuUaYCruwcd1IrYLc47Ou+J7wMzjeN9v4lsaEfiN12WFl8pDqOwfy6/7It2wmlm5hOuZY8wQ==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0"
             }
         },
         "node_modules/@react-aria/menu": {
-            "version": "3.13.0",
-            "resolved": "https://registry.npmjs.org/@react-aria/menu/-/menu-3.13.0.tgz",
-            "integrity": "sha512-2vNZV77I36sbAINj3QVbq4yxOfytzQpdQAhHy7QFr7WjvTYqPAVNuD4LgiGn97I5skLRakCs+tGiLgIQGY/0Ig==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/overlays": "^3.21.0",
-                "@react-aria/selection": "^3.17.4",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/menu": "^3.6.0",
-                "@react-stately/tree": "^3.7.5",
-                "@react-types/button": "^3.9.1",
-                "@react-types/menu": "^3.9.6",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4393,13 +4381,13 @@
             }
         },
         "node_modules/@react-aria/meter": {
-            "version": "3.4.10",
-            "resolved": "https://registry.npmjs.org/@react-aria/meter/-/meter-3.4.10.tgz",
-            "integrity": "sha512-8x4Y9NKRzJNPlcU9kn0kflYSLCyMwaIAx0JJFL576+HdZhyIbEPFk/vaaYWbyJgo5xYsicaeynwJ7j6lCyxF8w==",
+            "version": "3.4.11",
+            "resolved": "https://registry.npmjs.org/@react-aria/meter/-/meter-3.4.11.tgz",
+            "integrity": "sha512-P1G3Jdh0f/uieUDqvc3Ee4wzqBJa7H077BVSC3KPRqEp6YY7JimZGWjOwbFlO2PXhryXm/dI8EzUmh+4ZXjq/g==",
             "dependencies": {
-                "@react-aria/progress": "^3.4.10",
-                "@react-types/meter": "^3.3.6",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/progress": "^3.4.11",
+                "@react-types/meter": "^3.3.7",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4407,20 +4395,20 @@
             }
         },
         "node_modules/@react-aria/numberfield": {
-            "version": "3.11.0",
-            "resolved": "https://registry.npmjs.org/@react-aria/numberfield/-/numberfield-3.11.0.tgz",
-            "integrity": "sha512-fdl+d6sIwHRMiraXMQ5JyPlrmsQ3YECh9W7fzbY3CBcsG9mXVQjoQ50cV7u5VeakLXaBsgL4EmVe/zmTk2G0Aw==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/spinbutton": "^3.6.2",
-                "@react-aria/textfield": "^3.14.2",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/form": "^3.0.0",
-                "@react-stately/numberfield": "^3.9.0",
-                "@react-types/button": "^3.9.1",
-                "@react-types/numberfield": "^3.8.0",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4429,20 +4417,20 @@
             }
         },
         "node_modules/@react-aria/overlays": {
-            "version": "3.21.0",
-            "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.21.0.tgz",
-            "integrity": "sha512-ulE5RQP3ZUFqY6Zok4L/CCZW5HCPZeuyDEezPw4/4Y/WD6TjGZ1ChbPuGsAl+X+fo/iKTpe7joN4kYrKmTb5WA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/ssr": "^3.9.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-aria/visually-hidden": "^3.8.9",
-                "@react-stately/overlays": "^3.6.4",
-                "@react-types/button": "^3.9.1",
-                "@react-types/overlays": "^3.8.4",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4451,15 +4439,15 @@
             }
         },
         "node_modules/@react-aria/progress": {
-            "version": "3.4.10",
-            "resolved": "https://registry.npmjs.org/@react-aria/progress/-/progress-3.4.10.tgz",
-            "integrity": "sha512-q82LbDjimIo5a21Kg9aUYG94oFsDtpAwaQDj2YPVDt9kNie4BHOLHnHZWYk8jPoZDHMEk80jZBKWcDnk8+wWtw==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/label": "^3.7.5",
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/progress": "^3.5.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4467,19 +4455,19 @@
             }
         },
         "node_modules/@react-aria/radio": {
-            "version": "3.10.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/radio/-/radio-3.10.1.tgz",
-            "integrity": "sha512-UEeQncLloHz/H4iwHbiBXiYLCDsMmRDkL/GMcpUCx0OaQSuKB/kJTOR0rFEMVyCiDVXx7hJ16fcSh1lXpLL9gA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/form": "^3.0.2",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/label": "^3.7.5",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/radio": "^3.10.1",
-                "@react-types/radio": "^3.7.0",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4487,17 +4475,17 @@
             }
         },
         "node_modules/@react-aria/searchfield": {
-            "version": "3.7.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/searchfield/-/searchfield-3.7.2.tgz",
-            "integrity": "sha512-YpmvbWOgECTK9KjMYqWwSFPi57b7m0rdAQPMjB3Call0CE7tQiSP5TbwkpOa4Id8Kghm8ySV5fNkBKP2xhHxdg==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/textfield": "^3.14.2",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/searchfield": "^3.5.0",
-                "@react-types/button": "^3.9.1",
-                "@react-types/searchfield": "^3.5.2",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4505,23 +4493,23 @@
             }
         },
         "node_modules/@react-aria/select": {
-            "version": "3.14.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/select/-/select-3.14.2.tgz",
-            "integrity": "sha512-8kwqzNwJI+oC8PqotElEeWF24Ae58nnyeujcM0yCnuglTGqacGqCHmAOFHb99SxxSXmNC6OppAZNmRV3kUxdLA==",
-            "dependencies": {
-                "@react-aria/form": "^3.0.2",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/label": "^3.7.5",
-                "@react-aria/listbox": "^3.11.4",
-                "@react-aria/menu": "^3.13.0",
-                "@react-aria/selection": "^3.17.4",
-                "@react-aria/utils": "^3.23.1",
-                "@react-aria/visually-hidden": "^3.8.9",
-                "@react-stately/select": "^3.6.1",
-                "@react-types/button": "^3.9.1",
-                "@react-types/select": "^3.9.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4530,16 +4518,16 @@
             }
         },
         "node_modules/@react-aria/selection": {
-            "version": "3.17.4",
-            "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.17.4.tgz",
-            "integrity": "sha512-COq5qGbJebn9Wbo/3UGluhYLK4uUijuacew/7PgLArHIjiqPvK7kqcQA5Kdwrxzv4Z94f2x9fVf8Uf65zB543Q==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/selection": "^3.14.2",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4548,12 +4536,12 @@
             }
         },
         "node_modules/@react-aria/separator": {
-            "version": "3.3.10",
-            "resolved": "https://registry.npmjs.org/@react-aria/separator/-/separator-3.3.10.tgz",
-            "integrity": "sha512-/wM8yOY0XPWDJXbyiKo9lgDTePDCa0ZDoXAn+Hg4dwh6lQLBmEQBugN5sVJJ4BBdbyQbcRZcI2+eM4DfcU2kNg==",
+            "version": "3.3.11",
+            "resolved": "https://registry.npmjs.org/@react-aria/separator/-/separator-3.3.11.tgz",
+            "integrity": "sha512-UTla+3P2pELpP73WSfbwZgP1y1wODFBQbEOHnUxxO8ocyaUyQLJdvc07bBLLpPoyutlggRG0v9ACo0Rui7AjOg==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/utils": "^3.23.2",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4561,18 +4549,18 @@
             }
         },
         "node_modules/@react-aria/slider": {
-            "version": "3.7.5",
-            "resolved": "https://registry.npmjs.org/@react-aria/slider/-/slider-3.7.5.tgz",
-            "integrity": "sha512-AV0ZDWmdO8mvh3wS09uOAoenRCxkqaN7ZYb0rQLRvP2vhZJzaJPaN7/0f9/TLnvrZmmUspm4IqXD+Ci9q49lWA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/label": "^3.7.5",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/slider": "^3.5.1",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/slider": "^3.7.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4580,15 +4568,15 @@
             }
         },
         "node_modules/@react-aria/spinbutton": {
-            "version": "3.6.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.2.tgz",
-            "integrity": "sha512-ivrSmyjm/FSEBBD/Io3cwad88Kx8CiulLXZNJPvvEsTrsowDIyaLZ55oTrNIXXQZJY4Ns9ccaQUwD+QGBX/eIQ==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/live-announcer": "^3.3.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/button": "^3.9.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4597,9 +4585,9 @@
             }
         },
         "node_modules/@react-aria/ssr": {
-            "version": "3.9.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.1.tgz",
-            "integrity": "sha512-NqzkLFP8ZVI4GSorS0AYljC13QW2sc8bDqJOkBvkAt3M8gbcAXJWVRGtZBCRscki9RZF+rNlnPdg0G0jYkhJcg==",
+            "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==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0"
             },
@@ -4611,13 +4599,13 @@
             }
         },
         "node_modules/@react-aria/switch": {
-            "version": "3.6.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/switch/-/switch-3.6.1.tgz",
-            "integrity": "sha512-WaDJ4KJlrYvkLK4h3/KRGRfHlYjPmqDEh83ya0wtkZbKZte7/2+a1bnpwcrQeTmuDVSPthiieL1dQggg8sOuxA==",
+            "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==",
             "dependencies": {
-                "@react-aria/toggle": "^3.10.1",
-                "@react-stately/toggle": "^3.7.1",
-                "@react-types/switch": "^3.5.0",
+                "@react-aria/toggle": "^3.10.2",
+                "@react-stately/toggle": "^3.7.2",
+                "@react-types/switch": "^3.5.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4625,25 +4613,25 @@
             }
         },
         "node_modules/@react-aria/table": {
-            "version": "3.13.4",
-            "resolved": "https://registry.npmjs.org/@react-aria/table/-/table-3.13.4.tgz",
-            "integrity": "sha512-dhYumqv2kCH3LOtmXJlq0Qc3VVFwaDaHaTV6xgen2EZxUZQa3mZDoCoxSu8/w3LvKwQAk+NAOqmrOHzMMyOlOQ==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/grid": "^3.8.7",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/live-announcer": "^3.3.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-aria/visually-hidden": "^3.8.9",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/flags": "^3.0.0",
-                "@react-stately/table": "^3.11.5",
-                "@react-stately/virtualizer": "^3.6.7",
-                "@react-types/checkbox": "^3.7.0",
-                "@react-types/grid": "^3.2.3",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/table": "^3.9.2",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4652,17 +4640,17 @@
             }
         },
         "node_modules/@react-aria/tabs": {
-            "version": "3.8.4",
-            "resolved": "https://registry.npmjs.org/@react-aria/tabs/-/tabs-3.8.4.tgz",
-            "integrity": "sha512-q8c3QorgVqi9hxa1FisWuZ5rRkS0EyzynmJ2hNhyznIlGuMP30HsnsTIf1Y+hNNj3ofL04Xc5BkJpLrQvsBPAQ==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/selection": "^3.17.4",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/tabs": "^3.6.3",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/tabs": "^3.3.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4671,19 +4659,19 @@
             }
         },
         "node_modules/@react-aria/tag": {
-            "version": "3.3.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/tag/-/tag-3.3.2.tgz",
-            "integrity": "sha512-AWgwRBwhhrmfdvvV7cSp3VTsLkw1e04IKQCKss3hF7zd75T+LfwvCa8g4CUDsFXx3lvIh0rB/6evhBfn3D0rDw==",
-            "dependencies": {
-                "@react-aria/gridlist": "^3.7.4",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/label": "^3.7.5",
-                "@react-aria/selection": "^3.17.4",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/list": "^3.10.2",
-                "@react-types/button": "^3.9.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4692,18 +4680,18 @@
             }
         },
         "node_modules/@react-aria/textfield": {
-            "version": "3.14.2",
-            "resolved": "https://registry.npmjs.org/@react-aria/textfield/-/textfield-3.14.2.tgz",
-            "integrity": "sha512-l9FulRr7Kpchy+dt0hIZ7rMwMReJrZnjeWT7iwmZWFMleEYjSuRUnmM+L82oZBY9BSwftJ5kk6vG4AP7MLBulg==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/form": "^3.0.2",
-                "@react-aria/label": "^3.7.5",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/form": "^3.0.0",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/textfield": "^3.9.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4711,15 +4699,15 @@
             }
         },
         "node_modules/@react-aria/toggle": {
-            "version": "3.10.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.10.1.tgz",
-            "integrity": "sha512-Z+T/fyWvglLDMsNDCee/OF3Josntzlfq1/Iez+ShqIaSI/FgZIjglQbEFqXvM9gSGOX1fYfv9n4Ztj6DDT6J2Q==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/toggle": "^3.7.1",
-                "@react-types/checkbox": "^3.7.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4727,16 +4715,16 @@
             }
         },
         "node_modules/@react-aria/tooltip": {
-            "version": "3.7.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/tooltip/-/tooltip-3.7.1.tgz",
-            "integrity": "sha512-2lf72hIw3gfQLvKU9SHkh3a/CP8GJ3MbKlPq8x0gX7444zaYII/UDqIkde1TiR2fB71I/MqKSOx6mg28B9lj4w==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/tooltip": "^3.4.6",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/tooltip": "^3.4.6",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4744,13 +4732,13 @@
             }
         },
         "node_modules/@react-aria/utils": {
-            "version": "3.23.1",
-            "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.23.1.tgz",
-            "integrity": "sha512-iXibf9ojqdoygbvy/++v5cKLKgjc/5ZmKV8/9u/2Hkpha1cf5Td/Z+Vl42B6giUBAsuDio5kuZYfYC7Uk+t8ag==",
+            "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==",
             "dependencies": {
-                "@react-aria/ssr": "^3.9.1",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/ssr": "^3.9.2",
+                "@react-stately/utils": "^3.9.1",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0",
                 "clsx": "^2.0.0"
             },
@@ -4759,15 +4747,15 @@
             }
         },
         "node_modules/@react-aria/virtualizer": {
-            "version": "3.9.9",
-            "resolved": "https://registry.npmjs.org/@react-aria/virtualizer/-/virtualizer-3.9.9.tgz",
-            "integrity": "sha512-nso567w/vEd5wRLqF2tJF24XYx9wnf0NxrJZW/26xUVSUBgIy4QkYjxb2ylWQCLiRe8iA6xfC27dyvJVeQXwUg==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-stately/virtualizer": "^3.6.7",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4776,13 +4764,13 @@
             }
         },
         "node_modules/@react-aria/visually-hidden": {
-            "version": "3.8.9",
-            "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.9.tgz",
-            "integrity": "sha512-yUUj4M8YjtwzS45n0nB4bCKCYd3Dl6wmCZKAcAqZG9hMh6DUAcSp2xqYKOAzvNrtZZmAoopgMP4UC9hKlY6swQ==",
+            "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==",
             "dependencies": {
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/interactions": "^3.21.1",
+                "@react-aria/utils": "^3.23.2",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4817,24 +4805,24 @@
             "integrity": "sha512-x9ibmsP0ZVqzyCo1Pitbw+4b6iEXRw/r1TCy3vOUR3eKrzWLnHYZMR325BkZW2r8fnuWE/V3Fp4QZOP9qYORCw=="
         },
         "node_modules/@react-spectrum/actionbar": {
-            "version": "3.4.2",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/actionbar/-/actionbar-3.4.2.tgz",
-            "integrity": "sha512-7vgDP/Q+GbkZN5bMspqUnpVBXDysaznN15s0ZtShOwfWSqbBFHFzMSKcH7IzQoF1C49TWOKwy6LEOGAsfG0RnA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/live-announcer": "^3.3.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/actiongroup": "^3.10.2",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/overlays": "^5.5.4",
-                "@react-spectrum/text": "^3.5.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/collections": "^3.10.4",
-                "@react-types/actionbar": "^3.1.4",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4844,25 +4832,25 @@
             }
         },
         "node_modules/@react-spectrum/actiongroup": {
-            "version": "3.10.2",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/actiongroup/-/actiongroup-3.10.2.tgz",
-            "integrity": "sha512-8IslvMwN0c0+PaZfn6zdQokefpUynyQjUPOJMrDcP6CFUKrNkjEexxwipSmgw0OGb1AdGCzCAXpqNXGS0OwBVw==",
-            "dependencies": {
-                "@react-aria/actiongroup": "^3.7.2",
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/menu": "^3.18.0",
-                "@react-spectrum/text": "^3.5.2",
-                "@react-spectrum/tooltip": "^3.6.4",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/list": "^3.10.2",
-                "@react-types/actiongroup": "^3.4.6",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
-                "@spectrum-icons/workflow": "^4.2.9",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4872,14 +4860,14 @@
             }
         },
         "node_modules/@react-spectrum/avatar": {
-            "version": "3.0.9",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/avatar/-/avatar-3.0.9.tgz",
-            "integrity": "sha512-zGgfK7tAFOJ9FvJdlvpi++L6YF/RFBONlfJ/acCcls/ki+spzzlmmtml/RFzzO//4vsw/5iayfknWsYdSAl/2g==",
+            "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.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/avatar": "^3.0.4",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/utils": "^3.23.2",
+                "@react-spectrum/utils": "^3.11.5",
+                "@react-types/avatar": "^3.0.5",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4888,15 +4876,15 @@
             }
         },
         "node_modules/@react-spectrum/badge": {
-            "version": "3.1.10",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/badge/-/badge-3.1.10.tgz",
-            "integrity": "sha512-XsdwwbtIP0rxI2tRGrA3Nf46GSsekoO0tt1A/69P7dU230LkxTdILMLABCblP/lb6gxa1ksXkbWvB93L9ScDzw==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/text": "^3.5.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/badge": "^3.1.6",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4905,22 +4893,22 @@
             }
         },
         "node_modules/@react-spectrum/breadcrumbs": {
-            "version": "3.9.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/breadcrumbs/-/breadcrumbs-3.9.4.tgz",
-            "integrity": "sha512-IYo+N6hp6FjZFYZu3eD2tiGDNX+XXluxVKRVX0wCKo7jjylRrEUhaDe0vJ9OZjtmU3RRUzczUHmsnn4maSuAqw==",
-            "dependencies": {
-                "@react-aria/breadcrumbs": "^3.5.10",
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/menu": "^3.18.0",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/collections": "^3.10.4",
-                "@react-types/breadcrumbs": "^3.7.2",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4930,22 +4918,22 @@
             }
         },
         "node_modules/@react-spectrum/button": {
-            "version": "3.16.1",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/button/-/button-3.16.1.tgz",
-            "integrity": "sha512-B1U4Mwq1BMId4pWJdDG0sgHDC05Ccu2ofoe3iR2444A4yzKpKC5OFr4lZXlevkxrwSLPwCmIBvpkGHxZswvJSA==",
-            "dependencies": {
-                "@react-aria/button": "^3.9.2",
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/progress": "^3.7.4",
-                "@react-spectrum/text": "^3.5.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/toggle": "^3.7.1",
-                "@react-types/button": "^3.9.1",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4954,14 +4942,14 @@
             }
         },
         "node_modules/@react-spectrum/buttongroup": {
-            "version": "3.6.10",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/buttongroup/-/buttongroup-3.6.10.tgz",
-            "integrity": "sha512-UunM+1u8Fwc5K6Th07ld2JbbP5PEpwt8ezQ+imA2YIeGXS+uKDi36ZCO0BM0VqZ92bd9k2UarZQwza4oRuIWYw==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/buttongroup": "^3.3.6",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4970,25 +4958,25 @@
             }
         },
         "node_modules/@react-spectrum/calendar": {
-            "version": "3.4.6",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/calendar/-/calendar-3.4.6.tgz",
-            "integrity": "sha512-uwBOyctISMNgZwpC33tvi5ResoKkcwRmUt8ifQwaYPxUHfUJCja30IQbsIbQ3EcWFkIJsdWgyqOGIhD04XNlPg==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.1",
-                "@react-aria/calendar": "^3.5.5",
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-aria/visually-hidden": "^3.8.9",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/label": "^3.16.3",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/calendar": "^3.4.3",
-                "@react-types/button": "^3.9.1",
-                "@react-types/calendar": "^3.4.3",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -4998,21 +4986,21 @@
             }
         },
         "node_modules/@react-spectrum/checkbox": {
-            "version": "3.9.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/checkbox/-/checkbox-3.9.3.tgz",
-            "integrity": "sha512-cho26UkAaIldsmHxueLUyjI6LJVAW+8n+M+wibZTW2QAI456G/NS9+bUC1ZRbK+d49meHOk9y6rcKUnzcTq5jg==",
-            "dependencies": {
-                "@react-aria/checkbox": "^3.14.0",
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-spectrum/form": "^3.7.3",
-                "@react-spectrum/label": "^3.16.3",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/checkbox": "^3.6.2",
-                "@react-stately/toggle": "^3.7.1",
-                "@react-types/checkbox": "^3.7.0",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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"
             },
             "peerDependencies": {
@@ -5021,34 +5009,34 @@
             }
         },
         "node_modules/@react-spectrum/combobox": {
-            "version": "3.12.2",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/combobox/-/combobox-3.12.2.tgz",
-            "integrity": "sha512-w3z9myQ/NYpxh0UQoVhWB9b1QV3wguR+bb3KdBBp3ECgGAOsN7eSe5A+pIWFAjgbBxHWK/JuY0agFs9IWkauOA==",
-            "dependencies": {
-                "@react-aria/button": "^3.9.2",
-                "@react-aria/combobox": "^3.8.3",
-                "@react-aria/dialog": "^3.5.11",
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/form": "^3.0.2",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/label": "^3.7.5",
-                "@react-aria/overlays": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/form": "^3.7.3",
-                "@react-spectrum/label": "^3.16.3",
-                "@react-spectrum/listbox": "^3.12.6",
-                "@react-spectrum/overlays": "^5.5.4",
-                "@react-spectrum/progress": "^3.7.4",
-                "@react-spectrum/textfield": "^3.11.3",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/combobox": "^3.8.1",
-                "@react-types/button": "^3.9.1",
-                "@react-types/combobox": "^3.10.0",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5058,18 +5046,18 @@
             }
         },
         "node_modules/@react-spectrum/contextualhelp": {
-            "version": "3.6.8",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/contextualhelp/-/contextualhelp-3.6.8.tgz",
-            "integrity": "sha512-3o1zCtDeDd3KGoKDS8uAdKcbUke1FPMErjPjhU/viKR/RY2uA2pjpPBpLSLKG3jV2IWOojAnCPvdOk3FVB7dKQ==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/dialog": "^3.8.8",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/contextualhelp": "^3.2.7",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/workflow": "^4.2.9",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5079,29 +5067,29 @@
             }
         },
         "node_modules/@react-spectrum/datepicker": {
-            "version": "3.9.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/datepicker/-/datepicker-3.9.3.tgz",
-            "integrity": "sha512-d8JCrgF7UyET+7tfaNKO+7/vTZ6LDgUqQ7yfMorh/IdM9o8PH1IpRLQlYY3E2o9PFGiP90raI37JEoeoLd0Rwg==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.1",
-                "@react-aria/datepicker": "^3.9.2",
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/calendar": "^3.4.6",
-                "@react-spectrum/dialog": "^3.8.8",
-                "@react-spectrum/form": "^3.7.3",
-                "@react-spectrum/label": "^3.16.3",
-                "@react-spectrum/layout": "^3.6.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-spectrum/view": "^3.6.7",
-                "@react-stately/datepicker": "^3.9.1",
-                "@react-types/datepicker": "^3.7.1",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
-                "@spectrum-icons/workflow": "^4.2.9",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5111,28 +5099,28 @@
             }
         },
         "node_modules/@react-spectrum/dialog": {
-            "version": "3.8.8",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/dialog/-/dialog-3.8.8.tgz",
-            "integrity": "sha512-4FJG/B287UKh67+3QQfJ6ZoTS0PSZ+H7DTbtRgS0+t47EL8R1z09LsPSLysB7s9ZUUTvYgYfUqCJwADHipe/ng==",
-            "dependencies": {
-                "@react-aria/dialog": "^3.5.11",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/overlays": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/buttongroup": "^3.6.10",
-                "@react-spectrum/divider": "^3.5.10",
-                "@react-spectrum/layout": "^3.6.2",
-                "@react-spectrum/overlays": "^5.5.4",
-                "@react-spectrum/text": "^3.5.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-spectrum/view": "^3.6.7",
-                "@react-stately/overlays": "^3.6.4",
-                "@react-types/button": "^3.9.1",
-                "@react-types/dialog": "^3.5.7",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5142,14 +5130,14 @@
             }
         },
         "node_modules/@react-spectrum/divider": {
-            "version": "3.5.10",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/divider/-/divider-3.5.10.tgz",
-            "integrity": "sha512-KcvVGd0fKwsveD7JPtz6DY/VBzI/kRWqr9kX7tC5dhSB/ob5C9EPABePQk1ei2C02/VX7GUF5zpL1PxpRZlxOg==",
-            "dependencies": {
-                "@react-aria/separator": "^3.3.10",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/divider": "^3.3.6",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5157,13 +5145,13 @@
             }
         },
         "node_modules/@react-spectrum/dnd": {
-            "version": "3.3.7",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/dnd/-/dnd-3.3.7.tgz",
-            "integrity": "sha512-PxODjyRbBLvHmE8ivsvm8BL+pkmyyG1HhlqvxFdlYq6wkWNwINuMBvn531B5AgJb2wwz6jKZ2PSAxegTxaxFrw==",
+            "version": "3.3.8",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/dnd/-/dnd-3.3.8.tgz",
+            "integrity": "sha512-GfLqduIBpH2LArBsFpNa4i9cYeFg7lJpqwrLVJRl96DbhdXEcwt9a4tqvsegkqKn6KPHDPG7uTX0OqPoQJluXQ==",
             "dependencies": {
-                "@react-aria/dnd": "^3.5.2",
-                "@react-stately/dnd": "^3.2.7",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/dnd": "^3.5.3",
+                "@react-stately/dnd": "^3.2.8",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5173,15 +5161,15 @@
             }
         },
         "node_modules/@react-spectrum/form": {
-            "version": "3.7.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/form/-/form-3.7.3.tgz",
-            "integrity": "sha512-KYnRXwYPpEnFLxLMm2GQwXwA3RvNNilLdLykksH2N0x30YYKU+QCU+6RMVKv1jpTSTXdFK8q+pjXMpmbEDib3g==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/form": "^3.0.0",
-                "@react-types/form": "^3.7.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5190,13 +5178,13 @@
             }
         },
         "node_modules/@react-spectrum/icon": {
-            "version": "3.7.10",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/icon/-/icon-3.7.10.tgz",
-            "integrity": "sha512-vz5vaFeJK9trnzKCzCusHZbk62c0x7CYznBthxXUj+8vLr5VBfwHxsdJe20dcOyjEj3klA/nUTtUf2GZ6jPWEg==",
+            "version": "3.7.11",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/icon/-/icon-3.7.11.tgz",
+            "integrity": "sha512-CTvbukq0g2oEjMqwMDx2qjfmFAv6u7/KwHRn9pGf6BUiU53Zlnl6Fjtm+c9cw8d9GcwlXyIWYBMIKyMm4+7g5A==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/utils": "^3.23.2",
+                "@react-spectrum/utils": "^3.11.5",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5205,15 +5193,15 @@
             }
         },
         "node_modules/@react-spectrum/illustratedmessage": {
-            "version": "3.4.10",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/illustratedmessage/-/illustratedmessage-3.4.10.tgz",
-            "integrity": "sha512-Kl6MYZ2FYsXT9/G+VZ2fwZIENhWtVEGiXLqBww7oeSUw1S9HuCQU4xhvp1Jln3/4RUihEiaKbCukJtzwSIohBA==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/layout": "^3.6.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/illustratedmessage": "^3.3.6",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5222,14 +5210,14 @@
             }
         },
         "node_modules/@react-spectrum/image": {
-            "version": "3.4.10",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/image/-/image-3.4.10.tgz",
-            "integrity": "sha512-r7guBwH6sjx/GlGwT/qGYO3T+IZHczrkMKleWS1mxB/8lc75R9+4lxD7AY/DZzgyvKMvX+hJltakPMFN/eCdJA==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/image": "^3.3.6",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5238,17 +5226,17 @@
             }
         },
         "node_modules/@react-spectrum/inlinealert": {
-            "version": "3.2.2",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/inlinealert/-/inlinealert-3.2.2.tgz",
-            "integrity": "sha512-PZsHIeMsrZy66WyXmYlWd1RCISulz4Afloslg3mHHsJC6nsafbqXxDLoDZM+8BvH8eXRV0zroBG22nbY3V+5Og==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/layout": "^3.6.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5257,18 +5245,18 @@
             }
         },
         "node_modules/@react-spectrum/label": {
-            "version": "3.16.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/label/-/label-3.16.3.tgz",
-            "integrity": "sha512-RR/oEICzx2GeEC2w4CV5hoodv2Iz8bmb5z8yF98190yOBGWq8ai+gq0JXYOwqBj2McRmp5AaWgecj8HhGmdV2Q==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/form": "^3.7.3",
-                "@react-spectrum/layout": "^3.6.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/label": "^3.9.0",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5277,16 +5265,16 @@
             }
         },
         "node_modules/@react-spectrum/labeledvalue": {
-            "version": "3.1.11",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/labeledvalue/-/labeledvalue-3.1.11.tgz",
-            "integrity": "sha512-OW4fHEVoVhp/A/urNSns9mvrBYmBNDgvR4R4fCpfio5AXevYUqgIzYmTSMY8LUWa9+EkbtCnp5JSmD3KM25wrA==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/label": "^3.16.3",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5295,14 +5283,14 @@
             }
         },
         "node_modules/@react-spectrum/layout": {
-            "version": "3.6.2",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/layout/-/layout-3.6.2.tgz",
-            "integrity": "sha512-B5asGN+uFlfThTms4KraAU4OapMmN9Ryr1uj5uhHLtCwyQcLphg1Q+LMX8LfrNr7vPLxgoZ4L3em7ohkSb8Okg==",
+            "version": "3.6.3",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/layout/-/layout-3.6.3.tgz",
+            "integrity": "sha512-JsG4KclsgzKfRbe+PyMo8QryG5SmwYlym8JRuSLmZ+nfIKhESVbqSJE3IXblmDDWUb0rabQhSvW4p5h5nOil4w==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/layout": "^3.3.12",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/utils": "^3.23.2",
+                "@react-spectrum/utils": "^3.11.5",
+                "@react-types/layout": "^3.3.13",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5311,16 +5299,16 @@
             }
         },
         "node_modules/@react-spectrum/link": {
-            "version": "3.6.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/link/-/link-3.6.4.tgz",
-            "integrity": "sha512-qxTCuGJuQd88j/Cq9yRTu3h48QV1h8Y9GVBoEVHnNQ4HGQjpDLkQkAFQwIZHku87RKhVY9LgA3YmBuo/1AJbsg==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/link": "^3.6.4",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/link": "^3.5.2",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5329,30 +5317,30 @@
             }
         },
         "node_modules/@react-spectrum/list": {
-            "version": "3.7.7",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/list/-/list-3.7.7.tgz",
-            "integrity": "sha512-qL3lKLUauZ8fZsujPWa8K/Rz+zsY5eRktdqckQGVKBRLGOTc/WQcn9csb2hNOBSlYX5rYUabU5Ia7yGfMMauuA==",
-            "dependencies": {
-                "@react-aria/button": "^3.9.2",
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/gridlist": "^3.7.4",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-aria/virtualizer": "^3.9.9",
-                "@react-aria/visually-hidden": "^3.8.9",
-                "@react-spectrum/checkbox": "^3.9.3",
-                "@react-spectrum/dnd": "^3.3.7",
-                "@react-spectrum/layout": "^3.6.2",
-                "@react-spectrum/progress": "^3.7.4",
-                "@react-spectrum/text": "^3.5.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/layout": "^3.13.6",
-                "@react-stately/list": "^3.10.2",
-                "@react-types/grid": "^3.2.3",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0",
                 "react-transition-group": "^4.4.5"
             },
@@ -5363,27 +5351,27 @@
             }
         },
         "node_modules/@react-spectrum/listbox": {
-            "version": "3.12.6",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/listbox/-/listbox-3.12.6.tgz",
-            "integrity": "sha512-PXGxWtPIbWjFxAnBOkUHlcoqy6QfvHYPwoGnS0cObEwPP6J4Int9MynMVg1S3wwxKl8quYPojoidsrp04CGmIA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/listbox": "^3.11.4",
-                "@react-aria/utils": "^3.23.1",
-                "@react-aria/virtualizer": "^3.9.9",
-                "@react-spectrum/layout": "^3.6.2",
-                "@react-spectrum/progress": "^3.7.4",
-                "@react-spectrum/text": "^3.5.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/layout": "^3.13.6",
-                "@react-stately/list": "^3.10.2",
-                "@react-stately/virtualizer": "^3.6.7",
-                "@react-types/listbox": "^3.4.6",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5393,31 +5381,31 @@
             }
         },
         "node_modules/@react-spectrum/menu": {
-            "version": "3.18.0",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/menu/-/menu-3.18.0.tgz",
-            "integrity": "sha512-LSCvhs1IoEYZs798VzOPJia4V1/xGMbKIF0Ai7xqi+9DU+M/PSzWlhIRa1Ya/nTRxdUNOygG13CaY+WYnjYzEA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/menu": "^3.13.0",
-                "@react-aria/overlays": "^3.21.0",
-                "@react-aria/separator": "^3.3.10",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/layout": "^3.6.2",
-                "@react-spectrum/overlays": "^5.5.4",
-                "@react-spectrum/text": "^3.5.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/menu": "^3.6.0",
-                "@react-stately/overlays": "^3.6.4",
-                "@react-stately/tree": "^3.7.5",
-                "@react-types/menu": "^3.9.6",
-                "@react-types/overlays": "^3.8.4",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
-                "@spectrum-icons/workflow": "^4.2.9",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5427,15 +5415,15 @@
             }
         },
         "node_modules/@react-spectrum/meter": {
-            "version": "3.4.10",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/meter/-/meter-3.4.10.tgz",
-            "integrity": "sha512-cC4WnAoUSTD7ikGN4KSS92Dd3XRFxkzYlSQYt/ijBg3RR8k2sIerqbv6eEbYqMoErt6a0VXgBqSdon9Qj1jkuQ==",
-            "dependencies": {
-                "@react-aria/meter": "^3.4.10",
-                "@react-spectrum/progress": "^3.7.4",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/meter": "^3.3.6",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5444,26 +5432,26 @@
             }
         },
         "node_modules/@react-spectrum/numberfield": {
-            "version": "3.9.0",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/numberfield/-/numberfield-3.9.0.tgz",
-            "integrity": "sha512-Zj51Q3mm7LSCClaN6StYAj11hXf23RD/KiBiKRb+0q2OfJAszAgxVzx71BWOHj0OnK5INlF5INp7GWhiVZ7rJQ==",
-            "dependencies": {
-                "@react-aria/button": "^3.9.2",
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/numberfield": "^3.11.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/form": "^3.7.3",
-                "@react-spectrum/label": "^3.16.3",
-                "@react-spectrum/textfield": "^3.11.3",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/numberfield": "^3.9.0",
-                "@react-types/button": "^3.9.1",
-                "@react-types/numberfield": "^3.8.0",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
-                "@spectrum-icons/workflow": "^4.2.9",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5473,17 +5461,17 @@
             }
         },
         "node_modules/@react-spectrum/overlays": {
-            "version": "5.5.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/overlays/-/overlays-5.5.4.tgz",
-            "integrity": "sha512-xiIm84B1YbUOh8LxdpLMQN220/E6dyqTqB4KCTZ89YiKygJiEH9YPqM1GBsOEcpVUT+MEDPASixh2ADjY/YeIA==",
-            "dependencies": {
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/overlays": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/overlays": "^3.6.4",
-                "@react-types/overlays": "^3.8.4",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0",
                 "react-transition-group": "^4.4.5"
             },
@@ -5494,27 +5482,27 @@
             }
         },
         "node_modules/@react-spectrum/picker": {
-            "version": "3.14.2",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/picker/-/picker-3.14.2.tgz",
-            "integrity": "sha512-ZswvbG+2u6+c6qdy6wjh9IinT/Bkt1+Q7tuLGSTsPPAtWJoFEKQ9tntuvtYJx23vBZ2lrtGOWidJOeqKlOfshQ==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/select": "^3.14.2",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/form": "^3.7.3",
-                "@react-spectrum/label": "^3.16.3",
-                "@react-spectrum/listbox": "^3.12.6",
-                "@react-spectrum/overlays": "^5.5.4",
-                "@react-spectrum/progress": "^3.7.4",
-                "@react-spectrum/text": "^3.5.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/select": "^3.6.1",
-                "@react-types/select": "^3.9.1",
-                "@react-types/shared": "^3.22.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5524,15 +5512,15 @@
             }
         },
         "node_modules/@react-spectrum/progress": {
-            "version": "3.7.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/progress/-/progress-3.7.4.tgz",
-            "integrity": "sha512-xEz8RX54rPPw/WS6bgBpop2C3s7wjakwOT42KIZz0vVQrRCa596Mtmy7IrLLtyfHuIgNgNHs/BVaH46Nzrr9Uw==",
-            "dependencies": {
-                "@react-aria/progress": "^3.4.10",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/progress": "^3.5.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5541,16 +5529,16 @@
             }
         },
         "node_modules/@react-spectrum/provider": {
-            "version": "3.9.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/provider/-/provider-3.9.4.tgz",
-            "integrity": "sha512-GEfmQCC/s0JRxMR7WA41vyGnSPssJTA1+ijTkDLGbhCyKmUq6pmNFaCj3WRW/ki+Gg42eWaNXth5sPexbxr9qA==",
-            "dependencies": {
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/overlays": "^3.21.0",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/provider": "^3.7.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0",
                 "clsx": "^2.0.0"
             },
@@ -5560,19 +5548,19 @@
             }
         },
         "node_modules/@react-spectrum/radio": {
-            "version": "3.7.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/radio/-/radio-3.7.3.tgz",
-            "integrity": "sha512-wi0ielcjBbsDm3kgDXutWyJVQa31+8r2++1N+omj0zkffOiHqK5m+9faUikX7DKwQ4d/CkbAegrkN6q4yxkwbw==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/radio": "^3.10.1",
-                "@react-spectrum/form": "^3.7.3",
-                "@react-spectrum/label": "^3.16.3",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/radio": "^3.10.1",
-                "@react-types/radio": "^3.7.0",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5581,19 +5569,19 @@
             }
         },
         "node_modules/@react-spectrum/searchfield": {
-            "version": "3.8.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/searchfield/-/searchfield-3.8.3.tgz",
-            "integrity": "sha512-eHdfbQn2Jp2/USsKHOjLlVTqcnPHKUYa9f4bW9cXO0y08gfI1CSOhNlK3TYs625lvsRGCraCpsi6X2oStRWb8w==",
-            "dependencies": {
-                "@react-aria/searchfield": "^3.7.2",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/form": "^3.7.3",
-                "@react-spectrum/textfield": "^3.11.3",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/searchfield": "^3.5.0",
-                "@react-types/searchfield": "^3.5.2",
-                "@react-types/textfield": "^3.9.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5602,20 +5590,20 @@
             }
         },
         "node_modules/@react-spectrum/slider": {
-            "version": "3.6.6",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/slider/-/slider-3.6.6.tgz",
-            "integrity": "sha512-Meav6BLiTuuGxk+qFM3MJJubSIdBytb+Cfa3SIsZ8zx1X0O3eY517byJYjOVLo/FhzloWWNupr7aMPAGfOKb2A==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/slider": "^3.7.5",
-                "@react-aria/utils": "^3.23.1",
-                "@react-aria/visually-hidden": "^3.8.9",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/slider": "^3.5.1",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/slider": "^3.7.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5624,14 +5612,14 @@
             }
         },
         "node_modules/@react-spectrum/statuslight": {
-            "version": "3.5.10",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/statuslight/-/statuslight-3.5.10.tgz",
-            "integrity": "sha512-s697KmFlwF1dbM0nTNDanL6jqqC6da86tu8sqU7IWqowymbpkIdorFFHuFDiOUg+rKa+QXKzeLicaxH75o6oyw==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/statuslight": "^3.3.6",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5640,17 +5628,17 @@
             }
         },
         "node_modules/@react-spectrum/switch": {
-            "version": "3.5.2",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/switch/-/switch-3.5.2.tgz",
-            "integrity": "sha512-gpxgU5z0BlaorNjCw6J9c2kSQA9ZR7tjg3hF3491i+h2+xc2NuTQe18WYEHTXFTmlXVEEXE97BEtoX4KY7YU0g==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/switch": "^3.6.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/toggle": "^3.7.1",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/switch": "^3.5.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5659,33 +5647,33 @@
             }
         },
         "node_modules/@react-spectrum/table": {
-            "version": "3.12.7",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/table/-/table-3.12.7.tgz",
-            "integrity": "sha512-A+jirOXULysnKvrN0Q8hS4t64I8fFoyyXYAlfSTnza3n2hXKxYnYenKP/VjB7PvdFwZiBAY4+VVg37E0JfFUWw==",
-            "dependencies": {
-                "@react-aria/button": "^3.9.2",
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/table": "^3.13.4",
-                "@react-aria/utils": "^3.23.1",
-                "@react-aria/virtualizer": "^3.9.9",
-                "@react-aria/visually-hidden": "^3.8.9",
-                "@react-spectrum/checkbox": "^3.9.3",
-                "@react-spectrum/dnd": "^3.3.7",
-                "@react-spectrum/layout": "^3.6.2",
-                "@react-spectrum/menu": "^3.18.0",
-                "@react-spectrum/progress": "^3.7.4",
-                "@react-spectrum/tooltip": "^3.6.4",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/flags": "^3.0.0",
-                "@react-stately/layout": "^3.13.6",
-                "@react-stately/table": "^3.11.5",
-                "@react-stately/virtualizer": "^3.6.7",
-                "@react-types/grid": "^3.2.3",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/table": "^3.9.2",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5695,24 +5683,24 @@
             }
         },
         "node_modules/@react-spectrum/tabs": {
-            "version": "3.8.7",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/tabs/-/tabs-3.8.7.tgz",
-            "integrity": "sha512-DcPn/pcYuXiS6cwuMT4k3tusDoMuUTjWUYpAcHwDue9xcUGsb6BrNmMxrQ6J08nQN6DhUizqtGA9CCTS9NwcnA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/tabs": "^3.8.4",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/picker": "^3.14.2",
-                "@react-spectrum/text": "^3.5.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/list": "^3.10.2",
-                "@react-stately/tabs": "^3.6.3",
-                "@react-types/select": "^3.9.1",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/tabs": "^3.3.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5722,24 +5710,24 @@
             }
         },
         "node_modules/@react-spectrum/tag": {
-            "version": "3.2.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/tag/-/tag-3.2.3.tgz",
-            "integrity": "sha512-9LZ6SplsNUtvH3VjWZEMpDxbcQSNLNUuoYp9mISzeD8Zf2aNhHLIzm5kyB29hZcZO7lZzZY8GdX44a5awmKLZw==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/i18n": "^3.10.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/selection": "^3.17.4",
-                "@react-aria/tag": "^3.3.2",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/button": "^3.16.1",
-                "@react-spectrum/form": "^3.7.3",
-                "@react-spectrum/label": "^3.16.3",
-                "@react-spectrum/text": "^3.5.2",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/list": "^3.10.2",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5749,14 +5737,14 @@
             }
         },
         "node_modules/@react-spectrum/text": {
-            "version": "3.5.2",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/text/-/text-3.5.2.tgz",
-            "integrity": "sha512-cv2WrZrM24btD2zRDs/Qg5n/1+EE2D69RbGySQrJWerohPRJzYoID/ZXPsn0W6gKVivbn6lRQ1+c9ptoXA7b8Q==",
+            "version": "3.5.3",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/text/-/text-3.5.3.tgz",
+            "integrity": "sha512-28WiSnIPm8WC4JOsHPLudCGjPc3tk7zKm3L+8xqzr374j1+HdMGivrhVOf1t4jbUMcT5dOxR7huuDe7E97qe5A==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/text": "^3.3.6",
+                "@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"
             },
             "peerDependencies": {
@@ -5765,21 +5753,21 @@
             }
         },
         "node_modules/@react-spectrum/textfield": {
-            "version": "3.11.3",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/textfield/-/textfield-3.11.3.tgz",
-            "integrity": "sha512-859j6VUgXYdF/LoYnindXTirZHfyhX22/6BreQl7mxJT0JDqYn8G/k7pauRSGyw+Di3zuvujEKu8ZdJAMm/0JA==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/interactions": "^3.21.0",
-                "@react-aria/textfield": "^3.14.2",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/form": "^3.7.3",
-                "@react-spectrum/label": "^3.16.3",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/textfield": "^3.9.0",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5788,11 +5776,11 @@
             }
         },
         "node_modules/@react-spectrum/theme-dark": {
-            "version": "3.5.7",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/theme-dark/-/theme-dark-3.5.7.tgz",
-            "integrity": "sha512-WaBVkhJKsZNcsHCcm6HQOSV934KLxZK6cbTt/FjUZtNTHcQTb5s2xf2jPeMwcRbRJfzagfTuTr5BkM38B9Zi1Q==",
+            "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==",
             "dependencies": {
-                "@react-types/provider": "^3.7.1",
+                "@react-types/provider": "^3.7.2",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5800,11 +5788,11 @@
             }
         },
         "node_modules/@react-spectrum/theme-default": {
-            "version": "3.5.7",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/theme-default/-/theme-default-3.5.7.tgz",
-            "integrity": "sha512-5GgCKfzA1be3pE5XtQ+5hk39I4BhU1qY+fHCDxvE/tndm2I7pCk26d6ifxA0H2RVKH8V3eiH0WG1TNXHcHIO1g==",
+            "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==",
             "dependencies": {
-                "@react-types/provider": "^3.7.1",
+                "@react-types/provider": "^3.7.2",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5812,11 +5800,11 @@
             }
         },
         "node_modules/@react-spectrum/theme-light": {
-            "version": "3.4.7",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/theme-light/-/theme-light-3.4.7.tgz",
-            "integrity": "sha512-3NeIDN9QU8c40rqRSzurTdJRTZ5Uag4c2fLlF9Bve/yF7N6KlzK29H3EWNLBizAfozhPe5/HDEYqdArz7bxhlQ==",
+            "version": "3.4.8",
+            "resolved": "https://registry.npmjs.org/@react-spectrum/theme-light/-/theme-light-3.4.8.tgz",
+            "integrity": "sha512-6kjtQ6IGrh2E0WJKQ9eAR5yhcWFI2mNvd+wi8Uy3rtRdBB1L3iSfr2RmgxgcTCl50DnKqmcFsHbHaKMMKqhkaQ==",
             "dependencies": {
-                "@react-types/provider": "^3.7.1",
+                "@react-types/provider": "^3.7.2",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5824,21 +5812,21 @@
             }
         },
         "node_modules/@react-spectrum/tooltip": {
-            "version": "3.6.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/tooltip/-/tooltip-3.6.4.tgz",
-            "integrity": "sha512-z8RGtQjUHRT2haI1tz7lB9STuv6kj8GBGr++Zo5xE8nhY2QASRXE4gZ16YnGI8l2c7EFyLFstAEtbrxt835GWQ==",
-            "dependencies": {
-                "@react-aria/focus": "^3.16.1",
-                "@react-aria/overlays": "^3.21.0",
-                "@react-aria/tooltip": "^3.7.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/overlays": "^5.5.4",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-stately/tooltip": "^3.4.6",
-                "@react-types/overlays": "^3.8.4",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/tooltip": "^3.4.6",
-                "@spectrum-icons/ui": "^3.6.4",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5848,14 +5836,14 @@
             }
         },
         "node_modules/@react-spectrum/utils": {
-            "version": "3.11.4",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/utils/-/utils-3.11.4.tgz",
-            "integrity": "sha512-x1GfD25riFzbkscmLR7EUVDJolwjz7QSn8udtAW09kCcpCBjyA+SMmjt+rCpzYM4mtGfPlcJJrYDGDarGjNWxQ==",
+            "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.1",
-                "@react-aria/ssr": "^3.9.1",
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/i18n": "^3.10.2",
+                "@react-aria/ssr": "^3.9.2",
+                "@react-aria/utils": "^3.23.2",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0",
                 "clsx": "^2.0.0"
             },
@@ -5864,14 +5852,14 @@
             }
         },
         "node_modules/@react-spectrum/view": {
-            "version": "3.6.7",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/view/-/view-3.6.7.tgz",
-            "integrity": "sha512-eCwFfoMN7j4G44GZfGmFM+jWsD4zBxbmLsbrm6H1kEmxeeJkCM1jwF2H3Vc4WJxaBuOBY7tce8IHBRwQ3l0Wng==",
+            "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==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/view": "^3.4.6",
+                "@react-aria/utils": "^3.23.2",
+                "@react-spectrum/utils": "^3.11.5",
+                "@react-types/shared": "^3.22.1",
+                "@react-types/view": "^3.4.7",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5880,14 +5868,14 @@
             }
         },
         "node_modules/@react-spectrum/well": {
-            "version": "3.4.10",
-            "resolved": "https://registry.npmjs.org/@react-spectrum/well/-/well-3.4.10.tgz",
-            "integrity": "sha512-k1xnXk75R7V2DEbI8+rejT3d2pQR5j6DHZt/JzBbpmuGxvKqlkMgr364vBmusOEu8lvUE5/Q6O+qdnNTZeirWw==",
-            "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-spectrum/utils": "^3.11.4",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/well": "^3.3.6",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5895,14 +5883,14 @@
             }
         },
         "node_modules/@react-stately/calendar": {
-            "version": "3.4.3",
-            "resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.4.3.tgz",
-            "integrity": "sha512-OrEcdskszDjnjVnFuSiDC2PVBJ6lWMCJROD5s6W1LUehUtBp8LX9wPavAGHV43LbhN9ldj560sxaQ4WCddrRCA==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.1",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/calendar": "^3.4.3",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5910,14 +5898,14 @@
             }
         },
         "node_modules/@react-stately/checkbox": {
-            "version": "3.6.2",
-            "resolved": "https://registry.npmjs.org/@react-stately/checkbox/-/checkbox-3.6.2.tgz",
-            "integrity": "sha512-IzeyGd3MKoOAzvgbmds8wnCWRFUmQUznXEMxl1DbpqYpB+OH4nMS81D7yLSVeQPRtxcqKCSx+/98oycMThCilw==",
+            "version": "3.6.3",
+            "resolved": "https://registry.npmjs.org/@react-stately/checkbox/-/checkbox-3.6.3.tgz",
+            "integrity": "sha512-hWp0GXVbMI4sS2NbBjWgOnHNrRqSV4jeftP8zc5JsIYRmrWBUZitxluB34QuVPzrBO29bGsF0GTArSiQZt6BWw==",
             "dependencies": {
-                "@react-stately/form": "^3.0.0",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/checkbox": "^3.7.0",
-                "@react-types/shared": "^3.22.0",
+                "@react-stately/form": "^3.0.1",
+                "@react-stately/utils": "^3.9.1",
+                "@react-types/checkbox": "^3.7.1",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5925,11 +5913,11 @@
             }
         },
         "node_modules/@react-stately/collections": {
-            "version": "3.10.4",
-            "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.10.4.tgz",
-            "integrity": "sha512-OHhCrItGt4zB2bSrgObRo0H2SC7QlkH8ReGxo+NVIWchXRLRoiWBP7S+IwleewEo5gOqDVPY3hqA9n4iiI8twg==",
+            "version": "3.10.5",
+            "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.10.5.tgz",
+            "integrity": "sha512-k8Q29Nnvb7iAia1QvTanZsrWP2aqVNBy/1SlE6kLL6vDqtKZC+Esd1SDLHRmIcYIp5aTdfwIGd0NuiRQA7a81Q==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5937,18 +5925,18 @@
             }
         },
         "node_modules/@react-stately/combobox": {
-            "version": "3.8.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/combobox/-/combobox-3.8.1.tgz",
-            "integrity": "sha512-FaWkqTXQdWg7ptaeU4iPcqF/kxbRg2ZNUcvW/hiL/enciV5tRCsddvfNqvDvy1L30z9AUwlp9MWqzm/DhBITCw==",
-            "dependencies": {
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/form": "^3.0.0",
-                "@react-stately/list": "^3.10.2",
-                "@react-stately/overlays": "^3.6.4",
-                "@react-stately/select": "^3.6.1",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/combobox": "^3.10.0",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5956,11 +5944,11 @@
             }
         },
         "node_modules/@react-stately/data": {
-            "version": "3.11.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/data/-/data-3.11.1.tgz",
-            "integrity": "sha512-JedDhZ5e6Qetf+TGXKBdVVEvB50BymNJHKRFRQ9E3mmh/KFeY4V8THHKrNE/BhzB6Z3onsp5r14Z66Nku+klTg==",
+            "version": "3.11.2",
+            "resolved": "https://registry.npmjs.org/@react-stately/data/-/data-3.11.2.tgz",
+            "integrity": "sha512-yhK2upk2WbJeiLBRWHrh/4G2CvmmozCzoivLaRAPYu53m1J3MyzVGCLJgnZMbMZvAbNcYWZK6IzO6VqZ2y1fOw==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5968,17 +5956,17 @@
             }
         },
         "node_modules/@react-stately/datepicker": {
-            "version": "3.9.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/datepicker/-/datepicker-3.9.1.tgz",
-            "integrity": "sha512-o5xLvlZGJyAbTev2yruGlV2fzQyIDuYTgL19TTt0W0WCfjGGr/AAA9GjGXXmyoRA7sZMxqIPnnv7lNrdA38ofA==",
-            "dependencies": {
-                "@internationalized/date": "^3.5.1",
-                "@internationalized/string": "^3.2.0",
-                "@react-stately/form": "^3.0.0",
-                "@react-stately/overlays": "^3.6.4",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/datepicker": "^3.7.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5986,12 +5974,12 @@
             }
         },
         "node_modules/@react-stately/dnd": {
-            "version": "3.2.7",
-            "resolved": "https://registry.npmjs.org/@react-stately/dnd/-/dnd-3.2.7.tgz",
-            "integrity": "sha512-QqSCvE9Rhp+Mr8Mt/SrByze24BFX1cy7gmXbwoqAYgHNIx3gWCVdBLqxfpfgYIhZdF9H72EWS8lQkfkZla06Ng==",
+            "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==",
             "dependencies": {
-                "@react-stately/selection": "^3.14.2",
-                "@react-types/shared": "^3.22.0",
+                "@react-stately/selection": "^3.14.3",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -5999,9 +5987,9 @@
             }
         },
         "node_modules/@react-stately/flags": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.0.0.tgz",
-            "integrity": "sha512-e3i2ItHbIa0eEwmSXAnPdD7K8syW76JjGe8ENxwFJPW/H1Pu9RJfjkCb/Mq0WSPN/TpxBb54+I9TgrGhbCoZ9w==",
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.0.1.tgz",
+            "integrity": "sha512-h5PcDMj54aipQNO18ig/IMI1kzPwcvSwVq5M6Ib6XE1WIkOH0dIuW2eADdAOhcGi3KXJtXVdD29zh0Eox1TKgQ==",
             "dependencies": {
                 "@swc/helpers": "^0.4.14"
             }
@@ -6016,11 +6004,11 @@
             }
         },
         "node_modules/@react-stately/form": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.0.0.tgz",
-            "integrity": "sha512-C8wkfFmtx1escizibhdka5JvTy9/Vp173CS9cakjvWTmnjYYC1nOlzwp7BsYWTgerCFbRY/BU/Cf/bJDxPiUKQ==",
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.0.1.tgz",
+            "integrity": "sha512-T1Ul2Ou0uE/S4ECLcGKa0OfXjffdjEHfUFZAk7OZl0Mqq/F7dl5WpoLWJ4d4IyvZzGO6anFNenP+vODWbrF3NA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6028,14 +6016,14 @@
             }
         },
         "node_modules/@react-stately/grid": {
-            "version": "3.8.4",
-            "resolved": "https://registry.npmjs.org/@react-stately/grid/-/grid-3.8.4.tgz",
-            "integrity": "sha512-rwqV1K4lVhaiaqJkt4TfYqdJoVIyqvSm98rKAYfCNzrKcivVpoiCMJ2EMt6WlYCjDVBdEOQ7fMV1I60IV0pntA==",
-            "dependencies": {
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/selection": "^3.14.2",
-                "@react-types/grid": "^3.2.3",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6043,16 +6031,16 @@
             }
         },
         "node_modules/@react-stately/layout": {
-            "version": "3.13.6",
-            "resolved": "https://registry.npmjs.org/@react-stately/layout/-/layout-3.13.6.tgz",
-            "integrity": "sha512-lfkLbPjwsvmA/Rk4OodtrBfFNiApvk5t2cehYHtUU5OFvHGaqt/n//dZ85XI2ooUq1FwfBZZ0ZXAAvJeaWR1QA==",
-            "dependencies": {
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/table": "^3.11.5",
-                "@react-stately/virtualizer": "^3.6.7",
-                "@react-types/grid": "^3.2.3",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/table": "^3.9.2",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6060,14 +6048,14 @@
             }
         },
         "node_modules/@react-stately/list": {
-            "version": "3.10.2",
-            "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.10.2.tgz",
-            "integrity": "sha512-INt+zofkIg2KN8B95xPi9pJG7ZFWAm30oIm/lCPBqM3K1Nm03/QaAbiQj2QeJcOsG3lb7oqI6D6iwTolwJkjIQ==",
-            "dependencies": {
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/selection": "^3.14.2",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6075,13 +6063,13 @@
             }
         },
         "node_modules/@react-stately/menu": {
-            "version": "3.6.0",
-            "resolved": "https://registry.npmjs.org/@react-stately/menu/-/menu-3.6.0.tgz",
-            "integrity": "sha512-OB6CjNyfOkAuirqx1oTL8z8epS9WDzLyrXjmRnxdiCU9EgRXLGAQNECuO7VIpl58oDry8tgRJiJ8fn8FivWSQA==",
+            "version": "3.6.1",
+            "resolved": "https://registry.npmjs.org/@react-stately/menu/-/menu-3.6.1.tgz",
+            "integrity": "sha512-3v0vkTm/kInuuG8jG7jbxXDBnMQcoDZKWvYsBQq7+POt0LmijbLdbdZPBoz9TkZ3eo/OoP194LLHOaFTQyHhlw==",
             "dependencies": {
-                "@react-stately/overlays": "^3.6.4",
-                "@react-types/menu": "^3.9.6",
-                "@react-types/shared": "^3.22.0",
+                "@react-stately/overlays": "^3.6.5",
+                "@react-types/menu": "^3.9.7",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6089,14 +6077,14 @@
             }
         },
         "node_modules/@react-stately/numberfield": {
-            "version": "3.9.0",
-            "resolved": "https://registry.npmjs.org/@react-stately/numberfield/-/numberfield-3.9.0.tgz",
-            "integrity": "sha512-8O802S38e1htZbSzpPjbbIGAGxGC/DIzcW8H03UmBXiIFosEjpdmm8qRrJbhGfJGpwnehtzJQ6EaOLgLZMCFKg==",
-            "dependencies": {
-                "@internationalized/number": "^3.5.0",
-                "@react-stately/form": "^3.0.0",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/numberfield": "^3.8.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6104,12 +6092,12 @@
             }
         },
         "node_modules/@react-stately/overlays": {
-            "version": "3.6.4",
-            "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.4.tgz",
-            "integrity": "sha512-tHEaoAGpE9dSnsskqLPVKum59yGteoSqsniTopodM+miQozbpPlSjdiQnzGLroy5Afx5OZYClE616muNHUILXA==",
+            "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==",
             "dependencies": {
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/overlays": "^3.8.4",
+                "@react-stately/utils": "^3.9.1",
+                "@react-types/overlays": "^3.8.5",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6117,14 +6105,14 @@
             }
         },
         "node_modules/@react-stately/radio": {
-            "version": "3.10.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/radio/-/radio-3.10.1.tgz",
-            "integrity": "sha512-MsBYbcLCvjKsqTAKe43T681F2XwKMsS7PLG0eplZgWP9210AMY78GeY1XPYZKHPAau8XkbYiuJqbqTerIJ3DBw==",
+            "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.0",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/radio": "^3.7.0",
-                "@react-types/shared": "^3.22.0",
+                "@react-stately/form": "^3.0.1",
+                "@react-stately/utils": "^3.9.1",
+                "@react-types/radio": "^3.7.1",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6132,12 +6120,12 @@
             }
         },
         "node_modules/@react-stately/searchfield": {
-            "version": "3.5.0",
-            "resolved": "https://registry.npmjs.org/@react-stately/searchfield/-/searchfield-3.5.0.tgz",
-            "integrity": "sha512-SStjChkn/33pEn40slKQPnBnmQYyxVazVwPjiBkdeVejC42lUVairUTrGJgF0PNoZTbxn0so2/XzjqTC9T8iCw==",
+            "version": "3.5.1",
+            "resolved": "https://registry.npmjs.org/@react-stately/searchfield/-/searchfield-3.5.1.tgz",
+            "integrity": "sha512-9A8Wghx1avRHhMpNH1Nj+jFfiF1bhsff2GEC5PZgWYzhCykw3G5bywn3JAuUS4kh7Vpqhbu4KpHAhmWPSv4B/Q==",
             "dependencies": {
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/searchfield": "^3.5.2",
+                "@react-stately/utils": "^3.9.1",
+                "@react-types/searchfield": "^3.5.3",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6145,15 +6133,15 @@
             }
         },
         "node_modules/@react-stately/select": {
-            "version": "3.6.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/select/-/select-3.6.1.tgz",
-            "integrity": "sha512-e5ixtLiYLlFWM8z1msDqXWhflF9esIRfroptZsltMn1lt2iImUlDRlOTZlMtPQzUrDWoiHXRX88sSKUM/jXjQQ==",
-            "dependencies": {
-                "@react-stately/form": "^3.0.0",
-                "@react-stately/list": "^3.10.2",
-                "@react-stately/overlays": "^3.6.4",
-                "@react-types/select": "^3.9.1",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6161,13 +6149,13 @@
             }
         },
         "node_modules/@react-stately/selection": {
-            "version": "3.14.2",
-            "resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.14.2.tgz",
-            "integrity": "sha512-mL7OoiUgVWaaF7ks5XSxgbXeShijYmD4G3bkBHhqkpugU600QH6BM2hloCq8KOUupk1y8oTljPtF9EmCv375DA==",
+            "version": "3.14.3",
+            "resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.14.3.tgz",
+            "integrity": "sha512-d/t0rIWieqQ7wjLoMoWnuHEUSMoVXxkPBFuSlJF3F16289FiQ+b8aeKFDzFTYN7fFD8rkZTnpuE4Tcxg3TmA+w==",
             "dependencies": {
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/shared": "^3.22.0",
+                "@react-stately/collections": "^3.10.5",
+                "@react-stately/utils": "^3.9.1",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6175,13 +6163,13 @@
             }
         },
         "node_modules/@react-stately/slider": {
-            "version": "3.5.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/slider/-/slider-3.5.1.tgz",
-            "integrity": "sha512-NRZ5m1wOVxGZF1CQC6hOzt/LmHNUF2xpFSkzN29fW/InPH4jb3BuOkRbbWv76QaVe0Kdg2ZLWcMl2+Qt6adIeQ==",
+            "version": "3.5.2",
+            "resolved": "https://registry.npmjs.org/@react-stately/slider/-/slider-3.5.2.tgz",
+            "integrity": "sha512-ntH3NLRG+AwVC7q4Dx9DcmMkMh9vmHjHNXAgaoqNjhvwfSIae7sQ69CkVe6XeJjIBy6LlH81Kgapz+ABe5a1ZA==",
             "dependencies": {
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/slider": "^3.7.0",
+                "@react-stately/utils": "^3.9.1",
+                "@react-types/shared": "^3.22.1",
+                "@react-types/slider": "^3.7.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6189,18 +6177,18 @@
             }
         },
         "node_modules/@react-stately/table": {
-            "version": "3.11.5",
-            "resolved": "https://registry.npmjs.org/@react-stately/table/-/table-3.11.5.tgz",
-            "integrity": "sha512-l84iZJxpR0vlprHNEeGCVZTjOivP5fLpllmG+GswGxN4JXDqCEZ6gCQzpXxLyQTyBZ8lTRmmmmW20V2nCmDO4w==",
-            "dependencies": {
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/flags": "^3.0.0",
-                "@react-stately/grid": "^3.8.4",
-                "@react-stately/selection": "^3.14.2",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/grid": "^3.2.3",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/table": "^3.9.2",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6208,13 +6196,13 @@
             }
         },
         "node_modules/@react-stately/tabs": {
-            "version": "3.6.3",
-            "resolved": "https://registry.npmjs.org/@react-stately/tabs/-/tabs-3.6.3.tgz",
-            "integrity": "sha512-Nj+Gacwa2SIzYIvHW40GsyX4Q6c8kF7GOuXESeQswbCjnwqhrSbDBp+ngPcUPUJxqFh6JhDCVwAS3wMhUoyUwA==",
+            "version": "3.6.4",
+            "resolved": "https://registry.npmjs.org/@react-stately/tabs/-/tabs-3.6.4.tgz",
+            "integrity": "sha512-WZJgMBqzLgN88RN8AxhY4aH1+I+4w1qQA0Lh3LRSDegaytd+NHixCWaP3IPjePgCB5N1UsPe96Xglw75zjHmDg==",
             "dependencies": {
-                "@react-stately/list": "^3.10.2",
-                "@react-types/shared": "^3.22.0",
-                "@react-types/tabs": "^3.3.4",
+                "@react-stately/list": "^3.10.3",
+                "@react-types/shared": "^3.22.1",
+                "@react-types/tabs": "^3.3.5",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6222,12 +6210,12 @@
             }
         },
         "node_modules/@react-stately/toggle": {
-            "version": "3.7.1",
-            "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.7.1.tgz",
-            "integrity": "sha512-pZyhPJNdhidm/Uq/Pt58H0I6CUNyfnhfGAAn9Et6T3/SymcX1Zti5mZg5gXgICFlwGbucfLBe+Jt691Rnt2vaA==",
+            "version": "3.7.2",
+            "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.7.2.tgz",
+            "integrity": "sha512-SHCF2btcoK57c4lyhucRbyPBAFpp0Pdp0vcPdn3hUgqbu6e5gE0CwG/mgFmZRAQoc7PRc7XifL0uNw8diJJI0Q==",
             "dependencies": {
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/checkbox": "^3.7.0",
+                "@react-stately/utils": "^3.9.1",
+                "@react-types/checkbox": "^3.7.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6235,12 +6223,12 @@
             }
         },
         "node_modules/@react-stately/tooltip": {
-            "version": "3.4.6",
-            "resolved": "https://registry.npmjs.org/@react-stately/tooltip/-/tooltip-3.4.6.tgz",
-            "integrity": "sha512-uL93bmsXf+OOgpKLPEKfpDH4z+MK2CuqlqVxx7rshN0vjWOSoezE5nzwgee90+RpDrLNNNWTNa7n+NkDRpI1jA==",
+            "version": "3.4.7",
+            "resolved": "https://registry.npmjs.org/@react-stately/tooltip/-/tooltip-3.4.7.tgz",
+            "integrity": "sha512-ACtRgBQ8rphBtsUaaxvEAM0HHN9PvMuyvL0vUHd7jvBDCVZJ6it1BKu9SBKjekBkoBOw9nemtkplh9R2CA6V8Q==",
             "dependencies": {
-                "@react-stately/overlays": "^3.6.4",
-                "@react-types/tooltip": "^3.4.6",
+                "@react-stately/overlays": "^3.6.5",
+                "@react-types/tooltip": "^3.4.7",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6248,14 +6236,14 @@
             }
         },
         "node_modules/@react-stately/tree": {
-            "version": "3.7.5",
-            "resolved": "https://registry.npmjs.org/@react-stately/tree/-/tree-3.7.5.tgz",
-            "integrity": "sha512-xTJVwvhAeY0N5rui4N/TxN7f8hjXdqApDuGDxMZeFAWoQz8Abf7LFKBVQ3OkT6qVr7P+23dgoisUDBhD5a45Hg==",
-            "dependencies": {
-                "@react-stately/collections": "^3.10.4",
-                "@react-stately/selection": "^3.14.2",
-                "@react-stately/utils": "^3.9.0",
-                "@react-types/shared": "^3.22.0",
+            "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",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6263,9 +6251,9 @@
             }
         },
         "node_modules/@react-stately/utils": {
-            "version": "3.9.0",
-            "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.9.0.tgz",
-            "integrity": "sha512-yPKFY1F88HxuZ15BG2qwAYxtpE4HnIU0Ofi4CuBE0xC6I8mwo4OQjDzi+DZjxQngM9D6AeTTD6F1V8gkozA0Gw==",
+            "version": "3.9.1",
+            "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.9.1.tgz",
+            "integrity": "sha512-yzw75GE0iUWiyps02BOAPTrybcsMIxEJlzXqtvllAb01O9uX5n0i3X+u2eCpj2UoDF4zS08Ps0jPgWxg8xEYtA==",
             "dependencies": {
                 "@swc/helpers": "^0.5.0"
             },
@@ -6274,12 +6262,12 @@
             }
         },
         "node_modules/@react-stately/virtualizer": {
-            "version": "3.6.7",
-            "resolved": "https://registry.npmjs.org/@react-stately/virtualizer/-/virtualizer-3.6.7.tgz",
-            "integrity": "sha512-huhQSrfwiUq2idceSE2aQ54d9gttAovKDtw7uERWFt+UAxiprWq8hr6sl7rTdN2NB7fz/t+MAJJuwWMkzLUlOw==",
+            "version": "3.6.8",
+            "resolved": "https://registry.npmjs.org/@react-stately/virtualizer/-/virtualizer-3.6.8.tgz",
+            "integrity": "sha512-Pf06ihTwExRJltGhi72tmLIo0pcjkL55nu7ifMafAAdxZK4ONxRLSuUjjpvYf/0Rs92xRZy2t/XmHREnfirdkQ==",
             "dependencies": {
-                "@react-aria/utils": "^3.23.1",
-                "@react-types/shared": "^3.22.0",
+                "@react-aria/utils": "^3.23.2",
+                "@react-types/shared": "^3.22.1",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6287,470 +6275,470 @@
             }
         },
         "node_modules/@react-types/actionbar": {
-            "version": "3.1.4",
-            "resolved": "https://registry.npmjs.org/@react-types/actionbar/-/actionbar-3.1.4.tgz",
-            "integrity": "sha512-/z9N7ztd/MOdEDQNHTCNviYe0+rqy1s19Xg3tv/PV1oUCOsjrnja85VVxoa+AWR8IbwgDNIfpXg2Wa66b1FDbw==",
+            "version": "3.1.5",
+            "resolved": "https://registry.npmjs.org/@react-types/actionbar/-/actionbar-3.1.5.tgz",
+            "integrity": "sha512-Z3hfIoaOaW8wJxQm1NyWVvSftpNDYv9iWqpEWBEdhxuqsUkOVszZ7KcNaF4qsm4bJIcJWn3FNKhaTKGwISZcdQ==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/actiongroup": {
-            "version": "3.4.6",
-            "resolved": "https://registry.npmjs.org/@react-types/actiongroup/-/actiongroup-3.4.6.tgz",
-            "integrity": "sha512-Dho2mEDCU9ZAW+QX2HZkZhyxHK/EGfTvSWdHBFaCYsh4CPI/6PcvtirpSKMrzNNaZ97Exthv3GcLpAnLwM9jZw==",
+            "version": "3.4.7",
+            "resolved": "https://registry.npmjs.org/@react-types/actiongroup/-/actiongroup-3.4.7.tgz",
+            "integrity": "sha512-VsyHn6mGqEHKEIGFiHTq7rSuzuQjGVZGtnhh/9jQXW6zoSJyoM4fAnHEt+RE92NdiRv5e3+OzzrwG0TZsi87cQ==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/avatar": {
-            "version": "3.0.4",
-            "resolved": "https://registry.npmjs.org/@react-types/avatar/-/avatar-3.0.4.tgz",
-            "integrity": "sha512-fQ+qGce0EqcX0s2glnFjfvxSq42GHaqvl+eL8TnsDz0OIvB8KKzTO/rV/q1CIy/LtMP8fjCb6oqVFQcLfuODfw==",
+            "version": "3.0.5",
+            "resolved": "https://registry.npmjs.org/@react-types/avatar/-/avatar-3.0.5.tgz",
+            "integrity": "sha512-/hM/BnRoUhusT16G4hf2hfe4FocxaM5H/a7aetUrmlr9e5ppX3n0VsU0btIwxdh+aJL8u2BBt2scoiJaujQPNg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/badge": {
-            "version": "3.1.6",
-            "resolved": "https://registry.npmjs.org/@react-types/badge/-/badge-3.1.6.tgz",
-            "integrity": "sha512-6xjgfRnCVSBI6l/RQkI4u3tXiXw1aeFKRqXPcyIyt/kuu7rP0nKeYcM2XYyXXQp7vHfBdOEL2f6LwIHR/lx4uQ==",
+            "version": "3.1.7",
+            "resolved": "https://registry.npmjs.org/@react-types/badge/-/badge-3.1.7.tgz",
+            "integrity": "sha512-2Q5W9gleI83WpL1i6qPdcPC8l1V7364dpkNLcMi9DFmM2l69+QcPvSWVQMQLevmRo4cnaQ+TYsOgYv8I4rRJCg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/breadcrumbs": {
-            "version": "3.7.2",
-            "resolved": "https://registry.npmjs.org/@react-types/breadcrumbs/-/breadcrumbs-3.7.2.tgz",
-            "integrity": "sha512-esl6RucDW2CNMsApJxNYfMtDaUcfLlwKMPH/loYsOBbKxGl2HsgVLMcdpjEkTRs2HCTNCbBXWpeU8AY77t+bsw==",
+            "version": "3.7.3",
+            "resolved": "https://registry.npmjs.org/@react-types/breadcrumbs/-/breadcrumbs-3.7.3.tgz",
+            "integrity": "sha512-eFto/+6J+JR58vThNcALZRA1OlqlG3GzQ/bq3q8IrrkOZcrfbEJJCWit/+53Ia98siJKuF4OJHnotxIVIz5I3w==",
             "dependencies": {
-                "@react-types/link": "^3.5.2",
-                "@react-types/shared": "^3.22.0"
+                "@react-types/link": "^3.5.3",
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/button": {
-            "version": "3.9.1",
-            "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.9.1.tgz",
-            "integrity": "sha512-bf9iTar3PtqnyV9rA+wyFyrskZKhwmOuOd/ifYIjPs56YNVXWH5Wfqj6Dx3xdFBgtKx8mEVQxVhoX+WkHX+rtw==",
+            "version": "3.9.2",
+            "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.9.2.tgz",
+            "integrity": "sha512-EnPTkGHZRtiwAoJy5q9lDjoG30bEzA/qnvKG29VVXKYAGeqY2IlFs1ypmU+z1X/CpJgPcG3I5cakM7yTVm3pSg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/buttongroup": {
-            "version": "3.3.6",
-            "resolved": "https://registry.npmjs.org/@react-types/buttongroup/-/buttongroup-3.3.6.tgz",
-            "integrity": "sha512-aKFDzAWM6bk2+EBDSZe3zq4NMuguXsPyZ9OexN0YLleK4IkRKE2S51PdChY/GAFhfs7VsOKgjHrYWUnrmCCYvw==",
+            "version": "3.3.7",
+            "resolved": "https://registry.npmjs.org/@react-types/buttongroup/-/buttongroup-3.3.7.tgz",
+            "integrity": "sha512-EZ/1k66GtDtWfE/udNRz2UBbt4YJumeyW1SGcZEO3jketvpms58WapLsMkCezlLz7WYaa5ADV0kU8v88APjI6g==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/calendar": {
-            "version": "3.4.3",
-            "resolved": "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.4.3.tgz",
-            "integrity": "sha512-96x57ctX5wNEl+8et3sc2NQm8neOJayEeqOQQpyPtI7jyvst/xBrKCwysf9W/dhgPlUC+KeBAYFWfjd5hFVHYA==",
+            "version": "3.4.4",
+            "resolved": "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.4.4.tgz",
+            "integrity": "sha512-hV1Thmb/AES5OmfPvvmyjSkmsEULjiDfA7Yyy70L/YKuSNKb7Su+Bf2VnZuDW3ec+GxO4JJNlpJ0AkbphWBvcg==",
             "dependencies": {
-                "@internationalized/date": "^3.5.1",
-                "@react-types/shared": "^3.22.0"
+                "@internationalized/date": "^3.5.2",
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/checkbox": {
-            "version": "3.7.0",
-            "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.7.0.tgz",
-            "integrity": "sha512-3ZW/+Fh5GkL7mQhayyESB9+YQ6y7nImLQ8jB2lg42esaK5UF7IpG3xlrYm2z4KWLvQFXncX7SJsnwzYiBMLY+g==",
+            "version": "3.7.1",
+            "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.7.1.tgz",
+            "integrity": "sha512-kuGqjQFex0As/3gfWyk+e9njCcad/ZdnYLLiNvhlk15730xfa0MmnOdpqo9jfuFSXBjOcpxoofvEhvrRMtEdUA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/combobox": {
-            "version": "3.10.0",
-            "resolved": "https://registry.npmjs.org/@react-types/combobox/-/combobox-3.10.0.tgz",
-            "integrity": "sha512-1IXSNS02TPbguyYopaW2snU6sZusbClHrEyVr4zPeexTV4kpUUBNXOzFQ+eSQRR0r2XW57Z0yRW4GJ6FGU0yCA==",
+            "version": "3.10.1",
+            "resolved": "https://registry.npmjs.org/@react-types/combobox/-/combobox-3.10.1.tgz",
+            "integrity": "sha512-XMno1rgVRNta49vf5nV7VJpVSVAV20tt79t618gG1qRKH5Kt2Cy8lz2fQ5vHG6UTv/6jUOvU8g5Pc93sLaTmoA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/contextualhelp": {
-            "version": "3.2.7",
-            "resolved": "https://registry.npmjs.org/@react-types/contextualhelp/-/contextualhelp-3.2.7.tgz",
-            "integrity": "sha512-BHwBSBhPekKc/PxDpnkvfcEgpaYLMrV6WYgMfUz2/BMYOjdm+pb1y80vpNkWtrJKytyqp1zeZ+Ca+xzX1HdazA==",
+            "version": "3.2.8",
+            "resolved": "https://registry.npmjs.org/@react-types/contextualhelp/-/contextualhelp-3.2.8.tgz",
+            "integrity": "sha512-Yb0zOqkWfjKCO+gpfVj69CfWPhVq6RTgwLBV47ntIlHitpbh7RDYwDVulPyaVoopPbNSFUoV4YIJZPlOgpbKjg==",
             "dependencies": {
-                "@react-types/overlays": "^3.8.4",
-                "@react-types/shared": "^3.22.0"
+                "@react-types/overlays": "^3.8.5",
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/datepicker": {
-            "version": "3.7.1",
-            "resolved": "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.7.1.tgz",
-            "integrity": "sha512-5juVDULOytNzkotqX8j5mYKJckeIpkgbHqVSGkPgLw0++FceIaSZ6RH56cqLup0pO45paqIt9zHh+QXBYX+syg==",
+            "version": "3.7.2",
+            "resolved": "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.7.2.tgz",
+            "integrity": "sha512-zThqFAdhQL1dqyVDsDSSTdfCjoD6634eyg/B0ZJfQxcLUR/5pch3v/gxBhbyCVDGMNHRWUWIJvY9DVOepuoSug==",
             "dependencies": {
-                "@internationalized/date": "^3.5.1",
-                "@react-types/calendar": "^3.4.3",
-                "@react-types/overlays": "^3.8.4",
-                "@react-types/shared": "^3.22.0"
+                "@internationalized/date": "^3.5.2",
+                "@react-types/calendar": "^3.4.4",
+                "@react-types/overlays": "^3.8.5",
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/dialog": {
-            "version": "3.5.7",
-            "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.7.tgz",
-            "integrity": "sha512-geYoqAyQaTLG43AaXdMUVqZXYgkSifrD9cF7lR2kPAT0uGFv0YREi6ieU+aui8XJ83EW0xcxP+EPWd2YkN4D4w==",
+            "version": "3.5.8",
+            "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.8.tgz",
+            "integrity": "sha512-RX8JsMvty8ADHRqVEkppoynXLtN4IzUh8d5z88UEBbcvWKlHfd6bOBQjQcBH3AUue5wjfpPIt6brw2VzgBY/3Q==",
             "dependencies": {
-                "@react-types/overlays": "^3.8.4",
-                "@react-types/shared": "^3.22.0"
+                "@react-types/overlays": "^3.8.5",
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/divider": {
-            "version": "3.3.6",
-            "resolved": "https://registry.npmjs.org/@react-types/divider/-/divider-3.3.6.tgz",
-            "integrity": "sha512-Iwwe349IiCX7ZQK1Oz4AN5kWwiXG0DECSN4qB3h+14n97JKy3chWJC7UA+V6+2p5DbxmLVZm4XxDRgx7y0lVTg==",
+            "version": "3.3.7",
+            "resolved": "https://registry.npmjs.org/@react-types/divider/-/divider-3.3.7.tgz",
+            "integrity": "sha512-enAlzHVwzqBPpmE+/VAO5IGVPxPrscHkdbnEkTqW9JQeMUGXd4L0TdHOGd+vtcA+gILJMnhUfT3XE4wQ4HlpVw==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/form": {
-            "version": "3.7.1",
-            "resolved": "https://registry.npmjs.org/@react-types/form/-/form-3.7.1.tgz",
-            "integrity": "sha512-Wr44McYcB5Od3SwMzLhe1qaaZIy+YUe16jYRc/0io1gyCELXmplpIw8VQmf9/x62ze9CC/aZVdEJ/V9CogXmOA==",
+            "version": "3.7.2",
+            "resolved": "https://registry.npmjs.org/@react-types/form/-/form-3.7.2.tgz",
+            "integrity": "sha512-6/isEJY4PsYoHdMaGQtqQyquXGTwB1FqCBOPKQjI/vBGWG3fL7FGfWm4Z62eTbCH4Xyv3FZuNywlT8UjPMQyKA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/grid": {
-            "version": "3.2.3",
-            "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.2.3.tgz",
-            "integrity": "sha512-GQM4RDmYhstcYZ0Odjq+xUwh1fhLmRebG6qMM8OXHTPQ77nhl3wc1UTGRhZm6mzEionplSRx4GCpEMEHMJIU0w==",
+            "version": "3.2.4",
+            "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.2.4.tgz",
+            "integrity": "sha512-sDVoyQcH7MoGdx5nBi5ZOU/mVFBt9YTxhvr0PZ97dMdEHZtJC1w9SuezwWS34f50yb8YAXQRTICbZYcK4bAlDA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/illustratedmessage": {
-            "version": "3.3.6",
-            "resolved": "https://registry.npmjs.org/@react-types/illustratedmessage/-/illustratedmessage-3.3.6.tgz",
-            "integrity": "sha512-1FdJl1tR6mirmXT8yaTFeHNWdLXV6Dll66Mv1liEtTYsmCgn2anxwM73jK63t3jdT6ez/M1wGiwMlMtyiqo+ZQ==",
+            "version": "3.3.7",
+            "resolved": "https://registry.npmjs.org/@react-types/illustratedmessage/-/illustratedmessage-3.3.7.tgz",
+            "integrity": "sha512-r8WAv+w0WQSDRmDmG6jL0dXK8MjMr/lr2gBpVYVvC45Gu+fqf4VL+4zphgwASRysWclFbjQhgkgC7OEXk3GUKg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/image": {
-            "version": "3.3.6",
-            "resolved": "https://registry.npmjs.org/@react-types/image/-/image-3.3.6.tgz",
-            "integrity": "sha512-GSb0deyquS3kFt0e9SfPP9I/YaYYUToYYPzx9As0R0mzuVn6qTHhUtpKhBqQAOpE1Cd8XdEQeYsDB3sdOurI+A==",
+            "version": "3.3.7",
+            "resolved": "https://registry.npmjs.org/@react-types/image/-/image-3.3.7.tgz",
+            "integrity": "sha512-ty6wuan6p3Z8OzSrbjtL2k8yXHW+n0qiYKyqKpSA8TcRLDLhtvRceI7X12RuDfbAiEN6/zfLCuF47O3y7OeKug==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/label": {
-            "version": "3.9.0",
-            "resolved": "https://registry.npmjs.org/@react-types/label/-/label-3.9.0.tgz",
-            "integrity": "sha512-nTmPf5ED8aLGqvFsZHIHwMPrRX0cfbOyayva//Rdis41KWQoKUB80DIQjE+iUDOgTivIxGBkpqdIZVqRuehTnw==",
+            "version": "3.9.1",
+            "resolved": "https://registry.npmjs.org/@react-types/label/-/label-3.9.1.tgz",
+            "integrity": "sha512-0N/UOhwrKjE9VjVbUoJCsH5UwTzSu3d5B3xtvhH8YFqSbJCI+HMKILby8i8ys55RA9fQDMScIjCJafEFWW7UWA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/layout": {
-            "version": "3.3.12",
-            "resolved": "https://registry.npmjs.org/@react-types/layout/-/layout-3.3.12.tgz",
-            "integrity": "sha512-Ai6limgTVYQoGiXUvsXg8MHik+YAtRWEVLQhT5E1nQkDkNkQyccB+waUSfORhRkjJcnp+KMcbmPZ8V5ZO42rvQ==",
+            "version": "3.3.13",
+            "resolved": "https://registry.npmjs.org/@react-types/layout/-/layout-3.3.13.tgz",
+            "integrity": "sha512-Y3pmSsfDB+bV299F5SJannR6m/XM5IyfdqjPS0/Qx8KBBf0A3UWfL6qUr4BMEAKsEJuIXA7zT3RSo/AbGSbSyg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/link": {
-            "version": "3.5.2",
-            "resolved": "https://registry.npmjs.org/@react-types/link/-/link-3.5.2.tgz",
-            "integrity": "sha512-/s51/WejmpLiyxOgP89s4txgxYoGaPe8pVDItVo1h4+BhU1Puyvgv/Jx8t9dPvo6LUXbraaN+SgKk/QDxaiirw==",
+            "version": "3.5.3",
+            "resolved": "https://registry.npmjs.org/@react-types/link/-/link-3.5.3.tgz",
+            "integrity": "sha512-yVafjW3IejyVnK3oMBNjFABCGG6J27EUG8rvkaGaI1uB6srGUEhpJ97XLv11aj1QkXHBy3VGXqxEV3S7wn4HTw==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/listbox": {
-            "version": "3.4.6",
-            "resolved": "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.4.6.tgz",
-            "integrity": "sha512-XOQvrTqNh5WIPDvKiWiep8T07RAsMfjAXTjDbnjxVlKACUXkcwpts9kFaLnJ9LJRFt6DwItfP+WMkzvmx63/NQ==",
+            "version": "3.4.7",
+            "resolved": "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.4.7.tgz",
+            "integrity": "sha512-68y5H9CVSPFiwO6MOFxTbry9JQMK/Lb1M9i3M8TDyq1AbJxBPpgAvJ9RaqIMCucsnqCzpY/zA3D/X417zByL1w==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/menu": {
-            "version": "3.9.6",
-            "resolved": "https://registry.npmjs.org/@react-types/menu/-/menu-3.9.6.tgz",
-            "integrity": "sha512-w/RbFInOf4nNayQDv5c2L8IMJbcFOkBhsT3xvvpTy+CHvJcQdjggwaV1sRiw7eF/PwB81k2CwigmidUzHJhKDg==",
+            "version": "3.9.7",
+            "resolved": "https://registry.npmjs.org/@react-types/menu/-/menu-3.9.7.tgz",
+            "integrity": "sha512-K6KhloJVoGsqwkdeez72fkNI9dfrmLI/sNrB4XuOKo2crDQ/eyZYWyJmzz8giz/tHME9w774k487rVoefoFh5w==",
             "dependencies": {
-                "@react-types/overlays": "^3.8.4",
-                "@react-types/shared": "^3.22.0"
+                "@react-types/overlays": "^3.8.5",
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/meter": {
-            "version": "3.3.6",
-            "resolved": "https://registry.npmjs.org/@react-types/meter/-/meter-3.3.6.tgz",
-            "integrity": "sha512-1XYp1fA9UU0lO6kjf3TwVE8mppOJa64mBKAcLWtTyq1e/cYIAbx5o6CsuUx0YDpXKF6gdtvIWvfmxeWsmqJ1jQ==",
+            "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==",
             "dependencies": {
-                "@react-types/progress": "^3.5.1"
+                "@react-types/progress": "^3.5.2"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/numberfield": {
-            "version": "3.8.0",
-            "resolved": "https://registry.npmjs.org/@react-types/numberfield/-/numberfield-3.8.0.tgz",
-            "integrity": "sha512-2xnVvOVVSnvzT5JxldJdVRh/IMry9//PyQ5VcK3ze0m+bcD6zZspIdspzu8jiYyUPmZLHN1cZzx5GZSak1V6ig==",
+            "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==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/overlays": {
-            "version": "3.8.4",
-            "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.4.tgz",
-            "integrity": "sha512-pfgNlQnbF6RB/R2oSxyqAP3Uzz0xE/k5q4n5gUeCDNLjY5qxFHGE8xniZZ503nZYw6VBa9XMN1efDOKQyeiO0w==",
+            "version": "3.8.5",
+            "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.5.tgz",
+            "integrity": "sha512-4D7EEBQigD/m8hE68Ys8eloyyZFHHduqykSIgINJ0edmo0jygRbWlTwuhWFR9USgSP4dK54duN0Mvq0m4HEVEw==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/progress": {
-            "version": "3.5.1",
-            "resolved": "https://registry.npmjs.org/@react-types/progress/-/progress-3.5.1.tgz",
-            "integrity": "sha512-CqsUjczUK/SfuFzDcajBBaXRTW0D3G9S/yqLDj9e8E0ii+lGDLt1PHj24t1J7E88U2rVYqmM9VL4NHTt8o3IYA==",
+            "version": "3.5.2",
+            "resolved": "https://registry.npmjs.org/@react-types/progress/-/progress-3.5.2.tgz",
+            "integrity": "sha512-aQql22kusEudsHwDEzq6y/Mh29AM+ftRDKdS5E5g4MkCY5J4FMbOYco1T5So83NIvvG9+eKcxPoJUMjQQACAyA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/provider": {
-            "version": "3.7.1",
-            "resolved": "https://registry.npmjs.org/@react-types/provider/-/provider-3.7.1.tgz",
-            "integrity": "sha512-WKwHwG5b0LkI570tbHCy4hBhT/E+OrdgIybScDxM713B2OwmMKKyaPKdV05SeoomP8oiPvkaAeXhLZa1ah7CYg==",
+            "version": "3.7.2",
+            "resolved": "https://registry.npmjs.org/@react-types/provider/-/provider-3.7.2.tgz",
+            "integrity": "sha512-nzxbfuh/ZJXt/blGAiRPkxi9jAUnnBkHcvljqdfizfLJlN5epaYYaTWNUSde27Oe1tAnpm2WDlLQo5+0C6C0FA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/radio": {
-            "version": "3.7.0",
-            "resolved": "https://registry.npmjs.org/@react-types/radio/-/radio-3.7.0.tgz",
-            "integrity": "sha512-EcwGAXzSHjSqpFZha7xn3IUrhPiJLj+0yb1Ip0qPmhWz0VVw2DwrkY7q/jfaKroVvQhTo2TbfGhcsAQrt0fRqg==",
+            "version": "3.7.1",
+            "resolved": "https://registry.npmjs.org/@react-types/radio/-/radio-3.7.1.tgz",
+            "integrity": "sha512-Zut3rN1odIUBLZdijeyou+UqsLeRE76d9A+npykYGu29ndqmo3w4sLn8QeQcdj1IR71ZnG0pW2Y2BazhK5XrrQ==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/searchfield": {
-            "version": "3.5.2",
-            "resolved": "https://registry.npmjs.org/@react-types/searchfield/-/searchfield-3.5.2.tgz",
-            "integrity": "sha512-JAK2/Kg4Dr393FYfbRw0TlXKnJPX77sq1x/ZBxtO6p64+MuuIYKqw0i9PwDlo1PViw2QI5u8GFhKA2TgemY9uA==",
+            "version": "3.5.3",
+            "resolved": "https://registry.npmjs.org/@react-types/searchfield/-/searchfield-3.5.3.tgz",
+            "integrity": "sha512-gBfsT1WpY8UIb74yyYmnjiHpVasph2mdmGj9i8cGF2HUYwx5p+Fr85mtCGDph0uirvRoM5ExMp4snD+ueNAVCg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0",
-                "@react-types/textfield": "^3.9.0"
+                "@react-types/shared": "^3.22.1",
+                "@react-types/textfield": "^3.9.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/select": {
-            "version": "3.9.1",
-            "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.1.tgz",
-            "integrity": "sha512-EpKSxrnh8HdZvOF9dHQkjivAcdIp1K81FaxmvosH8Lygqh0iYXxAdZGtKLMyBoPI8YFhA+rotIzTcOqgCCnqWA==",
+            "version": "3.9.2",
+            "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.2.tgz",
+            "integrity": "sha512-fGFrunednY3Pq/BBwVOf87Fsuyo/SlevL0wFIE9OOl2V5NXVaTY7/7RYA8hIOHPzmvsMbndy419BEudiNGhv4A==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/shared": {
-            "version": "3.22.0",
-            "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.22.0.tgz",
-            "integrity": "sha512-yVOekZWbtSmmiThGEIARbBpnmUIuePFlLyctjvCbgJgGhz8JnEJOipLQ/a4anaWfzAgzSceQP8j/K+VOOePleA==",
+            "version": "3.22.1",
+            "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.22.1.tgz",
+            "integrity": "sha512-PCpa+Vo6BKnRMuOEzy5zAZ3/H5tnQg1e80khMhK2xys0j6ZqzkgQC+fHMNZ7VDFNLqqNMj/o0eVeSBDh2POjkw==",
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/slider": {
-            "version": "3.7.0",
-            "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.7.0.tgz",
-            "integrity": "sha512-uyQXUVFfqc9SPUW0LZLMan2n232F/OflRafiHXz9viLFa9tVOupVa7GhASRAoHojwkjoJ1LjFlPih7g5dOZ0/Q==",
+            "version": "3.7.1",
+            "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.7.1.tgz",
+            "integrity": "sha512-FKO3YZYdrBs00XbBW5acP+0L1cCdevl/uRJiXbnLpGysO5PrSFIRS7Wlv4M7ztf6gT7b1Ao4FNC9crbxBr6BzA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/statuslight": {
-            "version": "3.3.6",
-            "resolved": "https://registry.npmjs.org/@react-types/statuslight/-/statuslight-3.3.6.tgz",
-            "integrity": "sha512-MB/CnsbaE6reOrnpowJfgkpeSNY0ZuqA6g/k8331a+TP2yIO6X0cUYyEGG8S/k9hFyFCMKlcmmm4pwMrX4sZtQ==",
+            "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==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/switch": {
-            "version": "3.5.0",
-            "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.0.tgz",
-            "integrity": "sha512-/wNmUGjk69bP6t5k2QkAdrNN5Eb9Rz4dOyp0pCPmoeE+5haW6sV5NmtkvWX1NSc4DQz1xL/a5b+A0vxPCP22Jw==",
+            "version": "3.5.1",
+            "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.1.tgz",
+            "integrity": "sha512-2LFEKMGeufqyYmeN/5dtkDkCPG6x9O4eu6aaBaJmPGon7C/l3yiFEgRue6oCUYc1HixR7Qlp0sPxk0tQeWzrSg==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/table": {
-            "version": "3.9.2",
-            "resolved": "https://registry.npmjs.org/@react-types/table/-/table-3.9.2.tgz",
-            "integrity": "sha512-brw5JUANOzBa2rYNpN8AIl9nDZ9RwRZC6G/wTM/JhtirjC1S42oCtf8Ap5rWJBdmMG/5KOfcGNcAl/huyqb3gg==",
+            "version": "3.9.3",
+            "resolved": "https://registry.npmjs.org/@react-types/table/-/table-3.9.3.tgz",
+            "integrity": "sha512-Hs/pMbxJdga2zBol4H5pV1FVIiRjCuSTXst6idJjkctanTexR4xkyrtBwl+rdLNoGwQ2pGii49vgklc5bFK7zA==",
             "dependencies": {
-                "@react-types/grid": "^3.2.3",
-                "@react-types/shared": "^3.22.0"
+                "@react-types/grid": "^3.2.4",
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/tabs": {
-            "version": "3.3.4",
-            "resolved": "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.4.tgz",
-            "integrity": "sha512-4mCTtFrwMRypyGTZCvNYVT9CkknexO/UYvqwDm2jMYb8JgjRvxnomu776Yh7uyiYKWyql2upm20jqasEOm620w==",
+            "version": "3.3.5",
+            "resolved": "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.5.tgz",
+            "integrity": "sha512-6NTSZBOWekCtApdZrhu5tHhE/8q52oVohQN+J5T7shAXd6ZAtu8PABVR/nH4BWucc8FL0OUajRqunqzQMU13gA==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/text": {
-            "version": "3.3.6",
-            "resolved": "https://registry.npmjs.org/@react-types/text/-/text-3.3.6.tgz",
-            "integrity": "sha512-cO3IQ/DQ/xUGGskJ8/zCLkbzvrjlQbRnrJl95BEGs97CmiN+zqGoCqvDhjWEbuPRtfGXJ27CYZDC2oVZetUG4w==",
+            "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==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/textfield": {
-            "version": "3.9.0",
-            "resolved": "https://registry.npmjs.org/@react-types/textfield/-/textfield-3.9.0.tgz",
-            "integrity": "sha512-D/DiwzsfkwlAg3uv8hoIfwju+zhB/hWDEdTvxQbPkntDr0kmN/QfI17NMSzbOBCInC4ABX87ViXLGxr940ykGA==",
+            "version": "3.9.1",
+            "resolved": "https://registry.npmjs.org/@react-types/textfield/-/textfield-3.9.1.tgz",
+            "integrity": "sha512-JBHY9M2CkL6xFaGSfWmUJVu3tEK09FaeB1dU3IEh6P41xxbFnPakYHSSAdnwMXBtXPoSHIVsUBickW/pjgfe5g==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/tooltip": {
-            "version": "3.4.6",
-            "resolved": "https://registry.npmjs.org/@react-types/tooltip/-/tooltip-3.4.6.tgz",
-            "integrity": "sha512-RaZewdER7ZcsNL99RhVHs8kSLyzIBkwc0W6eFZrxST2MD9J5GzkVWRhIiqtFOd5U1aYnxdJ6woq72Ef+le6Vfw==",
+            "version": "3.4.7",
+            "resolved": "https://registry.npmjs.org/@react-types/tooltip/-/tooltip-3.4.7.tgz",
+            "integrity": "sha512-rV4HZRQxLRNhe24yATOxnFQtGRUmsR7mqxMupXCmd1vrw8h+rdKlQv1zW2q8nALAKNmnRXZJHxYQ1SFzb98fgg==",
             "dependencies": {
-                "@react-types/overlays": "^3.8.4",
-                "@react-types/shared": "^3.22.0"
+                "@react-types/overlays": "^3.8.5",
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/view": {
-            "version": "3.4.6",
-            "resolved": "https://registry.npmjs.org/@react-types/view/-/view-3.4.6.tgz",
-            "integrity": "sha512-GAdvvabJAYrVCgOUsZp8KkmNLfkKnDmoMNmwCN9I2OnSS+5JyjTrgNIOiznMjDEqhXTbaefcsVofoUfTYXjtyQ==",
+            "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==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
             }
         },
         "node_modules/@react-types/well": {
-            "version": "3.3.6",
-            "resolved": "https://registry.npmjs.org/@react-types/well/-/well-3.3.6.tgz",
-            "integrity": "sha512-NX4+bMmNYrbjllKR9Xxg0YHNWrscHzZQmcdYiM/Z8qZ1TNVPhXeLmKxCDamlmUSZudCqwui4q5xwzuUyrRRA6w==",
+            "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==",
             "dependencies": {
-                "@react-types/shared": "^3.22.0"
+                "@react-types/shared": "^3.22.1"
             },
             "peerDependencies": {
                 "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
@@ -6793,12 +6781,12 @@
             "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
         },
         "node_modules/@spectrum-icons/ui": {
-            "version": "3.6.4",
-            "resolved": "https://registry.npmjs.org/@spectrum-icons/ui/-/ui-3.6.4.tgz",
-            "integrity": "sha512-ojjICGgVaBeMKbeMB9/KCAs7l2ToJlNrJUVljkLv2Os6fukGHsAgVgO6wyw9diUIZGgentpx441+CQLooi9J6w==",
+            "version": "3.6.5",
+            "resolved": "https://registry.npmjs.org/@spectrum-icons/ui/-/ui-3.6.5.tgz",
+            "integrity": "sha512-L1QISmlzoA4xuBdMpWT2vkR7NDxuROjSwC5BxS8R2SAZR4oKs0dPfODaw2uju6D/xqJIqVrSM6yQDhV51lPKyA==",
             "dependencies": {
                 "@adobe/react-spectrum-ui": "1.2.0",
-                "@react-spectrum/icon": "^3.7.10",
+                "@react-spectrum/icon": "^3.7.11",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -6807,12 +6795,12 @@
             }
         },
         "node_modules/@spectrum-icons/workflow": {
-            "version": "4.2.9",
-            "resolved": "https://registry.npmjs.org/@spectrum-icons/workflow/-/workflow-4.2.9.tgz",
-            "integrity": "sha512-JdDzhnI7ASToodB1V5iy/CZNNHXxE7fwaPCS4+BpdwoibowgK5c1tnu1nHhzMHilw94UosYQjPbf3zzjSSYbjw==",
+            "version": "4.2.10",
+            "resolved": "https://registry.npmjs.org/@spectrum-icons/workflow/-/workflow-4.2.10.tgz",
+            "integrity": "sha512-CaDYaGMe2bYDozzHztrgEy0tljQWn4OBE9hJC2fAM5cMTve4viiWhcmHYYWFv3MZQyavHy1UwCL1jlXg7bK3PA==",
             "dependencies": {
                 "@adobe/react-spectrum-workflow": "2.3.4",
-                "@react-spectrum/icon": "^3.7.10",
+                "@react-spectrum/icon": "^3.7.11",
                 "@swc/helpers": "^0.5.0"
             },
             "peerDependencies": {
@@ -9112,9 +9100,9 @@
             "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
         },
         "node_modules/@types/node": {
-            "version": "20.11.18",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.18.tgz",
-            "integrity": "sha512-ABT5VWnnYneSBcNWYSCuR05M826RoMyMSGiFivXGx6ZUIsXb9vn4643IEwkg2zbEOSgAiSogtapN2fgc4mAPlw==",
+            "version": "20.11.20",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz",
+            "integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==",
             "dependencies": {
                 "undici-types": "~5.26.4"
             }
@@ -9249,9 +9237,9 @@
             }
         },
         "node_modules/@types/react": {
-            "version": "18.2.55",
-            "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.55.tgz",
-            "integrity": "sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==",
+            "version": "18.2.57",
+            "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.57.tgz",
+            "integrity": "sha512-ZvQsktJgSYrQiMirAN60y4O/LRevIV8hUzSOSNB6gfR3/o3wCBFQx3sPwIYtuDMeiVgsSS3UzCV26tEzgnfvQw==",
             "dependencies": {
                 "@types/prop-types": "*",
                 "@types/scheduler": "*",
@@ -10338,6 +10326,14 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/arrify": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+            "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/asap": {
             "version": "2.0.6",
             "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
@@ -10427,9 +10423,12 @@
             "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
         },
         "node_modules/available-typed-arrays": {
-            "version": "1.0.6",
-            "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz",
-            "integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==",
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+            "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+            "dependencies": {
+                "possible-typed-array-names": "^1.0.0"
+            },
             "engines": {
                 "node": ">= 0.4"
             },
@@ -10825,9 +10824,9 @@
             "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
         },
         "node_modules/bootstrap": {
-            "version": "5.3.2",
-            "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz",
-            "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==",
+            "version": "5.3.3",
+            "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
+            "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
             "funding": [
                 {
                     "type": "github",
@@ -14030,9 +14029,9 @@
             }
         },
         "node_modules/caniuse-lite": {
-            "version": "1.0.30001587",
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz",
-            "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==",
+            "version": "1.0.30001589",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz",
+            "integrity": "sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==",
             "funding": [
                 {
                     "type": "opencollective",
@@ -15582,6 +15581,18 @@
                 }
             }
         },
+        "node_modules/decamelize": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+            "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/decimal.js": {
             "version": "10.4.3",
             "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
@@ -16054,9 +16065,9 @@
             }
         },
         "node_modules/dotenv": {
-            "version": "16.4.4",
-            "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz",
-            "integrity": "sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==",
+            "version": "16.4.5",
+            "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
+            "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
             "dev": true,
             "engines": {
                 "node": ">=12"
@@ -16112,9 +16123,9 @@
             "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
         },
         "node_modules/electron-to-chromium": {
-            "version": "1.4.670",
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.670.tgz",
-            "integrity": "sha512-hcijYOWjOtjKrKPtNA6tuLlA/bTLO3heFG8pQA6mLpq7dRydSWicXova5lyxDzp1iVJaYhK7J2OQlGE52KYn7A=="
+            "version": "1.4.680",
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.680.tgz",
+            "integrity": "sha512-4nToZ5jlPO14W82NkF32wyjhYqQByVaDmLy4J2/tYcAbJfgO2TKJC780Az1V13gzq4l73CJ0yuyalpXvxXXD9A=="
         },
         "node_modules/emoji-regex": {
             "version": "9.2.2",
@@ -16371,14 +16382,14 @@
             "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
         },
         "node_modules/es-set-tostringtag": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
-            "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==",
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
+            "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
             "dev": true,
             "dependencies": {
-                "get-intrinsic": "^1.2.2",
-                "has-tostringtag": "^1.0.0",
-                "hasown": "^2.0.0"
+                "get-intrinsic": "^1.2.4",
+                "has-tostringtag": "^1.0.2",
+                "hasown": "^2.0.1"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -17959,18 +17970,6 @@
                 "node": ">=8"
             }
         },
-        "node_modules/eslint/node_modules/type-fest": {
-            "version": "0.20.2",
-            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
-            "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
-            "dev": true,
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
         "node_modules/espree": {
             "version": "9.6.1",
             "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
@@ -18069,6 +18068,11 @@
                 "node": ">=6"
             }
         },
+        "node_modules/eventemitter3": {
+            "version": "4.0.7",
+            "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+            "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
+        },
         "node_modules/events": {
             "version": "3.3.0",
             "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -18366,7 +18370,8 @@
         "node_modules/fast-diff": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
-            "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="
+            "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+            "dev": true
         },
         "node_modules/fast-equals": {
             "version": "4.0.3",
@@ -18733,9 +18738,9 @@
             }
         },
         "node_modules/flatted": {
-            "version": "3.2.9",
-            "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz",
-            "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
+            "version": "3.3.1",
+            "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+            "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
             "dev": true
         },
         "node_modules/flexlayout-react": {
@@ -19257,9 +19262,9 @@
             }
         },
         "node_modules/gaxios": {
-            "version": "6.2.0",
-            "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.2.0.tgz",
-            "integrity": "sha512-H6+bHeoEAU5D6XNc6mPKeN5dLZqEDs9Gpk6I+SZBEzK5So58JVrHPmevNi35fRl1J9Y5TaeLW0kYx3pCJ1U2mQ==",
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.3.0.tgz",
+            "integrity": "sha512-p+ggrQw3fBwH2F5N/PAI4k/G/y1art5OxKpb2J2chwNNHM4hHuAOtivjPuirMF4KNKwTTUal/lPfL2+7h2mEcg==",
             "dependencies": {
                 "extend": "^3.0.2",
                 "https-proxy-agent": "^7.0.1",
@@ -19282,9 +19287,9 @@
             }
         },
         "node_modules/gaxios/node_modules/https-proxy-agent": {
-            "version": "7.0.3",
-            "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.3.tgz",
-            "integrity": "sha512-kCnwztfX0KZJSLOBrcL0emLeFako55NWMovvyPP2AjsghNk9RB1yjSI+jVumPHYZsNXegNoqupSW9IY3afSH8w==",
+            "version": "7.0.4",
+            "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz",
+            "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==",
             "dependencies": {
                 "agent-base": "^7.0.2",
                 "debug": "4"
@@ -19794,9 +19799,9 @@
             }
         },
         "node_modules/has-proto": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
-            "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+            "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
             "engines": {
                 "node": ">= 0.4"
             },
@@ -20264,9 +20269,9 @@
             }
         },
         "node_modules/http-proxy-agent": {
-            "version": "7.0.1",
-            "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.1.tgz",
-            "integrity": "sha512-My1KCEPs6A0hb4qCVzYp8iEvA8j8YqcvXLZZH8C9OFuTYpYjHE7N2dtG3mRl1HMD4+VGXpF3XcDVcxGBT7yDZQ==",
+            "version": "7.0.2",
+            "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+            "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
             "dev": true,
             "dependencies": {
                 "agent-base": "^7.1.0",
@@ -20324,12 +20329,6 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
-        "node_modules/http-proxy/node_modules/eventemitter3": {
-            "version": "4.0.7",
-            "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
-            "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
-            "dev": true
-        },
         "node_modules/http-signature": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@@ -20404,14 +20403,6 @@
                 "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",
@@ -21368,9 +21359,9 @@
             }
         },
         "node_modules/is-negative-zero": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
-            "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+            "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
             "dev": true,
             "engines": {
                 "node": ">= 0.4"
@@ -21411,6 +21402,17 @@
                 "node": ">=8"
             }
         },
+        "node_modules/is-plain-obj": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+            "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+            "engines": {
+                "node": ">=12"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/is-plain-object": {
             "version": "2.0.4",
             "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@@ -21458,12 +21460,15 @@
             }
         },
         "node_modules/is-shared-array-buffer": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
-            "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
+            "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2"
+                "call-bind": "^1.0.7"
+            },
+            "engines": {
+                "node": ">= 0.4"
             },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
@@ -21869,9 +21874,9 @@
             }
         },
         "node_modules/jsdom/node_modules/https-proxy-agent": {
-            "version": "7.0.3",
-            "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.3.tgz",
-            "integrity": "sha512-kCnwztfX0KZJSLOBrcL0emLeFako55NWMovvyPP2AjsghNk9RB1yjSI+jVumPHYZsNXegNoqupSW9IY3afSH8w==",
+            "version": "7.0.4",
+            "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz",
+            "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==",
             "dev": true,
             "dependencies": {
                 "agent-base": "^7.0.2",
@@ -22356,12 +22361,6 @@
             "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
             "integrity": "sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w=="
         },
-        "node_modules/lodash.clonedeep": {
-            "version": "4.5.0",
-            "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
-            "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
-            "peer": true
-        },
         "node_modules/lodash.debounce": {
             "version": "4.0.8",
             "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -22372,12 +22371,6 @@
             "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
             "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
         },
-        "node_modules/lodash.isequal": {
-            "version": "4.5.0",
-            "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
-            "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
-            "peer": true
-        },
         "node_modules/lodash.merge": {
             "version": "4.6.2",
             "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -24011,9 +24004,9 @@
             }
         },
         "node_modules/mongoose": {
-            "version": "8.1.2",
-            "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.1.2.tgz",
-            "integrity": "sha512-5KMq7k6KmFCIB8/YMKMFsWdsdNkBwuARDRHDRpp5GKC78eT0LwHIaMEKo6gDUg3zBuMoy9OdcM/6f4dkW06C/A==",
+            "version": "8.2.0",
+            "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.2.0.tgz",
+            "integrity": "sha512-la93n6zCYRbPS+c5N9oTDAktvREy5OT9OCljp1Tah0y3+p8UPMTAoabWaLZMdzYruOtF9/9GRf6MasaZjiZP1A==",
             "dependencies": {
                 "bson": "^6.2.0",
                 "kareem": "2.5.1",
@@ -24101,14 +24094,6 @@
             "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
             "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="
         },
-        "node_modules/multimatch/node_modules/arrify": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
-            "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
-            "engines": {
-                "node": ">=8"
-            }
-        },
         "node_modules/multimatch/node_modules/brace-expansion": {
             "version": "1.1.11",
             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -24321,17 +24306,17 @@
             }
         },
         "node_modules/nodemailer": {
-            "version": "6.9.9",
-            "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.9.tgz",
-            "integrity": "sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA==",
+            "version": "6.9.10",
+            "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.10.tgz",
+            "integrity": "sha512-qtoKfGFhvIFW5kLfrkw2R6Nm6Ur4LNUMykyqu6n9BRKJuyQrqEGwdXXUAbwWEKt33dlWUGXb7rzmJP/p4+O+CA==",
             "engines": {
                 "node": ">=6.0.0"
             }
         },
         "node_modules/nodemon": {
-            "version": "3.0.3",
-            "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.3.tgz",
-            "integrity": "sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ==",
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
+            "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==",
             "dependencies": {
                 "chokidar": "^3.5.2",
                 "debug": "^4",
@@ -27304,9 +27289,9 @@
             }
         },
         "node_modules/openai/node_modules/@types/node": {
-            "version": "18.19.16",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.16.tgz",
-            "integrity": "sha512-mjtrR7Wco9ZwcGBc1zre6fENlj9z42/+0W26lBGtGBTPiR3Zm9iZAaiPhxreG6magwGCILLVYwlQ48GjAaqM6w==",
+            "version": "18.19.18",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.18.tgz",
+            "integrity": "sha512-80CP7B8y4PzZF0GWx15/gVWRrB5y/bIjNI84NK3cmQJu0WZwvmj2WMA5LcofQFVfLqqCSp545+U2LsrVzX36Zg==",
             "dependencies": {
                 "undici-types": "~5.26.4"
             }
@@ -27483,9 +27468,9 @@
             "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="
         },
         "node_modules/parse-bmfont-xml": {
-            "version": "1.1.5",
-            "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.5.tgz",
-            "integrity": "sha512-wgM+ANC5G5Yu08/IEFMxr9x+PpHg+R8jf8U8q0P91TBDaTdjcf4IwupUiPwEcJJKNqSshC2tOkDQW3Q30s1efQ==",
+            "version": "1.1.6",
+            "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz",
+            "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==",
             "dependencies": {
                 "xml-parse-from-string": "^1.0.0",
                 "xml2js": "^0.5.0"
@@ -27845,7 +27830,8 @@
         "node_modules/phin": {
             "version": "2.9.3",
             "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz",
-            "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA=="
+            "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==",
+            "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."
         },
         "node_modules/picocolors": {
             "version": "1.0.0",
@@ -28006,6 +27992,14 @@
             "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
             "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
         },
+        "node_modules/possible-typed-array-names": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
+            "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
         "node_modules/postcss": {
             "version": "8.4.35",
             "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
@@ -28319,9 +28313,9 @@
             }
         },
         "node_modules/prosemirror-view": {
-            "version": "1.32.7",
-            "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.32.7.tgz",
-            "integrity": "sha512-pvxiOoD4shW41X5bYDjRQk3DSG4fMqxh36yPMt7VYgU3dWRmqFzWJM/R6zeo1KtC8nyk717ZbQND3CC9VNeptw==",
+            "version": "1.33.1",
+            "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.1.tgz",
+            "integrity": "sha512-62qkYgSJIkwIMMCpuGuPzc52DiK1Iod6TWoIMxP4ja6BTD4yO8kCUL64PZ/WhH/dJ9fW0CDO39FhH1EMyhUFEg==",
             "dependencies": {
                 "prosemirror-model": "^1.16.0",
                 "prosemirror-state": "^1.0.0",
@@ -28582,20 +28576,6 @@
             "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
             "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="
         },
-        "node_modules/quill-delta": {
-            "version": "5.1.0",
-            "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
-            "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
-            "peer": true,
-            "dependencies": {
-                "fast-diff": "^1.3.0",
-                "lodash.clonedeep": "^4.5.0",
-                "lodash.isequal": "^4.5.0"
-            },
-            "engines": {
-                "node": ">= 12.0.0"
-            }
-        },
         "node_modules/random-bytes": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
@@ -28698,9 +28678,9 @@
             }
         },
         "node_modules/rc-util": {
-            "version": "5.38.1",
-            "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.38.1.tgz",
-            "integrity": "sha512-e4ZMs7q9XqwTuhIK7zBIVFltUtMSjphuPPQXHoHlzRzNdOwUxDejo0Zls5HYaJfRKNURcsS/ceKVULlhjBrxng==",
+            "version": "5.38.2",
+            "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.38.2.tgz",
+            "integrity": "sha512-yRGRPKyi84H7NkRSP6FzEIYBdUt4ufdsmXUZ7qM2H5qoByPax70NnGPkfo36N+UKUnUBj2f2Q2eUbwYMuAsIOQ==",
             "dependencies": {
                 "@babel/runtime": "^7.18.3",
                 "react-is": "^18.2.0"
@@ -28892,9 +28872,9 @@
             }
         },
         "node_modules/react-intersection-observer": {
-            "version": "9.8.0",
-            "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.8.0.tgz",
-            "integrity": "sha512-wXHvMQUsTagh3X0Z6jDtGkIXc3VVCd2tjDRYR9kII3GKrZr0XF0xtpfdamo2n8BSF+zzfeeBVOTjxZWpBp9X0g==",
+            "version": "9.8.1",
+            "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.8.1.tgz",
+            "integrity": "sha512-QzOFdROX8D8MH3wE3OVKH0f3mLjKTtEN1VX/rkNuECCff+aKky0pIjulDhr3Ewqj5el/L+MhBkM3ef0Tbt+qUQ==",
             "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"
@@ -29236,13 +29216,13 @@
             "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
         },
         "node_modules/recharts": {
-            "version": "2.12.0",
-            "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.0.tgz",
-            "integrity": "sha512-rVNcdNQ5b7+40Ue7mcEKZJyEv+3SUk2bDEVvOyXPDXXVE7TU3lrvnJUgAvO36hSzhRP2DnAamKXvHLFIFOU0Ww==",
+            "version": "2.12.1",
+            "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.1.tgz",
+            "integrity": "sha512-35vUCEBPf+pM+iVgSgVTn86faKya5pc4JO6cYJL63qOK2zDEyzDn20Tdj+CDI/3z+VcpKyQ8ZBQ9OiQ+vuAbjg==",
             "dependencies": {
                 "clsx": "^2.0.0",
                 "eventemitter3": "^4.0.1",
-                "lodash": "^4.17.19",
+                "lodash": "^4.17.21",
                 "react-is": "^16.10.2",
                 "react-smooth": "^4.0.0",
                 "recharts-scale": "^0.4.4",
@@ -29265,11 +29245,6 @@
                 "decimal.js-light": "^2.4.1"
             }
         },
-        "node_modules/recharts/node_modules/eventemitter3": {
-            "version": "4.0.7",
-            "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
-            "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
-        },
         "node_modules/recharts/node_modules/react-is": {
             "version": "16.13.1",
             "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -30015,9 +29990,9 @@
             "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
         },
         "node_modules/sass": {
-            "version": "1.70.0",
-            "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz",
-            "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==",
+            "version": "1.71.1",
+            "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
+            "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==",
             "dependencies": {
                 "chokidar": ">=3.0.0 <4.0.0",
                 "immutable": "^4.0.0",
@@ -30368,13 +30343,14 @@
             }
         },
         "node_modules/set-function-name": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz",
-            "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==",
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+            "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
             "dependencies": {
-                "define-data-property": "^1.0.1",
+                "define-data-property": "^1.1.4",
+                "es-errors": "^1.3.0",
                 "functions-have-names": "^1.2.3",
-                "has-property-descriptors": "^1.0.0"
+                "has-property-descriptors": "^1.0.2"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -30686,10 +30662,11 @@
             }
         },
         "node_modules/socket.io-adapter": {
-            "version": "2.5.2",
-            "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
-            "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
+            "version": "2.5.4",
+            "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz",
+            "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==",
             "dependencies": {
+                "debug": "~4.3.4",
                 "ws": "~8.11.0"
             }
         },
@@ -31012,9 +30989,9 @@
             "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
         },
         "node_modules/streamx": {
-            "version": "2.15.8",
-            "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.8.tgz",
-            "integrity": "sha512-6pwMeMY/SuISiRsuS8TeIrAzyFbG5gGPHFQsYjUr/pbBadaL1PCWmzKw+CHZSwainfvcF6Si6cVLq4XTEwswFQ==",
+            "version": "2.16.1",
+            "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz",
+            "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==",
             "dependencies": {
                 "fast-fifo": "^1.1.0",
                 "queue-tick": "^1.0.1"
@@ -31550,9 +31527,9 @@
             "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
         },
         "node_modules/terser": {
-            "version": "5.27.1",
-            "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.1.tgz",
-            "integrity": "sha512-29wAr6UU/oQpnTw5HoadwjUZnFQXGdOfj0LjZ4sVxzqwHh/QVkvr7m8y9WoR4iN3FRitVduTc6KdjcW38Npsug==",
+            "version": "5.27.2",
+            "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.2.tgz",
+            "integrity": "sha512-sHXmLSkImesJ4p5apTeT63DsV4Obe1s37qT8qvwHRmVxKTBH7Rv9Wr26VcAMmLbmk9UliiwK8z+657NyJHHy/w==",
             "dependencies": {
                 "@jridgewell/source-map": "^0.3.3",
                 "acorn": "^8.8.2",
@@ -31632,25 +31609,13 @@
         "node_modules/text-table": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
-            "dev": true
+            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
         },
         "node_modules/textarea-caret": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz",
             "integrity": "sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q=="
         },
-        "node_modules/thingies": {
-            "version": "1.16.0",
-            "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.16.0.tgz",
-            "integrity": "sha512-J23AVs11hSQxuJxvfQyMIaS9z1QpDxOCvMkL3ZxZl8/jmkgmnNGWrlyNxVz6Jbh0U6DuGmHqq6f7zUROfg/ncg==",
-            "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",
@@ -32479,6 +32444,18 @@
                 "node": ">= 0.8.0"
             }
         },
+        "node_modules/type-fest": {
+            "version": "0.20.2",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+            "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/type-is": {
             "version": "1.6.18",
             "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -32492,12 +32469,12 @@
             }
         },
         "node_modules/typed-array-buffer": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz",
-            "integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==",
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz",
+            "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.6",
+                "call-bind": "^1.0.7",
                 "es-errors": "^1.3.0",
                 "is-typed-array": "^1.1.13"
             },
@@ -32506,15 +32483,16 @@
             }
         },
         "node_modules/typed-array-byte-length": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
-            "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz",
+            "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2",
+                "call-bind": "^1.0.7",
                 "for-each": "^0.3.3",
-                "has-proto": "^1.0.1",
-                "is-typed-array": "^1.1.10"
+                "gopd": "^1.0.1",
+                "has-proto": "^1.0.3",
+                "is-typed-array": "^1.1.13"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -32524,16 +32502,17 @@
             }
         },
         "node_modules/typed-array-byte-offset": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz",
-            "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==",
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz",
+            "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==",
             "dev": true,
             "dependencies": {
-                "available-typed-arrays": "^1.0.5",
-                "call-bind": "^1.0.2",
+                "available-typed-arrays": "^1.0.7",
+                "call-bind": "^1.0.7",
                 "for-each": "^0.3.3",
-                "has-proto": "^1.0.1",
-                "is-typed-array": "^1.1.10"
+                "gopd": "^1.0.1",
+                "has-proto": "^1.0.3",
+                "is-typed-array": "^1.1.13"
             },
             "engines": {
                 "node": ">= 0.4"
@@ -32543,14 +32522,20 @@
             }
         },
         "node_modules/typed-array-length": {
-            "version": "1.0.4",
-            "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
-            "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz",
+            "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==",
             "dev": true,
             "dependencies": {
-                "call-bind": "^1.0.2",
+                "call-bind": "^1.0.7",
                 "for-each": "^0.3.3",
-                "is-typed-array": "^1.1.9"
+                "gopd": "^1.0.1",
+                "has-proto": "^1.0.3",
+                "is-typed-array": "^1.1.13",
+                "possible-typed-array-names": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
             },
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
@@ -32711,17 +32696,6 @@
                 "url": "https://opencollective.com/unified"
             }
         },
-        "node_modules/unified/node_modules/is-plain-obj": {
-            "version": "4.1.0",
-            "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
-            "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
-            "engines": {
-                "node": ">=12"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
         "node_modules/uninstall": {
             "version": "0.0.0",
             "resolved": "https://registry.npmjs.org/uninstall/-/uninstall-0.0.0.tgz",
@@ -33233,9 +33207,9 @@
             }
         },
         "node_modules/web-streams-polyfill": {
-            "version": "3.3.2",
-            "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz",
-            "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==",
+            "version": "3.3.3",
+            "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+            "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
             "engines": {
                 "node": ">= 8"
             }
@@ -33249,9 +33223,9 @@
             }
         },
         "node_modules/webpack": {
-            "version": "5.90.1",
-            "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz",
-            "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==",
+            "version": "5.90.3",
+            "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz",
+            "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==",
             "dependencies": {
                 "@types/eslint-scope": "^3.7.3",
                 "@types/estree": "^1.0.5",
@@ -33392,49 +33366,12 @@
                 }
             }
         },
-        "node_modules/webpack-dev-middleware/node_modules/arg": {
-            "version": "5.0.2",
-            "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
-            "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
-        },
-        "node_modules/webpack-dev-middleware/node_modules/json-joy": {
-            "version": "11.28.0",
-            "resolved": "https://registry.npmjs.org/json-joy/-/json-joy-11.28.0.tgz",
-            "integrity": "sha512-WTq2tYD2r+0rUFId4gtUjwejV20pArh4q2WRJKxJdwLlPFHyW94HwwB2vUr5lUJTVkehhhWEVLwOUI0MSacNIw==",
-            "dependencies": {
-                "arg": "^5.0.2",
-                "hyperdyperid": "^1.2.0",
-                "thingies": "^1.14.1"
-            },
-            "bin": {
-                "jj": "bin/jj.js",
-                "json-pack": "bin/json-pack.js",
-                "json-pack-test": "bin/json-pack-test.js",
-                "json-patch": "bin/json-patch.js",
-                "json-patch-test": "bin/json-patch-test.js",
-                "json-pointer": "bin/json-pointer.js",
-                "json-pointer-test": "bin/json-pointer-test.js",
-                "json-unpack": "bin/json-unpack.js"
-            },
-            "engines": {
-                "node": ">=10.0"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/streamich"
-            },
-            "peerDependencies": {
-                "quill-delta": "^5",
-                "rxjs": "7",
-                "tslib": "2"
-            }
-        },
         "node_modules/webpack-dev-middleware/node_modules/memfs": {
-            "version": "4.7.0",
-            "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.7.0.tgz",
-            "integrity": "sha512-FGbf9Yz2gzXCUmpymkKnzAQOitriZQlIMtmnzb2LOcT0FTUdzL6AAwNGQrSOACx/UiW7XQsG65vrIA9+L01Edw==",
+            "version": "4.7.7",
+            "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.7.7.tgz",
+            "integrity": "sha512-x9qc6k88J/VVwnfTkJV8pRRswJ2156Rc4w5rciRqKceFDZ0y1MqsNL9pkg5sE0GOcDzZYbonreALhaHzg1siFw==",
             "dependencies": {
-                "json-joy": "^11.0.0",
-                "thingies": "^1.11.1"
+                "tslib": "^2.0.0"
             },
             "engines": {
                 "node": ">= 4.0.0"
@@ -33442,18 +33379,6 @@
             "funding": {
                 "type": "github",
                 "url": "https://github.com/sponsors/streamich"
-            },
-            "peerDependencies": {
-                "tslib": "2"
-            }
-        },
-        "node_modules/webpack-dev-middleware/node_modules/rxjs": {
-            "version": "7.8.1",
-            "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
-            "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
-            "peer": true,
-            "dependencies": {
-                "tslib": "^2.1.0"
             }
         },
         "node_modules/webpack-dev-server": {
@@ -34218,18 +34143,6 @@
                 "node": ">=10"
             }
         },
-        "node_modules/yargs-unparser/node_modules/decamelize": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
-            "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
-            "dev": true,
-            "engines": {
-                "node": ">=10"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
         "node_modules/yargs-unparser/node_modules/is-plain-obj": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a69030019..3670382c5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -37,7 +37,7 @@ import { GestureOverlay } from '../../GestureOverlay';
 import { CtrlKey } from '../../GlobalKeyHandler';
 import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
 import { LightboxView } from '../../LightboxView';
-import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
+import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
 import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
 import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
 import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView';
@@ -1181,7 +1181,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const childLayout = entry.pair.layout;
         const childData = entry.pair.data;
         return (
-            <CollectionFreeFormDocumentViewWrapper
+            <CollectionFreeFormDocumentView
                 {...OmitKeys(entry, ['replica', 'pair']).omit}
                 key={childLayout[Id] + (entry.replica || '')}
                 Document={childLayout}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index a0a64ab59..2800ea200 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, makeObservable, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { OmitKeys, numberRange } from '../../../Utils';
@@ -12,14 +12,16 @@ import { DocumentManager } from '../../util/DocumentManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { DocComponent } from '../DocComponent';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import './CollectionFreeFormDocumentView.scss';
 import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
 import { FieldViewProps } from './FieldView';
 
-export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps {
+/// 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
+const freeFormPropsKeys = ['x', 'y', 'z', 'zIndex', 'rotation', 'opacity', 'backgroundColor', 'color', 'highlight', 'width', 'height', 'autoDim', 'transition'];
+interface freeFormProps {
     x: number;
     y: number;
     z: number;
@@ -33,102 +35,17 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
     opacity?: number;
     highlight?: boolean;
     transition?: string;
-    RenderCutoffProvider: (doc: Doc) => boolean;
-    CollectionFreeFormView: CollectionFreeFormView;
-}
-@observer
-export class CollectionFreeFormDocumentViewWrapper extends ObservableReactComponent<CollectionFreeFormDocumentViewWrapperProps> {
-    constructor(props: CollectionFreeFormDocumentViewWrapperProps) {
-        super(props);
-        makeObservable(this);
-    }
-    @observable X = this.props.x;
-    @observable Y = this.props.y;
-    @observable Z = this.props.z;
-    @observable ZIndex = this.props.zIndex;
-    @observable Rotation = this.props.rotation;
-    @observable Opacity = this.props.opacity;
-    @observable BackgroundColor = this.props.backgroundColor;
-    @observable Color = this.props.color;
-    @observable Highlight = this.props.highlight;
-    @observable Width = this.props.width;
-    @observable Height = this.props.height;
-    @observable AutoDim = this.props.autoDim;
-    @observable Transition = this.props.transition;
-    CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
-    RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
-
-    get Document() {
-        return this._props.Document;
-    }
-    @computed get WrapperKeys() {
-        return Object.keys(this).filter(key => key.startsWith('w_')).map(key => key.replace('w_', ''))
-                  .map(key => ({upper:key, lower:key[0].toLowerCase() + key.substring(1)})); // prettier-ignore
-    }
-
-    // wrapper functions around prop fields that have been converted to observables to keep 'props' from ever changing.
-    // this way, downstream code only invalidates when it uses a specific prop, not when any prop changes
-    w_X = ()               => this.X; // prettier-ignore
-    w_Y = ()               => this.Y; // prettier-ignore
-    w_Z = ()               => this.Z; // prettier-ignore
-    w_ZIndex = ()          => this.ZIndex ?? NumCast(this.Document.zIndex); // prettier-ignore
-    w_Rotation = ()        => this.Rotation ?? NumCast(this.Document._rotation); // prettier-ignore
-    w_Opacity = ()         => this.Opacity; // prettier-ignore
-    w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.Document._backgroundColor, 'string', null); // prettier-ignore
-    w_Color = ()           => this.Color ?? Cast(this.Document._color, 'string', null); // prettier-ignore
-    w_Highlight = ()       => this.Highlight; // prettier-ignore
-    w_Width = ()           => this.Width; // prettier-ignore
-    w_Height = ()          => this.Height; // prettier-ignore
-    w_AutoDim = ()         => this.AutoDim; // prettier-ignore
-    w_Transition = ()      => this.Transition; // prettier-ignore
-
-    PanelWidth = ()        => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
-    PanelHeight = ()       => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
-
-    componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>>) {
-        super.componentDidUpdate(prevProps);
-        this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
-    }
-    render() {
-        const layoutProps = this.WrapperKeys.reduce((val, keys) => [(val['w_' + keys.upper] = (this as any)['w_' + keys.upper]), val][1], {} as { [key: string]: Function });
-        return (
-            <CollectionFreeFormDocumentView
-                {...OmitKeys(this._props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore
-                {...layoutProps}
-                PanelWidth={this.PanelWidth}
-                PanelHeight={this.PanelHeight}
-            />
-        );
-    }
 }
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    w_X: () => number;
-    w_Y: () => number;
-    w_Z: () => number;
-    w_ZIndex?: () => number;
-    w_Rotation?: () => number;
-    w_Color: () => string;
-    w_BackgroundColor: () => string;
-    w_Opacity: () => number | undefined;
-    w_Highlight: () => boolean | undefined;
-    w_Transition: () => string | undefined;
-    w_Width: () => number;
-    w_Height: () => number;
-    PanelWidth: () => number;
-    PanelHeight: () => number;
     RenderCutoffProvider: (doc: Doc) => boolean;
     CollectionFreeFormView: CollectionFreeFormView;
 }
-
 @observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
-    constructor(props: CollectionFreeFormDocumentViewProps) {
-        super(props);
-        makeObservable(this);
-    }
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & freeFormProps>() {
     get displayName() { // this makes mobx trace() statements more descriptive
         return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
     } // prettier-ignore
+    public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
     public static animFields: { key: string; val?: number }[] = [
         { key: 'x' },
         { key: 'y' },
@@ -145,16 +62,53 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
     public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
 
-    get CollectionFreeFormView() {
-        return this._props.CollectionFreeFormView;
+    constructor(props: CollectionFreeFormDocumentViewProps & freeFormProps) {
+        super(props);
+        makeObservable(this);
+    }
+
+    get WrapperKeys() {
+        // each of these keys is set by the CollectionView and passed via props. however, if any one of these props changes
+        // (or any other prop), then it's as if they all change.
+        // Anything that accesses these props will then invalidate unncessarily.
+        // To avoid this, we copy these prop values into local observables.  Now when 'props' changes, nothing invalidates.
+        // Instead, we copy each values into its observable which ohnly triggers invalidations if the new value is different
+        // than the old value, and then only things that access that observable will invalidate.
+        return freeFormPropsKeys
+                  .map(key => ({upper:key[0].toUpperCase() + key.substring(1), lower:key})); // prettier-ignore
+    }
+    @observable X = this.props.x;
+    @observable Y = this.props.y;
+    @observable Z = this.props.z;
+    @observable ZIndex = this.props.zIndex;
+    @observable Rotation = this.props.rotation;
+    @observable Opacity = this.props.opacity;
+    @observable BackgroundColor = this.props.backgroundColor;
+    @observable Color = this.props.color;
+    @observable Highlight = this.props.highlight;
+    @observable Width = this.props.width;
+    @observable Height = this.props.height;
+    @observable AutoDim = this.props.autoDim;
+    @observable Transition = this.props.transition;
+
+    componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewProps & freeFormProps>>) {
+        super.componentDidUpdate(prevProps);
+        this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
     }
 
+    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._props.transition; // prettier-ignore
+    RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
+    PanelWidth = ()        => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
+    PanelHeight = ()       => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
+
     styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
         if (doc === this.layoutDoc) {
             switch (property) {
-                case StyleProp.Opacity:         return this._props.w_Opacity(); // only change the opacity for this specific document, not its children
-                case StyleProp.BackgroundColor: return this._props.w_BackgroundColor();
-                case StyleProp.Color:           return this._props.w_Color();
+                case StyleProp.Opacity:         return this.Opacity; // only change the opacity for this specific document, not its children
+                case StyleProp.BackgroundColor: return this.BackgroundColor;
+                case StyleProp.Color:           return this.Color;
             } // prettier-ignore
         }
         return this._props.styleProvider?.(doc, props, property);
@@ -253,14 +207,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     nudge = (x: number, y: number) => {
         const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y);
-        this.Document.x = this._props.w_X() + locX;
-        this.Document.y = this._props.w_Y() + locY;
+        this.Document.x = this.X + locX;
+        this.Document.y = this.Y + locY;
     };
     screenToLocalTransform = () =>
         this._props
             .ScreenToLocalTransform()
-            .translate(-this._props.w_X(), -this._props.w_Y())
-            .rotateDeg(-(this._props.w_Rotation?.() || 0));
+            .translate(-this.X, -this.Y)
+            .rotateDeg(-(this.Rotation || 0));
     returnThis = () => this;
 
     /// this indicates whether the doc view is activated because of its relationshop to a group
@@ -273,27 +227,26 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         const isGroup = this.dataDoc.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
         return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
     };
-    public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
     render() {
         TraceMobx();
-        const passOnProps = OmitKeys(this._props, Object.keys(this._props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore
+
         return (
             <div
                 className={CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName}
                 style={{
-                    width: this._props.PanelWidth(),
-                    height: this._props.PanelHeight(),
-                    transform: `translate(${this._props.w_X()}px, ${this._props.w_Y()}px) rotate(${NumCast(this._props.w_Rotation?.())}deg)`,
-                    transition: this._props.w_Transition?.() || StrCast(this.Document.dataTransition),
-                    zIndex: this._props.w_ZIndex?.(),
-                    display: this._props.w_Width?.() ? undefined : 'none',
+                    width: this.PanelWidth(),
+                    height: this.PanelHeight(),
+                    transform: `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rotation)}deg)`,
+                    transition: this.Transition || StrCast(this.Document.dataTransition),
+                    zIndex: this.ZIndex,
+                    display: this.Width ? undefined : 'none',
                 }}>
-                {this._props.RenderCutoffProvider(this.Document) ? (
-                    <div style={{ position: 'absolute', width: this._props.PanelWidth(), height: this._props.PanelHeight(), background: 'lightGreen' }} />
+                {this.RenderCutoffProvider(this.Document) ? (
+                    <div style={{ position: 'absolute', width: this.PanelWidth(), height: this.PanelHeight(), background: 'lightGreen' }} />
                 ) : (
                     <DocumentView
-                        {...passOnProps}
-                        DataTransition={this._props.w_Transition}
+                        {...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore
+                        DataTransition={this.DataTransition}
                         CollectionFreeFormDocumentView={this.returnThis}
                         styleProvider={this.styleProvider}
                         ScreenToLocalTransform={this.screenToLocalTransform}
-- 
cgit v1.2.3-70-g09d2


From 27306bd0ae259241182d90cc62225609dfc8da74 Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Sun, 25 Feb 2024 12:51:26 -0500
Subject: fixed conversion of doc to schema cells to allow number 0 to be
 converted to a string.

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3670382c5..50a9feff8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1962,8 +1962,8 @@ ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
         for (let i = 0; i < children.length; i++) {
             let eachRow = [];
             for (let j = 0; j < keys.length; j++) {
-                var cell = children[i][keys[j]];
-                if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+                var cell = children[i][keys[j]]?.toString();
+                if (cell) cell = cell.toString().replace(/\,/g, '');
                 eachRow.push(cell);
             }
             csvRows.push(eachRow);
-- 
cgit v1.2.3-70-g09d2


From d99f64efe9e69f2159f1ad8f851b24533a996ba5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 26 Feb 2024 12:57:48 -0500
Subject: fixed some string types to be enumerations for dropAction.  fixed bug
 in golden layout dragging where a stack's tabs could disappear.

---
 src/client/documents/Documents.ts                  | 40 ++++++++++------
 src/client/goldenLayout.js                         |  2 +-
 src/client/util/CurrentUserUtils.ts                | 44 +++++++++---------
 src/client/util/DragManager.ts                     | 54 +++++++++++++++-------
 src/client/views/MainView.tsx                      |  6 +--
 src/client/views/collections/CollectionMenu.tsx    |  4 +-
 .../views/collections/CollectionPileView.tsx       |  3 +-
 .../views/collections/CollectionTreeView.tsx       | 16 +++++--
 src/client/views/collections/TreeView.tsx          |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  9 +++-
 .../collectionSchema/CollectionSchemaView.tsx      |  4 +-
 .../collectionSchema/SchemaTableCell.tsx           |  7 +--
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/trails/PresBox.tsx          |  5 +-
 src/client/views/topbar/TopBar.tsx                 |  3 +-
 src/fields/documentSchemas.ts                      |  6 +--
 17 files changed, 129 insertions(+), 82 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2d2f5fe4a..8a13395c3 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -68,10 +68,20 @@ class EmptyBox {
         return '';
     }
 }
+
+export enum FInfoFieldType {
+    string,
+    boolean,
+    number,
+    Doc,
+    enumeration,
+    date,
+    list,
+}
 export class FInfo {
     description: string = '';
     readOnly: boolean = false;
-    fieldType?: string = '';
+    fieldType?: FInfoFieldType;
     values?: Field[];
 
     filterable?: boolean = true;
@@ -84,7 +94,7 @@ export class FInfo {
     searchable = () => true;
 }
 class BoolInfo extends FInfo {
-    fieldType? = 'boolean';
+    fieldType? = FInfoFieldType.boolean;
     values?: boolean[] = [true, false];
     constructor(d: string, filterable?: boolean) {
         super(d);
@@ -93,7 +103,7 @@ class BoolInfo extends FInfo {
     override searchable = () => false;
 }
 class NumInfo extends FInfo {
-    fieldType? = 'number';
+    fieldType? = FInfoFieldType.number;
     values?: number[] = [];
     constructor(d: string, filterable?: boolean, readOnly?: boolean, values?: number[]) {
         super(d, readOnly);
@@ -103,7 +113,7 @@ class NumInfo extends FInfo {
     override searchable = () => false;
 }
 class StrInfo extends FInfo {
-    fieldType? = 'string';
+    fieldType? = FInfoFieldType.string;
     values?: string[] = [];
     constructor(d: string, filterable?: boolean, readOnly?: boolean, values?: string[]) {
         super(d, readOnly);
@@ -112,7 +122,7 @@ class StrInfo extends FInfo {
     }
 }
 class DocInfo extends FInfo {
-    fieldType? = 'Doc';
+    fieldType? = FInfoFieldType.Doc;
     values?: Doc[] = [];
     constructor(d: string, filterable?: boolean, values?: Doc[]) {
         super(d, true);
@@ -122,45 +132,45 @@ class DocInfo extends FInfo {
     override searchable = () => false;
 }
 class DimInfo extends FInfo {
-    fieldType? = 'enumeration';
+    fieldType? = FInfoFieldType.enumeration;
     values? = [DimUnit.Pixel, DimUnit.Ratio];
     readOnly = false;
     filterable = false;
     override searchable = () => false;
 }
 class PEInfo extends FInfo {
-    fieldType? = 'enumeration';
+    fieldType? = FInfoFieldType.enumeration;
     values? = ['all', 'none'];
     readOnly = false;
     filterable = false;
     override searchable = () => false;
 }
 class DAInfo extends FInfo {
-    fieldType? = 'enumeration';
-    values? = ['embed', 'copy', 'move', 'same', 'proto', 'none'];
+    fieldType? = FInfoFieldType.enumeration;
+    values? = ['embed', 'copy', 'move', 'same', 'add', 'inSame', 'proto'];
     readOnly = false;
     filterable = false;
     override searchable = () => false;
 }
 class CTypeInfo extends FInfo {
-    fieldType? = 'enumeration';
+    fieldType? = FInfoFieldType.enumeration;
     values? = Array.from(Object.keys(CollectionViewType));
     readOnly = false;
     filterable = false;
     override searchable = () => false;
 }
 class DTypeInfo extends FInfo {
-    fieldType? = 'enumeration';
+    fieldType? = FInfoFieldType.enumeration;
     values? = Array.from(Object.keys(DocumentType));
     override searchable = () => false;
 }
 class DateInfo extends FInfo {
-    fieldType? = 'date';
+    fieldType? = FInfoFieldType.date;
     values?: DateField[] = [];
     filterable = true;
 }
 class ListInfo extends FInfo {
-    fieldType? = 'list';
+    fieldType? = FInfoFieldType.list;
     values?: List<any>[] = [];
 }
 type BOOLt = BoolInfo | boolean;
@@ -728,7 +738,7 @@ export namespace Docs {
                 {
                     data: '',
                     layout: { view: ComparisonBox, dataField: defaultDataKey },
-                    options: { backgroundColor: 'gray', dropAction: 'move', waitForDoubleClickToClick: 'always', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit' },
+                    options: { backgroundColor: 'gray', dropAction: dropActionType.move, waitForDoubleClickToClick: 'always', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit' },
                 },
             ],
             [
@@ -1182,7 +1192,7 @@ export namespace Docs {
             return InstanceFromProto(
                 Prototypes.get(DocumentType.COL),
                 new List(documents),
-                { backgroundColor: 'transparent', dropAction: 'move', _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Pile },
+                { backgroundColor: 'transparent', dropAction: dropActionType.move, _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Pile },
                 id
             );
         }
diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index 2b94d35ee..cb1dfd76a 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -4727,7 +4727,7 @@
                  */
             } else {
                 type = isVertical ? 'column' : 'row';
-                if (this.parent.contentItems.length === 1) {
+                if (this.parent.contentItems.length === 1 && this.contentItems.length === 1) {
                     let grandparent = this.parent.parent;
                     let correctRowOrCol = this.layoutManager.createContentItem({ type: type }, this);
                     grandparent.replaceChild(this.parent, correctRowOrCol);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 714e33d25..d396ba815 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -14,7 +14,7 @@ import { SetCachedGroups, SharingPermissions } from "../../fields/util";
 import { GestureUtils } from "../../pen-gestures/GestureUtils";
 import { DocServer } from "../DocServer";
 import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
-import { DocUtils, Docs, DocumentOptions, FInfo } from "../documents/Documents";
+import { DocUtils, Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents";
 import { DashboardView } from "../views/DashboardView";
 import { OverlayView } from "../views/OverlayView";
 import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView";
@@ -257,11 +257,11 @@ export class CurrentUserUtils {
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts),   opts: { _width: 300, _height: 300 }},
             {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeView_HideUnrendered: true}},
             {key: "ViewSlide",  creator: slideView,                                         opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}},
-            {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: "embed" as dropActionType, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }},
+            {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }},
             {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }},
             {key: "Slide",      creator: opts => Docs.Create.TreeDocument([], opts),        opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree, 
                                                                                                 treeView_HasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true, 
-                                                                                                dropAction:'move', treeView_Type: TreeViewType.outline, 
+                                                                                                dropAction:dropActionType.move, treeView_Type: TreeViewType.outline, 
                                                                                                 backgroundColor: "white", _xMargin: 0, _yMargin: 0, _createDocOnCR: true 
                                                                                             }, funcs: {title: 'this.text?.Text'}},
             ];
@@ -311,7 +311,7 @@ export class CurrentUserUtils {
         const reqdOpts:DocumentOptions = {
             title: "Document Creators", _layout_showTitle: "title", _xMargin: 0, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
             _layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
-             childDragAction: 'embed' 
+             childDragAction: dropActionType.embed
         };
         const reqdScripts = { dropConverter: "convertToButtons(dragData)" };
         return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts);
@@ -353,7 +353,7 @@ export class CurrentUserUtils {
         });
 
         const reqdStackOpts:DocumentOptions ={
-            title: "menuItemPanel", childDragAction: "same", layout_boxShadow: "rgba(0,0,0,0)",  dontRegisterView: true, ignoreClick: true,
+            title: "menuItemPanel", childDragAction: dropActionType.same, layout_boxShadow: "rgba(0,0,0,0)",  dontRegisterView: true, ignoreClick: true,
             _chromeHidden: true, _gridGap: 0, _yMargin: 0, _xMargin: 0, _layout_autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, isSystem: true,
         };
         return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
@@ -436,7 +436,7 @@ export class CurrentUserUtils {
     /// Search option on the left side button panel
     static setupSearcher(doc: Doc, field:string) {
         return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), {
-                dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDragAction: "embed", 
+                dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDragAction: dropActionType.embed, 
                 _lockedPosition: true, _type_collection: CollectionViewType.Schema  });
     }
 
@@ -476,8 +476,8 @@ export class CurrentUserUtils {
         const childContextMenuIcons = ["tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
         const reqdOpts:DocumentOptions = {
             title: "My Dashboards", childHideLinkButton: true, treeView_FreezeChildren: "remove|add", treeView_HideTitle: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
-            dropAction: "inSame", treeView_Type: TreeViewType.fileSystem, isFolder: true, isSystem: true,  treeView_TruncateTitleWidth: 350, ignoreClick: true, 
-            layout_headerButton: newDashboardButton, childDragAction: "inSame",
+            dropAction: dropActionType.inPlace, treeView_Type: TreeViewType.fileSystem, isFolder: true, isSystem: true,  treeView_TruncateTitleWidth: 350, ignoreClick: true, 
+            layout_headerButton: newDashboardButton, childDragAction: dropActionType.inPlace,
             _layout_showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
             contextMenuLabels:new List<string>(contextMenuLabels),
             contextMenuIcons:new List<string>(contextMenuIcons),
@@ -510,9 +510,9 @@ export class CurrentUserUtils {
         const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.layout_headerButton), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript);
         
         const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _forceActive: true, 
-            title: "My Documents", layout_headerButton: newFolderButton, treeView_HideTitle: true, dropAction: 'add', isSystem: true,
+            title: "My Documents", layout_headerButton: newFolderButton, treeView_HideTitle: true, dropAction: dropActionType.add, isSystem: true,
             isFolder: true, treeView_Type: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true, 
-            treeView_TruncateTitleWidth: 350, ignoreClick: true, childDragAction: "embed",
+            treeView_TruncateTitleWidth: 350, ignoreClick: true, childDragAction: dropActionType.embed,
             layout_explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
         };
         const fileFolders = new Set(DocListCast(DocCast(doc[field])?.data));
@@ -522,8 +522,8 @@ export class CurrentUserUtils {
     /// initializes the panel displaying docs that have been recently closed
     static setupRecentlyClosed(doc: Doc, field:string) {
         const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, isFolder: true,
-            title: "My Recently Closed", childHideLinkButton: true, treeView_HideTitle: true, childDragAction: "move", isSystem: true,
-            treeView_TruncateTitleWidth: 350, ignoreClick: true,  layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: "same",
+            title: "My Recently Closed", childHideLinkButton: true, treeView_HideTitle: true, childDragAction: dropActionType.move, isSystem: true,
+            treeView_TruncateTitleWidth: 350, ignoreClick: true,  layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: dropActionType.same,
             contextMenuLabels: new List<string>(["Empty recently closed"]),
             contextMenuIcons:new List<string>(["trash"]),
             layout_explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
@@ -546,7 +546,7 @@ export class CurrentUserUtils {
     static setupUserDocView(doc: Doc, field:string) {
         const reqdOpts:DocumentOptions = {
             _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
-            layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: "same", ignoreClick: true, isSystem: true,
+            layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: dropActionType.same, ignoreClick: true, isSystem: true,
             treeView_HideTitle: true, treeView_TruncateTitleWidth: 350
         };
         if (!doc[field]) DocUtils.AssignOpts(doc, {treeView_Open: true, treeView_ExpandedView: "fields" });
@@ -587,7 +587,7 @@ export class CurrentUserUtils {
         ];
         const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts));
         const dockBtnsReqdOpts:DocumentOptions = {
-            title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move',
+            title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: dropActionType.move,
             childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: true, ignoreClick: true
         };
         reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
@@ -743,7 +743,7 @@ export class CurrentUserUtils {
 
     /// Initializes all the default buttons for the top bar context menu
     static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
-        const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true,  linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
+        const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: dropActionType.embed, childDontRegisterViews: true,  linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
         const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined);
         const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) );
         return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
@@ -769,7 +769,7 @@ export class CurrentUserUtils {
         ];
         const btns = btnDescs.map(desc => dockBtn({_width: desc.opts.width??30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts, desc.funcs));
         const dockBtnsReqdOpts:DocumentOptions = {
-            title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move',
+            title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: dropActionType.move,
             childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: false, ignoreClick: true
         };
         return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
@@ -809,7 +809,7 @@ export class CurrentUserUtils {
             // childContextMenuLabels: new List<string>(["Add to Dashboards",]),
             // childContextMenuIcons: new List<string>(["user-plus",]),
             "acl-Guest": SharingPermissions.Augment, "_acl-Guest": SharingPermissions.Augment,
-            childDragAction: "embed", isSystem: true, childContentPointerEvents: "none", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
+            childDragAction: dropActionType.embed, isSystem: true, childContentPointerEvents: "none", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
             // NOTE: treeView_HideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar
             _layout_showTitle: "title", treeView_HideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, 
             layout_explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'"
@@ -824,7 +824,7 @@ export class CurrentUserUtils {
         const reqdOpts:DocumentOptions = {
             title: "My Imports", _forceActive: true, _layout_showTitle: "title", childLayoutString: ImportElementBox.LayoutString('data'),
             _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, childLimitHeight: 0, onClickScriptDisable:"never",
-            childDragAction: "copy", _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true,
+            childDragAction: dropActionType.copy, _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true,
             dontRegisterView: true, layout_explainer: "This is where documents that are Imported into Dash will go."
         };
         const myImports = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.MasonryDocument([], opts), reqdOpts, undefined, {onClick: "deselectAll()"});
@@ -886,7 +886,7 @@ export class CurrentUserUtils {
         this.setupDocTemplates(doc); // sets up the template menu of templates
         //this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
         DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) =>  Docs.Prototypes.MainScriptDocument(), {});
-        DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, _chromeHidden:true, childLayoutFitWidth:false, childDocumentsActive:false, dropAction: 'move'}); // drop down panel at top of dashboard for stashing documents
+        DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, _chromeHidden:true, 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)
@@ -909,9 +909,9 @@ export class CurrentUserUtils {
                 const options = pair[1] as FInfo;
                 const opts:DocumentOptions = { isSystem: true, title: pair[0], ...OmitKeys(options, ["values"]).omit, fieldIsLayout: pair[0].startsWith("_")};
                 switch (options.fieldType) {
-                    case "boolean": opts.fieldValues = new List<boolean>(options.values as any); break;
-                    case "number": opts.fieldValues = new List<number>(options.values as any); break;
-                    case Doc.name: opts.fieldValues = new List<Doc>(options.values as any); break;
+                    case FInfoFieldType.boolean: opts.fieldValues = new List<boolean>(options.values as any); break;
+                    case FInfoFieldType.number: opts.fieldValues = new List<number>(options.values as any); break;
+                    case FInfoFieldType.Doc: opts.fieldValues = new List<Doc>(options.values as any); break;
                     default: opts.fieldValues = new List<string>(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType
                 }
                 DocUtils.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 1f093a33c..f6af6196f 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,10 +1,24 @@
+/**
+ * The DragManager handles all dragging interactions that occur entirely within Dash (as opposed to external drag operations from the file system, etc)
+ *
+ * Events are generated for
+ *  a pause in the drag movement (dashDragMovePause) as a Doc(s) is dragged,
+ *  just before (dashPreDrop) a Doc(s) is dropped,
+ *  and just after (dashDropEvent) a Doc(s) is dropped
+ * If the document is dragged and paused over the golden layout header tabs, the
+ *   drag interaction will switch to a golden layout tab drag.
+ *
+ * All drag operations can be aborted by hitting the Esc key
+ *
+ */
+
 import { action, observable, runInAction } from 'mobx';
 import { DateField } from '../../fields/DateField';
 import { Doc, Field, Opt, StrListCast } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
 import { ScriptField } from '../../fields/ScriptField';
-import { ScriptCast, StrCast } from '../../fields/Types';
+import { ScriptCast } from '../../fields/Types';
 import { emptyFunction, Utils } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView';
@@ -16,7 +30,15 @@ import { UndoManager } from './UndoManager';
 import { DocData } from '../../fields/DocSymbols';
 const { default : { contextMenuZindex } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
 
-export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'inSame' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
+export enum dropActionType {
+    embed = 'embed', // create a new embedding of the dragged document for the new location
+    copy = 'copy', // copy the dragged document
+    move = 'move', // move the dragged document to the drop location after removing it from where it was
+    add = 'add', // add the dragged document to the drop location without removing it from where it was
+    same = 'same', // only allow drop within same collection (or same hierarchical tree collection)
+    inPlace = 'inSame', // keep document in place (unless overridden by a drag modifier)
+    proto = 'proto',
+} // undefined = move, same = move but doesn't call dropPropertiesToRemove
 
 /**
  * Initialize drag
@@ -129,9 +151,9 @@ export namespace DragManager {
         treeViewDoc?: Doc;
         offset: number[];
         canEmbed?: boolean;
-        userDropAction: dropActionType; // the user requested drop action -- this will be honored as specified by modifier keys
+        userDropAction?: dropActionType; // the user requested drop action -- this will be honored as specified by modifier keys
         defaultDropAction?: dropActionType; // an optionally specified default drop action when there is no user drop actionl - this will be honored if there is no user drop action
-        dropAction: dropActionType; // a drop action request by the initiating code.  the actual drop action may be different -- eg, if the request is 'embed', but the document is dropped within the same collection, the drop action will be switched to 'move'
+        dropAction?: dropActionType; // a drop action request by the initiating code.  the actual drop action may be different -- eg, if the request is 'embed', but the document is dropped within the same collection, the drop action will be switched to 'move'
         dropPropertiesToRemove?: string[];
         moveDocument?: MoveFunction;
         removeDocument?: RemoveFunction;
@@ -170,8 +192,8 @@ export namespace DragManager {
         dropDocCreator: (annotationOn: Doc | undefined) => Doc;
         dropDocument?: Doc;
         offset: number[];
-        dropAction: dropActionType;
-        userDropAction: dropActionType;
+        dropAction?: dropActionType;
+        userDropAction?: dropActionType;
     }
 
     let defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
@@ -189,7 +211,7 @@ export namespace DragManager {
         const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
         const preDropHandler = (e: Event) => {
             const de = (e as CustomEvent<DropEvent>).detail;
-            (preDropFunc ?? defaultPreDropFunc)(e, de, StrCast(doc.dropAction) as dropActionType);
+            (preDropFunc ?? defaultPreDropFunc)(e, de, doc.dropAction as any as dropActionType);
         };
         element.addEventListener('dashOnDrop', handler);
         element.addEventListener('dashPreDrop', preDropHandler);
@@ -218,19 +240,19 @@ export namespace DragManager {
                         dragData.draggedDocuments.map(async d =>
                             !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
                                 ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result)
-                                : docDragData.dropAction === 'embed'
+                                : docDragData.dropAction === dropActionType.embed
                                   ? Doc.BestEmbedding(d)
-                                  : docDragData.dropAction === 'add'
+                                  : docDragData.dropAction === dropActionType.add
                                     ? d
-                                    : docDragData.dropAction === 'proto'
+                                    : docDragData.dropAction === dropActionType.proto
                                       ? d[DocData]
-                                      : docDragData.dropAction === 'copy'
+                                      : docDragData.dropAction === dropActionType.copy
                                         ? (await Doc.MakeClone(d)).clone
                                         : d
                         )
                     )
                 ).filter(d => d);
-                !['same', 'proto'].includes(docDragData.dropAction as any) &&
+                ![dropActionType.same, dropActionType.proto].includes(docDragData.dropAction as any) &&
                     docDragData.droppedDocuments
                         // .filter(drop => !drop.dragOnlyWithinContainer || ['embed', 'copy'].includes(docDragData.dropAction as any))
                         .forEach((drop: Doc, i: number) => {
@@ -504,7 +526,7 @@ export namespace DragManager {
         const moveHandler = (e: PointerEvent) => {
             e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
             if (dragData instanceof DocumentDragData) {
-                dragData.userDropAction = e.ctrlKey && e.altKey ? 'copy' : e.shiftKey ? 'move' : e.ctrlKey ? 'embed' : dragData.defaultDropAction;
+                dragData.userDropAction = e.ctrlKey && e.altKey ? dropActionType.copy : e.shiftKey ? dropActionType.move : e.ctrlKey ? dropActionType.embed : dragData.defaultDropAction;
             }
             if (['lm_tab', 'lm_title_wrap', 'lm_tabs', 'lm_header'].includes(typeof (e.target as any).className === 'string' ? (e.target as any)?.className : '') && dragData.draggedDocuments.length === 1) {
                 if (!startWindowDragTimer) {
@@ -530,7 +552,7 @@ export namespace DragManager {
             if (target && !Doc.UserDoc()._noAutoscroll && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._freeform_noAutoPan)) {
                 const autoScrollHandler = () => {
                     target.dispatchEvent(
-                        new CustomEvent<React.DragEvent>('dashDragAutoScroll', {
+                        new CustomEvent<React.DragEvent>('dashDragMovePause', {
                             bubbles: true,
                             detail: {
                                 shiftKey: e.shiftKey,
@@ -552,7 +574,7 @@ export namespace DragManager {
                                 screenY: e.screenY,
                                 detail: e.detail,
                                 view: e.view ? e.view : (new Window() as any),
-                                nativeEvent: new DragEvent('dashDragAutoScroll'),
+                                nativeEvent: new DragEvent('dashDragMovePause'),
                                 currentTarget: target,
                                 target: target,
                                 bubbles: true,
@@ -566,7 +588,7 @@ export namespace DragManager {
                                 isPropagationStopped: () => ('not implemented for this event' ? false : false),
                                 persist: emptyFunction,
                                 timeStamp: e.timeStamp,
-                                type: 'dashDragAutoScroll',
+                                type: 'dashDragMovePause',
                             },
                         })
                     );
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b6cb845a6..3be52597a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -19,7 +19,7 @@ import { Docs } from '../documents/Documents';
 import { CalendarManager } from '../util/CalendarManager';
 import { CaptureManager } from '../util/CaptureManager';
 import { DocumentManager } from '../util/DocumentManager';
-import { DragManager } from '../util/DragManager';
+import { DragManager, dropActionType } from '../util/DragManager';
 import { GroupManager } from '../util/GroupManager';
 import { HistoryUtil } from '../util/History';
 import { Hypothesis } from '../util/HypothesisUtils';
@@ -623,7 +623,7 @@ export class MainView extends ObservableReactComponent<{}> {
                     isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
                     ScreenToLocalTransform={this.headerBarScreenXf}
                     childHideResizeHandles={true}
-                    childDragAction="move"
+                    childDragAction={dropActionType.move}
                     dontRegisterView={true}
                     hideResizeHandles={true}
                     PanelWidth={this.headerBarDocWidth}
@@ -905,7 +905,7 @@ export class MainView extends ObservableReactComponent<{}> {
                     Document={Doc.MyDockedBtns}
                     docViewPath={returnEmptyDocViewList}
                     fieldKey="data"
-                    dropAction="embed"
+                    dropAction={dropActionType.embed}
                     styleProvider={DefaultStyleProvider}
                     select={emptyFunction}
                     isAnyChildContentActive={returnFalse}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 0f90818ef..8729ef549 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -11,7 +11,7 @@ import { RichTextField } from '../../../fields/RichTextField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DragManager } from '../../util/DragManager';
+import { DragManager, dropActionType } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { Transform } from '../../util/Transform';
@@ -91,7 +91,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     Document={selDoc}
                     docViewPath={returnEmptyDocViewList}
                     fieldKey="data"
-                    dropAction="embed"
+                    dropAction={dropActionType.embed}
                     styleProvider={DefaultStyleProvider}
                     select={emptyFunction}
                     isContentActive={returnTrue}
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index d0df77cbe..7d7f0bb61 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -13,6 +13,7 @@ import { computePassLayout, computeStarburstLayout } from './collectionFreeForm'
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import './CollectionPileView.scss';
 import { CollectionSubView } from './CollectionSubView';
+import { dropActionType } from '../../util/DragManager';
 
 @observer
 export class CollectionPileView extends CollectionSubView() {
@@ -72,7 +73,7 @@ export class CollectionPileView extends CollectionSubView() {
                     // pile children never have their contents active, but will be document active whenever the entire pile is.
                     childContentsActive={returnFalse}
                     childDocumentsActive={this._props.isDocumentActive}
-                    childDragAction="move"
+                    childDragAction={dropActionType.move}
                     childClickScript={this.toggleIcon}
                 />
             </div>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 786301136..4d60cbefc 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -153,12 +153,18 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         return res;
     }
 
-    protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => {
+    protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) => {
         const dragData = de.complete.docDragData;
         if (dragData) {
-            const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.Document) ? true : false;
+            const sourceDragAction = dragData.dropAction;
+            const sameTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.Document);
             const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d));
-            dragData.dropAction = dropAction && !isAlreadyInTree() ? dropAction : sameTree && dragData.dropAction !== 'inSame' ? 'same' : dragData.dropAction;
+            dragData.dropAction =
+                targetDropAction && !isAlreadyInTree() // if dropped document is not in the tree
+                    ? targetDropAction // then use the target's drop action if it's specified
+                    : !sameTree() || sourceDragAction === dropActionType.inPlace // if doc from another tree, or a non inPlace source drag action is specified
+                      ? sourceDragAction // use the source dragAction
+                      : dropActionType.same; // otherwise use same tree semantics to move within tree
             e.stopPropagation();
         }
     };
@@ -287,7 +293,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     @observable _renderCount = 1;
     @computed get treeViewElements() {
         TraceMobx();
-        const dragAction = StrCast(this.Document.childDragAction) as dropActionType;
+        const dragAction = StrCast(this.Document.childDragAction) as any as dropActionType;
         const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
         const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false;
         if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
@@ -473,7 +479,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                         isAnnotationOverlayScrollable={true}
                         childDocumentsActive={this._props.isContentActive}
                         fieldKey={this._props.fieldKey + '_annotations'}
-                        dropAction="move"
+                        dropAction={dropActionType.move}
                         select={emptyFunction}
                         addDocument={this.addAnnotationDocument}
                         removeDocument={this.remAnnotationDocument}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 85f7cf7fe..4f2d8b9c0 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -439,7 +439,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         droppedDocuments: Doc[],
         before: boolean,
         inside: number | boolean,
-        dropAction: dropActionType,
+        dropAction: dropActionType | undefined,
         removeDocument: DragManager.RemoveFunction | undefined,
         moveDocument: DragManager.MoveFunction | undefined,
         forceAdd: boolean,
@@ -1161,7 +1161,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         const before = pt[1] < rect.top + rect.height / 2;
         const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
 
-        const docs = this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false, false));
+        this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, dropActionType.copy, undefined, undefined, false, false));
     };
 
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 50a9feff8..50b3dbd70 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -462,7 +462,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 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 { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 };
-                const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
+                const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? dropActionType.embed : undefined);
                 de.moveDocument = this._props.moveDocument;
                 de.offset = this.screenToFreeformContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
                 DragManager.StartDocumentDrag(
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index a417d777a..d0e59180d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -635,8 +635,13 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     }
     MarqueeRef: HTMLDivElement | null = null;
 
+    /**
+     * This is called for every drag movement when a document is dragged over this collection.
+     * If the document is dragged within 25 pixels of the edge of the collection and paused, this will
+     * auto scroll the collection so that it can be dragged farther (unless auto panning has been disabled)
+     */
     @action
-    onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
+    onDragMovePause = (e: CustomEvent<React.DragEvent>) => {
         if ((e as any).handlePan || this._props.isAnnotationOverlay) return;
         (e as any).handlePan = true;
 
@@ -659,7 +664,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             <div
                 className="marqueeView"
                 ref={r => {
-                    r?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
+                    r?.addEventListener('dashDragMovePause', this.onDragMovePause as any);
                     this.MarqueeRef = r;
                 }}
                 style={{
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 31b4a2dd4..eee3836d2 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -10,7 +10,7 @@ import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Ty
 import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
 import { Docs, DocumentOptions, DocUtils, FInfo } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from '../../../util/DragManager';
+import { DragManager, dropActionType } from '../../../util/DragManager';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { undoable, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
@@ -981,7 +981,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
                 styleProvider={this.noOpacityStyleProvider}
                 waitForDoubleClickToClick={returnNever}
                 defaultDoubleClick={returnIgnore}
-                dragAction="move"
+                dragAction={dropActionType.move}
                 onClickScriptDisable="always"
                 focus={this._props.schema.focusDocument}
                 childFilters={this._props.schema.childDocFilters}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 001ad5ab6..46867665d 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -10,7 +10,7 @@ import { Doc, DocListCast, Field } from '../../../../fields/Doc';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
-import { FInfo } from '../../../documents/Documents';
+import { FInfo, FInfoFieldType } from '../../../documents/Documents';
 import { DocFocusOrOpen } from '../../../util/DocumentManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch, undoable } from '../../../util/UndoManager';
@@ -29,6 +29,7 @@ import { Popup, Size, Type } from 'browndash-components';
 import { IconLookup, faCaretDown } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { SettingsManager } from '../../../util/SettingsManager';
+import { dropActionType } from '../../../util/DragManager';
 
 export interface SchemaTableCellProps {
     Document: Doc;
@@ -86,7 +87,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
             isSelected: returnFalse,
             setHeight: returnFalse,
             select: emptyFunction,
-            dragAction: 'move',
+            dragAction: dropActionType.move,
             renderDepth: 1,
             isContentActive: returnFalse,
             whenChildContentsActiveChanged: emptyFunction,
@@ -149,7 +150,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
         if (cellValue instanceof DateField) return ColumnType.Date;
         if (cellValue instanceof RichTextField) return ColumnType.RTF;
         if (typeof cellValue === 'number') return ColumnType.Any;
-        if (typeof cellValue === 'string' && columnTypeStr !== 'enumeration') return ColumnType.Any;
+        if (typeof cellValue === 'string' && columnTypeStr !== FInfoFieldType.enumeration) return ColumnType.Any;
         if (typeof cellValue === 'boolean') return ColumnType.Boolean;
 
         if (columnTypeStr && columnTypeStr in FInfotoColType) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d131f72d5..abb1aa59f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -413,7 +413,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
             this.cleanupPointerEvents();
             this._longPressSelector && clearTimeout(this._longPressSelector);
-            this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
+            this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && dropActionType.embed) || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
         }
     };
 
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index b8f6575dd..918987034 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -36,6 +36,7 @@ import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
+import { dropActionType } from '../../../util/DragManager';
 export interface pinDataTypes {
     scrollable?: boolean;
     dataviz?: number[];
@@ -1074,7 +1075,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
-    removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.Document, this.fieldKey, doc);
+    removeDocument = (doc: Doc | Doc[]) => !(doc instanceof Doc ? [doc] : doc).map(d => Doc.RemoveDocFromList(this.Document, this.fieldKey, d)).some(p => !p);
     getTransform = () => this.ScreenToLocalBoxXf().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
     panelHeight = () => this._props.PanelHeight() - 40;
     /**
@@ -2607,7 +2608,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 childIgnoreNativeSize={true}
                                 moveDocument={returnFalse}
                                 ignoreUnrendered={true}
-                                childDragAction="move"
+                                childDragAction={dropActionType.move}
                                 setContentViewBox={emptyFunction}
                                 //childLayoutFitWidth={returnTrue}
                                 childOpacity={returnOne}
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 4155800b8..0952dda20 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -25,6 +25,7 @@ import { Colors } from '../global/globalEnums';
 import { DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
 import { DefaultStyleProvider } from '../StyleProvider';
 import './TopBar.scss';
+import { dropActionType } from '../../util/DragManager';
 
 /**
  * ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user
@@ -102,7 +103,7 @@ export class TopBar extends React.Component {
                     Document={selDoc}
                     docViewPath={returnEmptyDocViewList}
                     fieldKey="data"
-                    dropAction="embed"
+                    dropAction={dropActionType.embed}
                     styleProvider={DefaultStyleProvider}
                     select={emptyFunction}
                     isContentActive={returnTrue}
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 8eeb52709..1cacfe30c 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -96,9 +96,9 @@ export const documentSchema = createSchema({
     // drag drop properties
     _dragOnlyWithinContainer: 'boolean', // whether document can be dropped into a different collection
     dragFactory: Doc, // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
-    dropAction: 'string', // override specifying what should happen when something is dropped on this document (can be "embed", "copy", "move")
-    dragAction: 'string', // override specifying what should happen when this document s dragged (can be "embed", "copy", "move")
-    childDragAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "embed" or "copy")
+    dropAction: 'string', // override specifying what should happen when something is dropped on this document (dropActionType)
+    dragAction: 'string', // override specifying what should happen when this document s dragged (dropActionType)
+    childDragAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (dropActionType)
     dropPropertiesToRemove: listSpec('string'), // properties that should be removed from the embed/copy/etc of this document when it is dropped
 });
 
-- 
cgit v1.2.3-70-g09d2


From e973de1476194fe68a2f218e458e4619fb71c75e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 29 Feb 2024 09:34:56 -0500
Subject: fixed minimap to show at least as much as the freeformview is showing
 (was a problem for example when there was just a single document)

---
 src/client/views/collections/TabDocView.tsx                       | 8 ++++++++
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx     | 4 ++--
 2 files changed, 10 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 02aa76d82..b52f2e7b6 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -564,6 +564,12 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
         const dim = Math.max(xbounds, ybounds);
         return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
     }
+    @computed get xPadding() {
+        return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l));
+    }
+    @computed get yPadding() {
+        return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l));
+    }
     childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null);
     returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150);
     miniDown = (e: React.PointerEvent) => {
@@ -620,6 +626,8 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
                     childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
                     searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
                     fitContentsToBox={returnTrue}
+                    xPadding={this.xPadding}
+                    yPadding={this.yPadding}
                 />
                 <div className="miniOverlay" onPointerDown={this.miniDown}>
                     <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 50b3dbd70..e48656f5e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -159,8 +159,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
             : aggregateBounds(
                   this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
-                  NumCast(this.layoutDoc._xPadding, 10),
-                  NumCast(this.layoutDoc._yPadding, 10)
+                  NumCast(this.layoutDoc._xPadding, this._props.xPadding ?? 10),
+                  NumCast(this.layoutDoc._yPadding, this._props.yPadding ?? 10)
               );
     }
     @computed get nativeWidth() {
-- 
cgit v1.2.3-70-g09d2

