From 7e4d793eaa7e5b6b564355a11fa02a5611645f20 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 21 Mar 2025 18:58:21 -0400
Subject: trying to improve how data / layout / root and templtae docs are
 accessed.

---
 src/client/views/nodes/DocumentView.tsx | 36 ++++++++++++++++-----------------
 1 file changed, 18 insertions(+), 18 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cac276535..fdaf13733 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -88,7 +88,7 @@ export interface DocumentViewProps extends FieldViewSharedProps {
 @observer
 export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps & { showAIEditor: boolean }>() {
     // this makes mobx trace() statements more descriptive
-    public get displayName()  { return 'DocumentViewInternal(' + this.Document.title + ')'; } // prettier-ignore
+    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.
 
     /**
@@ -143,10 +143,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     @computed get highlighting()      { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore
     @computed get borderPath()        { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore
 
-    @computed get onClickHdlr()       { return this._props.onClickScript?.()       ?? ScriptCast(this.layoutDoc.onClick       ?? this.Document.onClick); } // prettier-ignore
-    @computed get onDoubleClickHdlr() { return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick ?? this.Document.onDoubleClick); } // prettier-ignore
-    @computed get onPointerDownHdlr() { return this._props.onPointerDownScript?.() ?? ScriptCast(this.layoutDoc.onPointerDown ?? this.Document.onPointerDown); } // prettier-ignore
-    @computed get onPointerUpHdlr()   { return this._props.onPointerUpScript?.()   ?? ScriptCast(this.layoutDoc.onPointerUp   ?? this.Document.onPointerUp); } // prettier-ignore
+    @computed get onClickHdlr()       { return this._props.onClickScript?.()       ?? ScriptCast(this.Document._onClick       ?? this.Document.onClick); } // prettier-ignore
+    @computed get onDoubleClickHdlr() { return this._props.onDoubleClickScript?.() ?? ScriptCast(this.Document._onDoubleClick ?? this.Document.onDoubleClick); } // prettier-ignore
+    @computed get onPointerDownHdlr() { return this._props.onPointerDownScript?.() ?? ScriptCast(this.Document._onPointerDown ?? this.Document.onPointerDown); } // prettier-ignore
+    @computed get onPointerUpHdlr()   { return this._props.onPointerUpScript?.()   ?? ScriptCast(this.Document._onPointerUp   ?? this.Document.onPointerUp); } // prettier-ignore
 
     @computed get disableClickScriptFunc() {
         const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
@@ -322,7 +322,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                         DocumentView.DeselectAll();
                         Doc.UnBrushDoc(this.Document);
                     } else this._singleClickFunc?.();
-                }, 'on double click: ' + this.Document.title)();
+                }, 'on double click: ' + this.Document.$title)();
                 this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
                 this._doubleClickTimeout = undefined;
                 this._singleClickFunc = undefined;
@@ -340,7 +340,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     if (this.layoutDoc.onDragStart && !(e.ctrlKey || e.button > 0)) stopPropagate = false;
                     preventDefault = false;
                 }
-                this._singleClickFunc = undoable(clickFunc ?? sendToBack ?? selectFunc, 'click: ' + this.Document.title);
+                this._singleClickFunc = undoable(clickFunc ?? sendToBack ?? selectFunc, 'click: ' + this.Document.$title);
                 const waitForDblClick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
                 if ((clickFunc && waitForDblClick !== 'never') || waitForDblClick === 'always') {
                     this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
@@ -519,7 +519,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
         if (e && !(e.nativeEvent instanceof simMouseEvent ? e.nativeEvent.dash : false)) {
             const onDisplay = () => {
-                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.
+                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')) {
@@ -545,7 +545,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     cm.addItem({ description: item.label, event: () => (item.method ? item.method() : item.script?.script.run({ this: this.Document, documentView: this, scriptContext: this._props.scriptContext })), icon: item.icon as IconProp })
             );
 
-        if (!this.Document.isFolder) {
+        if (!this.Document.$isFolder) {
             const templateDoc = Cast(this.Document[StrCast(this.Document.layout_fieldKey)], Doc, null);
             const appearance = cm.findByDescription('Appearance...');
             const appearanceItems = appearance?.subitems ?? [];
@@ -635,7 +635,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
         let documentationDescription: string | undefined;
         let documentationLink: string | undefined;
-        switch (this.Document.type) {
+        switch (this.Document.$type) {
             case DocumentType.COL:
                 documentationDescription = 'See collection documentation';
                 documentationLink = 'https://brown-dash.github.io/Dash-Documentation/views/';
@@ -722,9 +722,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     private _tagsBtnHeight = 22;
     @computed get currentScale() {
         const viewXfScale = this._props.DocumentView!().screenToLocalScale();
-        const x = NumCast(this.Document.height) / viewXfScale / 80;
+        const x = NumCast(this.Document._height) / viewXfScale / 80;
         const xscale = x >= 1 ? 0 : 1 / (1 + x * (viewXfScale - 1));
-        const y = NumCast(this.Document.width) / viewXfScale / 200;
+        const y = NumCast(this.Document._width) / viewXfScale / 200;
         const yscale = y >= 1 ? 0 : 1 / (1 + y * viewXfScale - 1);
         return Math.max(xscale, yscale, 1 / viewXfScale);
     }
@@ -735,7 +735,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     /**
      * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size.
      */
-    @computed get maxWidgetSize() { return Math.min(this._tagsBtnHeight * this.viewScaling, 0.25 * Math.min(NumCast(this.Document.width), NumCast(this.Document.height))); } // prettier-ignore
+    @computed get maxWidgetSize() { return Math.min(this._tagsBtnHeight * this.viewScaling, 0.25 * Math.min(NumCast(this.Document._width), NumCast(this.Document._height))); } // prettier-ignore
     /**
      * How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content
      */
@@ -746,7 +746,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     @computed get viewBoxContents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
-        const noBackground = this.Document.isGroup && !this._componentView?.isUnstyledView?.() && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+        const noBackground = this.Document.$isGroup && !this._componentView?.isUnstyledView?.() && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
         return (
             <>
                 <div
@@ -957,7 +957,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             : (this.docContents ?? (
                   <div
                       className="documentView-node"
-                      id={this.Document.type !== DocumentType.LINK ? this._docView?.DocUniqueId : undefined}
+                      id={this.Document.$type !== DocumentType.LINK ? this._docView?.DocUniqueId : undefined}
                       style={{
                           ...style,
                           background: this.backgroundBoxColor,
@@ -1015,7 +1015,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     borderRadius: this._componentView?.isUnstyledView?.() ? undefined : this.borderRounding,
                     pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
                 }}>
-                {this._componentView?.isUnstyledView?.() || this.Document.type === DocumentType.CONFIG || !renderDoc ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
+                {this._componentView?.isUnstyledView?.() || this.Document.$type === DocumentType.CONFIG || !renderDoc ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
                 {jsx}
             </div>
         );
@@ -1210,7 +1210,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     }
     @computed private get nativeScaling() {
         if (this.shouldNotScale) return 1;
-        const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
+        const minTextScale = this.Document.$type === DocumentType.RTF ? 0.1 : 0;
         const ai = this._showAIEditor && this.nativeWidth === this.layoutDoc.width ? 95 : 0;
         const effNW = Math.max(this.effectiveNativeWidth - ai, 1);
         const effNH = Math.max(this.effectiveNativeHeight - ai, 1);
@@ -1325,7 +1325,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     public startDragging = (x: number, y: number, dropAction: dropActionType | undefined, 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 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?.();
-- 
cgit v1.2.3-70-g09d2


From bc308b888f41e8789f1b9f522ced46e68e726862 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 21 Mar 2025 19:24:06 -0400
Subject: from last

---
 src/client/documents/DocUtils.ts        | 4 ++--
 src/client/util/CalendarManager.tsx     | 5 +++--
 src/client/util/CurrentUserUtils.ts     | 4 ++--
 src/client/views/DocComponent.tsx       | 2 +-
 src/client/views/PinFuncs.ts            | 4 ++--
 src/client/views/PropertiesButtons.tsx  | 2 +-
 src/client/views/PropertiesView.tsx     | 2 +-
 src/client/views/nodes/DocumentView.tsx | 8 ++++----
 src/fields/Doc.ts                       | 4 +---
 9 files changed, 17 insertions(+), 18 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index 13f6f6920..b0170a192 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -383,8 +383,8 @@ export namespace DocUtils {
                     const newDoc = DocUtils.copyDragFactory(dragDoc);
                     if (newDoc) {
                         newDoc._author = ClientUtils.CurrentUserEmail();
-                        newDoc._x = x;
-                        newDoc._y = y;
+                        newDoc.x = x;
+                        newDoc.y = y;
                         newDoc.$backgroundColor = Doc.UserDoc().textBackgroundColor;
                         DocumentView.SetSelectOnLoad(newDoc);
                         if (pivotField) {
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index a0db4e32e..3357e5d59 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -17,6 +17,7 @@ import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
 import './CalendarManager.scss';
 import { SnappingManager } from './SnappingManager';
 import { CalendarDate, DateValue } from '@internationalized/date';
+import { DocData } from '../../fields/DocSymbols';
 // import 'react-date-range/dist/styles.css';
 // import 'react-date-range/dist/theme/default.css';
 
@@ -118,7 +119,7 @@ export class CalendarManager extends ObservableReactComponent<object> {
     // TODO: Make undoable
     private addToCalendar = () => {
         const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
-        const targetDoc = docs[0]; // doc to add to calendar
+        const targetDoc = docs[0]?.[DocData];
 
         if (targetDoc) {
             let calendar: Doc;
@@ -229,7 +230,7 @@ export class CalendarManager extends ObservableReactComponent<object> {
     @computed
     get calendarInterface() {
         const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
-        const targetDoc = docs[0];
+        const targetDoc = docs[0]?.[DocData];
 
         return (
             <div
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 924041552..537c703b4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -325,7 +325,7 @@ export class CurrentUserUtils {
             slide.$text  = rtfield;
             slide.$layout_textPainted = `<CollectionView {...props} fieldKey={'text'}/>`;
             slide.$type_collection = CollectionViewType.Freeform;
-            slide._onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
+            slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
             return slide; 
         }
         const mermaidsApi = () => {
@@ -376,7 +376,7 @@ pie title Minerals in my tap water
             slide.$text  = rtfield;
             slide.$layout_textPainted = `<CollectionView {...props} fieldKey={'text'}/>`;
             slide.$_type_collection = CollectionViewType.Freeform;
-            slide._onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
+            slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
             return slide; 
         }
         plotlyApi(); mermaidsApi();
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 064906530..79a0cc602 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -42,7 +42,7 @@ export function DocComponent<P extends DocComponentProps>() {
          * This is the document being rendered.  It may be a template so it may or may no inherit from the data doc.
          */
         @computed get layoutDoc() {
-            return this._props.LayoutTemplateString ? this.Document : Doc.Layout(this._props.Document, this._props.LayoutTemplate?.());
+            return this._props.LayoutTemplateString ? this._props.Document : Doc.Layout(this._props.Document, this._props.LayoutTemplate?.());
         }
 
         /**
diff --git a/src/client/views/PinFuncs.ts b/src/client/views/PinFuncs.ts
index 1ab8575a8..d756830da 100644
--- a/src/client/views/PinFuncs.ts
+++ b/src/client/views/PinFuncs.ts
@@ -76,8 +76,8 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) {
             pinDoc.config_data = Field.Copy(targetDoc[fkey]);
         }
         if (pinProps.pinData.dataannos) {
-            const fieldKey = Doc.LayoutFieldKey(targetDoc);
-            pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc['$' + fieldKey + '_annotations']).filter(doc => !doc.layout_unrendered));
+            const fieldKey = '$' + Doc.LayoutFieldKey(targetDoc) + +'_annotations';
+            pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[fieldKey]).filter(doc => !doc.layout_unrendered));
         }
         if (pinProps.pinData.inkable) {
             pinDoc.config_fillColor = targetDoc.fillColor;
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 2b4fe478e..a1e8fe7ba 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -131,7 +131,7 @@ export class PropertiesButtons extends React.Component {
             on => `${on ? 'Flashcard enabled' : 'Flashcard disabled'} `,
             () => <MdTouchApp />,
             (dv, doc) => {
-                const on = !!doc.onPaint;
+                const on = !!doc.$onPaint;
                 doc.$onPaint = on ? undefined : ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, { documentView: 'any' });
                 doc.$layout_textPainted = on ? undefined : `<ComparisonBox {...props} fieldKey={'${dv?.LayoutFieldKey ?? 'text'}'}/>`;
             }
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index c72f958fc..b95553c4e 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -891,7 +891,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
             }
         });
     }
-    @computed get colorStk() { return StrCast(this.selectedStrokes.lastElement()?.$olor); } // prettier-ignore
+    @computed get colorStk() { return StrCast(this.selectedStrokes.lastElement()?.$color); } // prettier-ignore
     set colorStk(value) {
         this.selectedStrokes.forEach(doc => {
             doc.$color = value || undefined;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fdaf13733..46bd076d4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -143,10 +143,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     @computed get highlighting()      { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore
     @computed get borderPath()        { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore
 
-    @computed get onClickHdlr()       { return this._props.onClickScript?.()       ?? ScriptCast(this.Document._onClick       ?? this.Document.onClick); } // prettier-ignore
-    @computed get onDoubleClickHdlr() { return this._props.onDoubleClickScript?.() ?? ScriptCast(this.Document._onDoubleClick ?? this.Document.onDoubleClick); } // prettier-ignore
-    @computed get onPointerDownHdlr() { return this._props.onPointerDownScript?.() ?? ScriptCast(this.Document._onPointerDown ?? this.Document.onPointerDown); } // prettier-ignore
-    @computed get onPointerUpHdlr()   { return this._props.onPointerUpScript?.()   ?? ScriptCast(this.Document._onPointerUp   ?? this.Document.onPointerUp); } // prettier-ignore
+    @computed get onClickHdlr()       { return this._props.onClickScript?.()       ?? ScriptCast(this.layoutDoc.onClick       ?? this.Document.onClick); } // prettier-ignore
+    @computed get onDoubleClickHdlr() { return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick ?? this.Document.onDoubleClick); } // prettier-ignore
+    @computed get onPointerDownHdlr() { return this._props.onPointerDownScript?.() ?? ScriptCast(this.layoutDoc.onPointerDown ?? this.Document.onPointerDown); } // prettier-ignore
+    @computed get onPointerUpHdlr()   { return this._props.onPointerUpScript?.()   ?? ScriptCast(this.layoutDoc.onPointerUp   ?? this.Document.onPointerUp); } // prettier-ignore
 
     @computed get disableClickScriptFunc() {
         const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index f5bd4f44c..e2f708700 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1156,9 +1156,7 @@ export namespace Doc {
     // the document containing the view layout information - will be the Document itself unless the Document has
     // a layout field or 'layout' is given.
     export function Layout(doc: Doc, layout?: Doc): Doc {
-        const templateField = layout && StrCast(layout.isTemplateForField, Doc.LayoutFieldKey(layout)); // the field that the template renders
-        const overrideLayout = layout && Cast(doc[layout.title + '(' + templateField + ')'], Doc, null);
-        //  [`${StrCast(layout.isTemplateForField, 'data')}_layout[` + layout[Id] + ']'], Doc, null);
+        const overrideLayout = layout && Cast(doc[layout.title + '(' + StrCast(layout.isTemplateForField, 'data') + ')'], Doc, null);
         return overrideLayout || doc[DocLayout] || doc;
     }
     export function SetLayout(doc: Doc, layout: Doc | string) {
-- 
cgit v1.2.3-70-g09d2


From d0fccd1050f5d6ccc24c1e4d2b7d1c0ed94fb2a7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 23 Mar 2025 10:27:28 -0400
Subject: updated more [DocData] to .$ things

---
 src/client/util/CalendarManager.tsx                |  5 +--
 src/client/util/LinkManager.ts                     |  6 +--
 src/client/views/DocComponent.tsx                  |  9 ++--
 src/client/views/PropertiesDocContextSelector.tsx  |  2 +-
 src/client/views/PropertiesView.tsx                |  8 +---
 src/client/views/SidebarAnnos.tsx                  |  9 ++--
 src/client/views/StyleProviderQuiz.tsx             |  8 ++--
 src/client/views/TagsView.tsx                      | 33 +++++++--------
 .../views/collections/CollectionDockingView.tsx    |  6 +--
 .../collections/CollectionMasonryViewFieldRow.tsx  |  3 +-
 src/client/views/collections/CollectionMenu.tsx    |  3 +-
 .../views/collections/CollectionNoteTakingView.tsx |  2 +-
 .../collections/CollectionNoteTakingViewColumn.tsx |  2 +-
 .../collections/CollectionStackedTimeline.tsx      |  8 ++--
 .../views/collections/CollectionStackingView.tsx   |  5 +--
 src/client/views/collections/CollectionSubView.tsx | 12 +++++-
 src/client/views/collections/TabDocView.tsx        |  2 +-
 src/client/views/collections/TreeView.tsx          |  5 +--
 .../CollectionFreeFormInfoUI.tsx                   | 11 +++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 25 ++++++-----
 .../collectionFreeForm/FaceCollectionBox.tsx       |  4 +-
 .../collectionFreeForm/ImageLabelBox.tsx           | 19 ++++-----
 .../collections/collectionFreeForm/MarqueeView.tsx | 19 ++++-----
 src/client/views/global/globalScripts.ts           | 49 +++++++++-------------
 src/client/views/nodes/AudioBox.tsx                |  2 +-
 src/client/views/nodes/ComparisonBox.tsx           | 10 ++---
 .../views/nodes/DataVizBox/components/TableBox.tsx |  4 +-
 src/client/views/nodes/DiagramBox.tsx              |  5 +--
 src/client/views/nodes/DocumentView.tsx            | 14 +++----
 src/client/views/nodes/ImageBox.tsx                | 42 +++++++++----------
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/client/views/nodes/LinkBox.tsx                 |  6 +--
 src/client/views/nodes/LinkDescriptionPopup.tsx    |  3 +-
 src/client/views/nodes/PDFBox.tsx                  | 17 ++++----
 .../views/nodes/RecordingBox/RecordingBox.tsx      |  3 +-
 src/client/views/nodes/VideoBox.tsx                | 48 ++++++++++-----------
 src/client/views/nodes/calendarBox/CalendarBox.tsx |  2 +-
 .../nodes/chatbot/chatboxcomponents/ChatBox.tsx    |  6 +--
 .../views/nodes/formattedText/EquationView.tsx     |  5 +--
 src/client/views/nodes/imageEditor/ImageEditor.tsx |  9 ++--
 src/client/views/nodes/trails/PresBox.tsx          |  9 ++--
 src/client/views/smartdraw/SmartDrawHandler.tsx    | 21 +++++-----
 src/client/views/smartdraw/StickerPalette.tsx      | 28 ++++++-------
 src/fields/Doc.ts                                  | 25 ++++++-----
 src/fields/ScriptField.ts                          |  3 +-
 45 files changed, 243 insertions(+), 276 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 3357e5d59..1a5a5f889 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -17,7 +17,6 @@ import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
 import './CalendarManager.scss';
 import { SnappingManager } from './SnappingManager';
 import { CalendarDate, DateValue } from '@internationalized/date';
-import { DocData } from '../../fields/DocSymbols';
 // import 'react-date-range/dist/styles.css';
 // import 'react-date-range/dist/theme/default.css';
 
@@ -119,7 +118,7 @@ export class CalendarManager extends ObservableReactComponent<object> {
     // TODO: Make undoable
     private addToCalendar = () => {
         const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
-        const targetDoc = docs[0]?.[DocData];
+        const targetDoc = docs[0];
 
         if (targetDoc) {
             let calendar: Doc;
@@ -230,7 +229,7 @@ export class CalendarManager extends ObservableReactComponent<object> {
     @computed
     get calendarInterface() {
         const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
-        const targetDoc = docs[0]?.[DocData];
+        const targetDoc = docs[0];
 
         return (
             <div
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index d04d41968..3f98ab3c4 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -156,7 +156,7 @@ export class LinkManager {
     }
     public deleteLink(linkDoc: Doc) {
         const ret = Doc.RemoveDocFromList(Doc.LinkDBDoc(), 'data', linkDoc);
-        linkDoc[DocData].link_anchor_1 = linkDoc[DocData].link_anchor_2 = undefined;
+        linkDoc.$link_anchor_1 = linkDoc.$link_anchor_2 = undefined;
         return ret;
     }
     public deleteAllLinksOnAnchor(anchor: Doc) {
@@ -258,8 +258,8 @@ export function UPDATE_SERVER_CACHE() {
 
     // print out cached docs
     //Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = ');
-    const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc));
-    const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
+    // const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc));
+    // const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
     //Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
 
     rp.post(ClientUtils.prepend('/setCacheDocumentIds'), {
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 79a0cc602..a311f6436 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -49,7 +49,7 @@ export function DocComponent<P extends DocComponentProps>() {
          * This is the unique data repository for a dcoument that stores the intrinsic document data
          */
         @computed get dataDoc() {
-            return this.Document[DocData];
+            return this._props.Document[DocData];
         }
     }
     return Component;
@@ -93,7 +93,7 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
          * This is the unique data repository for a dcoument that stores the intrinsic document data
          */
         @computed get dataDoc() {
-            return this.Document.isTemplateForField || this.Document.isTemplateDoc ? (this._props.TemplateDataDocument ?? this.Document[DocData]) : this.Document[DocData];
+            return this._props.Document.isTemplateForField || this._props.Document.isTemplateDoc ? (this._props.TemplateDataDocument ?? this._props.Document[DocData]) : this._props.Document[DocData];
         }
 
         /**
@@ -151,7 +151,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
          * This is the unique data repository for a dcoument that stores the intrinsic document data
          */
         @computed get dataDoc() {
-            return this.Document.isTemplateForField || this.Document.isTemplateDoc ? (this._props.TemplateDataDocument ?? this.Document[DocData]) : this.Document[DocData];
+            return this._props.Document.isTemplateForField || this._props.Document.isTemplateDoc ? (this._props.TemplateDataDocument ?? this._props.Document[DocData]) : this._props.Document[DocData];
         }
 
         /**
@@ -225,8 +225,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
                 if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) {
                     added.forEach(adoc => {
                         adoc._dragOnlyWithinContainer = undefined;
-                        if (annotationKey ?? this._annotationKeySuffix()) adoc[DocData].annotationOn = this.Document;
-                        else adoc[DocData].annotationOn = undefined;
+                        adoc.$annotationOn = (annotationKey ?? this._annotationKeySuffix()) ? this.Document : undefined;
                         Doc.SetContainer(adoc, this.Document);
                         inheritParentAcls(targetDataDoc, adoc, true);
                     });
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index f494ff16a..ee6486a9c 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -26,7 +26,7 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope
 
     @computed get _docs() {
         if (!this._props.DocView) return [];
-        const target = this._props.DocView._props.Document;
+        const target = this._props.DocView.Document;
         const targetContext = this._props.DocView.containerViewPath?.().lastElement()?.Document;
         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>());
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index b95553c4e..7e9cd002b 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -181,7 +181,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @action
     docHeight = () => {
         const layoutDoc = this.selectedLayoutDoc;
-        if (layoutDoc && this.dataDoc) {
+        if (layoutDoc) {
             return Math.max(
                 70,
                 Math.min(
@@ -308,11 +308,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
 
     @computed get contextCount() {
-        if (this.selectedDocumentView) {
-            const target = this.selectedDocumentView.Document;
-            return Doc.GetEmbeddings(target).length - 1;
-        }
-        return 0;
+        return this.selectedDocumentView ? Doc.GetEmbeddings(this.selectedDocumentView.Document).length - 1 : 0;
     }
 
     @computed get links() {
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 816fc8ed3..b3f3a4478 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -4,7 +4,6 @@ import * as React from 'react';
 import { ClientUtils, returnAll, returnFalse, returnOne, returnZero } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
 import { Doc, DocListCast, Field, FieldResult, FieldType, StrListCast } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
@@ -88,9 +87,9 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
             .filter(data => data.split(':')[0])
             .filter(data => !filterExlusions?.includes(data.split(':')[0]))
             .map(data => {
-                const key = data.split(':')[0];
+                const key = '$'+data.split(':')[0];
                 const val = Field.Copy(this.allMetadata.get(key));
-                target[DocData][key] = val;
+                target[key] = val;
                 return {
                     type: 'dashField',
                     attrs: { fieldKey: key, docId: '', hideKey: false, hideValue: false, editable: true },
@@ -98,7 +97,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                 };
             });
 
-        if (!anchor.text) anchor[DocData].text = '-selection-';
+        if (!anchor.text) anchor.$text = '-selection-';
         const textLines: { type: string; attrs: object; content?: unknown[] }[] = [
             {
                 type: 'paragraph',
@@ -133,7 +132,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
         };
         if (taggedContent.length) textLines.push(metadatatext);
         if (textLines.length) {
-            target[DocData].text = new RichTextField(
+            target.$text = new RichTextField(
                 JSON.stringify({
                     doc: {
                         type: 'doc',
diff --git a/src/client/views/StyleProviderQuiz.tsx b/src/client/views/StyleProviderQuiz.tsx
index f9ee8d3db..b4a1f53c5 100644
--- a/src/client/views/StyleProviderQuiz.tsx
+++ b/src/client/views/StyleProviderQuiz.tsx
@@ -67,7 +67,7 @@ export namespace styleProviderQuiz {
             newCol.zIndex = 1000;
             newCol.forceActive = true;
             newCol.quiz = text;
-            newCol[DocData][Doc.LayoutFieldKey(newCol) + '_transform'] = 'none';
+            newCol['$' + Doc.LayoutFieldKey(newCol) + '_transform'] = 'none';
             Doc.AddDocToList(img.Document, 'quizBoxes', newCol);
             img.addDocument(newCol);
             //   img._loading = false;
@@ -248,7 +248,7 @@ export namespace styleProviderQuiz {
     function check(img: ImageBox) {
         //this._loading = true;
         imgQuizBoxes(img).forEach(async doc => {
-            const input = StrCast(doc[DocData].title);
+            const input = StrCast(doc.$title);
             if (imgQuizMode(img) == quizMode.SMART && input) {
                 const questionText = 'Question: What was labeled in this image?';
                 const rubricText = ' Rubric: ' + StrCast(doc.quiz);
@@ -275,8 +275,8 @@ export namespace styleProviderQuiz {
 
     function redo(img: ImageBox) {
         imgQuizBoxes(img).forEach(doc => {
-            doc[DocData].title = '';
-            doc.backgroundColor = '#e4e4e4';
+            doc.$title = '';
+            doc.$backgroundColor = '#e4e4e4';
         });
     }
 
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 93d6fb684..7d2a6be5e 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -72,7 +72,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
         const newTagCol = new Doc();
         newTagCol.title = tag;
         newTagCol.collections = new List<Doc>();
-        newTagCol[DocData].docs = new List<Doc>();
+        newTagCol.$docs = new List<Doc>();
         Doc.ActiveDashboard && Doc.AddDocToList(Doc.ActiveDashboard, 'myTagCollections', newTagCol);
 
         return newTagCol;
@@ -82,7 +82,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
      * @param tag tag string
      * @returns An array of documents that contain the tag.
      */
-    public static allDocsWithTag = (tag: string) => DocListCast(TagItem.findTagCollectionDoc(tag)?.[DocData].docs);
+    public static allDocsWithTag = (tag: string) => DocListCast(TagItem.findTagCollectionDoc(tag)?.$docs);
 
     public static docHasTag = (doc: Doc, tag: string) => StrListCast(doc?.tags).includes(tag);
     /**
@@ -95,11 +95,11 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
 
         // If the document is of type COLLECTION, make it a smart collection, otherwise, add the tag to the document.
         if (doc.type === DocumentType.COL && !doc.annotationOn) {
-            Doc.AddDocToList(tagCollection[DocData], 'collections', doc);
+            Doc.AddDocToList(tagCollection, 'collections', doc);
 
             // Iterate through the tag Doc collections and add a copy of the document to each collection
-            for (const cdoc of DocListCast(tagCollection[DocData].docs)) {
-                if (!DocListCast(doc[DocData].data).find(d => Doc.AreProtosEqual(d, cdoc))) {
+            for (const cdoc of DocListCast(tagCollection.$docs)) {
+                if (!DocListCast(doc.$data).find(d => Doc.AreProtosEqual(d, cdoc))) {
                     const newEmbedding = Doc.MakeEmbedding(cdoc);
                     Doc.AddDocToList(doc[DocData], 'data', newEmbedding);
                     Doc.SetContainer(newEmbedding, doc);
@@ -111,7 +111,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
 
             // Iterate through the tag document's collections and add a copy of the document to each collection
             for (const collection of DocListCast(tagCollection.collections)) {
-                if (!DocListCast(collection[DocData].data).find(d => Doc.AreProtosEqual(d, doc))) {
+                if (!DocListCast(collection.$data).find(d => Doc.AreProtosEqual(d, doc))) {
                     const newEmbedding = Doc.MakeEmbedding(doc);
                     Doc.AddDocToList(collection[DocData], 'data', newEmbedding);
                     Doc.SetContainer(newEmbedding, collection);
@@ -119,8 +119,8 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
             }
         }
 
-        if (!doc[DocData].tags) doc[DocData].tags = new List<string>();
-        const tagList = doc[DocData].tags as List<string>;
+        if (!doc.$tags) doc.$tags = new List<string>();
+        const tagList = doc.$tags as List<string>;
         if (!tagList.includes(tag)) tagList.push(tag);
     };
 
@@ -131,22 +131,22 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
      * @param tagDoc doc that collections the Docs with the tag
      */
     public static removeTagFromDoc = (doc: Doc, tag: string, tagDoc?: Doc) => {
-        if (doc[DocData].tags) {
+        if (doc.$tags) {
             if (doc.type === DocumentType.COL) {
                 tagDoc && Doc.RemoveDocFromList(tagDoc[DocData], 'collections', doc);
 
                 for (const cur_doc of TagItem.allDocsWithTag(tag)) {
-                    doc[DocData].data = new List<Doc>(DocListCast(doc[DocData].data).filter(d => !Doc.AreProtosEqual(cur_doc, d)));
+                    doc.$data = new List<Doc>(DocListCast(doc.$data).filter(d => !Doc.AreProtosEqual(cur_doc, d)));
                 }
             } else {
                 tagDoc && Doc.RemoveDocFromList(tagDoc[DocData], 'docs', doc);
 
                 for (const collection of DocListCast(tagDoc?.collections)) {
-                    collection[DocData].data = new List<Doc>(DocListCast(collection[DocData].data).filter(d => !Doc.AreProtosEqual(doc, d)));
+                    collection.$data = new List<Doc>(DocListCast(collection.$data).filter(d => !Doc.AreProtosEqual(doc, d)));
                 }
             }
         }
-        doc[DocData].tags = new List<string>(StrListCast(doc[DocData].tags).filter(label => label !== tag));
+        doc.$tags = new List<string>(StrListCast(doc.$tags).filter(label => label !== tag));
     };
 
     private _ref: React.RefObject<HTMLDivElement>;
@@ -171,11 +171,10 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
 
         // Create a new collection and set up configurations.
         const newCollection = ((doc: Doc) => {
-            const docData = doc[DocData];
-            docData.data = new List<Doc>(newEmbeddings);
-            docData.title = this._props.tag;
-            docData.tags = new List<string>([this._props.tag]);
-            docData.freeform_fitContentsToBox = true;
+            doc.$data = new List<Doc>(newEmbeddings);
+            doc.$title = this._props.tag;
+            doc.$tags = new List<string>([this._props.tag]);
+            doc.$freeform_fitContentsToBox = true;
             doc._freeform_panX = doc._freeform_panY = 0;
             doc._width = 900;
             doc._height = 900;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index a45b37f43..12a131deb 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -5,7 +5,7 @@ import * as ReactDOM from 'react-dom/client';
 import ResizeObserver from 'resize-observer-polyfill';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, incrementTitleCopy, returnTrue, UpdateIcon } from '../../../ClientUtils';
 import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
-import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols';
+import { AclAdmin, AclEdit } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -481,7 +481,7 @@ export class CollectionDockingView extends CollectionSubView() {
             Array.from(cloned.map.entries()).forEach(entry => {
                 json = json.replace(entry[0], entry[1][Id]);
             });
-            cloned.clone[DocData].dockingConfig = json;
+            cloned.clone.$dockingConfig = json;
             return DashboardView.openDashboard(cloned.clone);
         }
         const matches = json.match(/"documentId":"[a-z0-9-]+"/g);
@@ -495,7 +495,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) {
-                newtab[DocData].data = new List<Doc>(newtabdocs);
+                newtab.$data = new List<Doc>(newtabdocs);
                 newtabdocs.forEach(ntab => Doc.SetContainer(ntab, newtab));
             }
             json = json.replace(origtab[Id], newtab[Id]);
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 996626575..4fe73895e 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -5,7 +5,6 @@ import * as React from 'react';
 import { returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
 import { emptyFunction, numberRange } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
 import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
 import { Docs } from '../../documents/Documents';
@@ -164,7 +163,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
         const { pivotField } = this._props;
         const newDoc = Docs.Create.TextDocument(value, { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
         DocumentView.SetSelectOnLoad(newDoc);
-        pivotField && (newDoc[DocData][pivotField] = this.getValue(this._props.heading));
+        pivotField && (newDoc['$' + pivotField] = this.getValue(this._props.heading));
         const docs = this._props.parent.childDocList;
         return docs ? !!docs.splice(0, 0, newDoc) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
     };
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index de999c91a..26aca458c 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -9,7 +9,6 @@ import * as React from 'react';
 import { ClientUtils, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
 import { Doc, DocListCast, Opt, returnEmptyDoclist } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { RichTextField } from '../../../fields/RichTextField';
@@ -213,7 +212,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         title: 'set content',
         script: 'getProto(this.target).data = copyField(this.source);',
         immediate: undoable((source: Doc[]) => {
-            this.target[DocData].data = new List<Doc>(source);
+            this.target.$data = new List<Doc>(source);
         }, ''),
         initialize: emptyFunction,
     };
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index c499bd288..4650727eb 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -441,7 +441,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);
-            newDoc[DocData].text = undefined;
+            newDoc.$text = undefined;
             DocumentView.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 40b3f9ef2..df9b5a1eb 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -157,7 +157,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
     deleteColumn = () => {
         const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
         if (this._props.headingObject) {
-            //          this._props.docList.forEach(d => (d[DocData][this._props.pivotField] = undefined));
+            //          this._props.docList.forEach(d => (d['$'+this._props.pivotField] = undefined));
             colHdrData.splice(colHdrData.indexOf(this._props.headingObject), 1);
             this._props.resizeColumns(colHdrData);
         }
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index a7a9f2114..0836cce38 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -432,8 +432,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 _isTimelineLabel: true,
                 layout_borderRounding: anchorEndTime === undefined ? '100%' : undefined,
             });
-        anchor[DocData][startTag] = anchorStartTime;
-        anchor[DocData][endTag] = anchorEndTime;
+        anchor['$' + startTag] = anchorStartTime;
+        anchor['$' + endTag] = anchorEndTime;
         if (addAsAnnotation) {
             if (Cast(dataDoc[fieldKey], listSpec(Doc), null)) {
                 Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor);
@@ -804,9 +804,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
     };
 
     resetTitle = () => {
-        this._props.mark[DocData].title = ComputedField.MakeFunction(
-            `["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"]`
-        );
+        this._props.mark.$title = ComputedField.MakeFunction(`["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"]`);
     };
     // context menu
     contextMenuItems = () => {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 392d85af3..9972fe03d 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -5,7 +5,6 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ClientUtils, DivHeight, returnNone, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils';
 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';
@@ -316,11 +315,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             const layoutFieldKey = StrCast(fieldProps.fieldKey);
             const newDoc = Doc.MakeCopy(fieldProps.Document, true);
             const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
-            newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
+            newDoc['$' + Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
             if (layoutFieldKey !== 'layout' && fieldProps.Document[layoutFieldKey] instanceof Doc) {
                 newDoc[layoutFieldKey] = fieldProps.Document[layoutFieldKey];
             }
-            newDoc[DocData].text = undefined;
+            newDoc.$text = undefined;
             DocumentView.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 655894e40..29caead5b 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -9,7 +9,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
+import { BoolCast, Cast, DateCast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
@@ -85,6 +85,9 @@ export function CollectionSubView<X>() {
         constructor(props: X & SubCollectionViewProps) {
             super(props);
             makeObservable(this);
+            console.log(`propsTitle: ${this._props.Document.title} DocTitle: ${this.Document.title} LayoutTitle:${this.layoutDoc.title} DataTitle:${this.dataDoc.title}`);
+            console.log(`tempTitle: ${this._props.TemplateDataDocument?.title} LayouTResolve: ${DocCast(this.layoutDoc.resolvedDataDoc)?.title} propDocResolve: ${DocCast(this._props.Document.resolvedDataDoc)?.title}`);
+            console.log('Children:', this.childDocs, this.childLayoutPairs);
         }
 
         @observable _focusFilters: Opt<string[]> = undefined; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it
@@ -108,7 +111,12 @@ export function CollectionSubView<X>() {
         }
 
         get dataDoc() {
-            return this._props.TemplateDataDocument instanceof Doc && this.Document.isTemplateForField ? Doc.GetProto(this._props.TemplateDataDocument) : this.Document.resolvedDataDoc ? this.Document : this.Document[DocData]; // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
+            console.log(this._props.Document.title + ' isTemplate: ' + this.layoutDoc.isTemplateForField);
+            return this._props.TemplateDataDocument instanceof Doc && this.layoutDoc.isTemplateForField //
+                ? this._props.TemplateDataDocument[DocData]
+                : this.layoutDoc.resolvedDataDoc
+                  ? this._props.Document
+                  : this.Document[DocData]; // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
         }
 
         get childContainerViewPath() {
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index cc56a8ff9..2d2c0fce5 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -347,7 +347,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                 undoable(() => {
                     const target = e.currentTarget as unknown as { value: string };
                     titleEle.size = target?.value.length + 3;
-                    doc[DocData].title = target?.value ?? '';
+                    doc.$title = target?.value ?? '';
                 }, 'edit tab title')();
             };
 
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 6208905fe..d6a19e88e 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -372,9 +372,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             _width: 1000,
             _height: 10,
         });
-        const bulletData = bullet[DocData];
-        bulletData.title = ComputedField.MakeFunction('this.text?.Text');
-        bulletData.data = new List<Doc>([]);
+        bullet.$title = ComputedField.MakeFunction('this.text?.Text');
+        bullet.$data = new List<Doc>([]);
         DocumentView.addViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
 
         return bullet;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 8b9a3e0ec..35c6d30fe 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -1,16 +1,15 @@
 import { makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, FieldType, FieldResult } from '../../../../fields/Doc';
+import { Doc, DocListCast, FieldResult, FieldType } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
 import { StrCast } from '../../../../fields/Types';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
 import { TopBar } from '../../topbar/TopBar';
 import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState';
-import './CollectionFreeFormView.scss';
-import { DocData } from '../../../../fields/DocSymbols';
 import { CollectionFreeFormView } from './CollectionFreeFormView';
+import './CollectionFreeFormView.scss';
 
 export interface CollectionFreeFormInfoUIProps {
     Document: Doc;
@@ -41,7 +40,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
     set currState(val) { runInAction(() => {this._currState = val;}); } // prettier-ignore
 
     componentWillUnmount(): void {
-        this._props.Document[DocData].backgroundColor = this._originalbackground;
+        this._props.Document.$backgroundColor = this._originalbackground;
     }
 
     setCurrState = (state: infoState) => {
@@ -52,9 +51,9 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
     };
 
     setupStates = () => {
-        this._originalbackground = StrCast(this._props.Document[DocData].backgroundColor);
+        this._originalbackground = StrCast(this._props.Document.$backgroundColor);
         //  state entry functions
-        // const setBackground = (colour: string) => () => {this._props.Document[DocData].backgroundColor = colour;} // prettier-ignore
+        // const setBackground = (colour: string) => () => {this._props.Document.$backgroundColor = colour;} // prettier-ignore
         // const setOpacity = (opacity: number) => () => {this._props.LayoutDoc.opacity = opacity;} // prettier-ignore
         //  arc transition trigger conditions
         const firstDoc = () => (this._props.childDocs().length ? this._props.childDocs()[0] : undefined);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 89aa53c35..6030e146c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -561,7 +561,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const { points } = ge;
                 const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
                 const inkDoc = this.createInkDoc(points, B);
-                if (Doc.ActiveInk === InkInkTool.Highlight) inkDoc[DocData].backgroundColor = 'transparent';
+                if (Doc.ActiveInk === InkInkTool.Highlight) inkDoc.$backgroundColor = 'transparent';
                 if (Doc.ActiveInk === InkInkTool.Write) {
                     this.unprocessedDocs.push(inkDoc);
                     CollectionFreeFormView.collectionsWithUnprocessedInk.add(this);
@@ -639,7 +639,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         const B = this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height);
                         const inkDoc = this.createInkDoc(points, B);
                         ['color', 'fillColor', 'stroke_width', 'stroke_dash', 'stroke_bezier'].forEach(field => {
-                            inkDoc[DocData][field] = stroke.dataDoc[field];
+                            inkDoc['$' + field] = stroke.dataDoc[field];
                         });
                         this.addDocument(inkDoc);
                     });
@@ -1260,15 +1260,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
      * Adds the created drawing to the freeform canvas and sets the metadata.
      */
     addDrawing = (doc: Doc, opts: DrawingOptions, gptRes: string, x?: number, y?: number) => {
-        const docData = doc[DocData];
-        docData.title = opts.text;
-        docData._width = opts.size;
-        docData.ai_drawing_input = opts.text;
-        docData.ai_drawing_complexity = opts.complexity;
-        docData.ai_drawing_colored = opts.autoColor;
-        docData.ai_drawing_size = opts.size;
-        docData.ai_drawing_data = gptRes;
-        docData.ai = 'gpt';
+        doc.$title = opts.text;
+        doc.$width = opts.size;
+        doc.$ai_drawing_input = opts.text;
+        doc.$ai_drawing_complexity = opts.complexity;
+        doc.$ai_drawing_colored = opts.autoColor;
+        doc.$ai_drawing_size = opts.size;
+        doc.$ai_drawing_data = gptRes;
+        doc.$ai = 'gpt';
         this._drawingContainer = doc;
         if (x !== undefined && y !== undefined) {
             [doc.x, doc.y] = this.screenToFreeformContentsXf.transformPoint(x, y);
@@ -1493,7 +1492,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     createTextDocCopy = undoable((fieldProps: FieldViewProps, below: boolean) => {
         const textDoc = DocCast(fieldProps.Document.rootDocument, fieldProps.Document);
         const newDoc = Doc.MakeCopy(textDoc, true);
-        newDoc[DocData][Doc.LayoutFieldKey(newDoc, fieldProps.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
+        newDoc['$' + Doc.LayoutFieldKey(newDoc, fieldProps.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
         newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
         newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
         DocumentView.SetSelectOnLoad(newDoc);
@@ -1686,7 +1685,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
      * since rendering a large collection of documents can be slow, at startup, docs are rendered in batches.
      * each doc's render() method will call the cutoff provider which will let the doc know if it should render itself yet, or wait
      */
-    renderCutoffProvider = computedFn((doc: Doc) => (this.Document.isTemplateDoc || this.Document.isTemplateForField ? false : !this._renderCutoffData.get(doc[Id] + '')));
+    renderCutoffProvider = computedFn((doc: Doc) => (this._props.Document.isTemplateDoc || this._props.Document.isTemplateForField ? false : !this._renderCutoffData.get(doc[Id] + '')));
 
     doEngineLayout(
         poolData: Map<string, PoolData>,
diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
index b9f8b13a7..b40189d76 100644
--- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
@@ -108,7 +108,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     if (faceAnno) {
                         faceAnno.face && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(faceAnno, DocCast(faceAnno.face));
                         FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, this.Document);
-                        faceAnno[DocData].face = this.Document[DocData];
+                        faceAnno.$face = this.Document[DocData];
                     }
                 }
             });
@@ -118,7 +118,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 const imgDoc = faceAnno;
                 faceAnno.face && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(imgDoc, DocCast(faceAnno.face));
                 FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, this.Document);
-                faceAnno[DocData].face = this.Document[DocData];
+                faceAnno.$face = this.Document[DocData];
             });
         e.stopPropagation();
         return true;
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
index a3d9641da..c983d7c26 100644
--- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
@@ -9,7 +9,6 @@ import React from 'react';
 import { imageUrlToBase64 } from '../../../../ClientUtils';
 import { Utils, numberRange } from '../../../../Utils';
 import { Doc, NumListCast, Opt } from '../../../../fields/Doc';
-import { DocData } from '../../../../fields/DocSymbols';
 import { List } from '../../../../fields/List';
 import { ImageCast } from '../../../../fields/Types';
 import { gptGetEmbedding, gptImageLabel } from '../../../apis/gpt/GPT';
@@ -165,7 +164,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
         // Converts the images into a Base64 format, afterwhich the information is sent to GPT to label them.
 
         const imageInfos = this._selectedImages.map(async doc => {
-            if (!doc[DocData].tags_chat) {
+            if (!doc.$tags_chat) {
                 const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.');
                 return imageUrlToBase64(`${name}_o.${type}`).then(hrefBase64 =>
                 !hrefBase64 ? undefined : 
@@ -176,7 +175,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
         (await Promise.all(imageInfos)).forEach(imageInfo => {
             if (imageInfo) {
-                imageInfo.doc[DocData].tags_chat = (imageInfo.doc[DocData].tags_chat as List<string>) ?? new List<string>();
+                imageInfo.doc.$tags_chat = (imageInfo.doc.$tags_chat as List<string>) ?? new List<string>();
 
                 const labels = imageInfo.labels.split('\n');
                 labels.forEach(label => {
@@ -186,7 +185,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             .replace(/^\d+\.\s*|-|f\*/, '')
                             .replace(/^#/, '')
                             .trim();
-                    (imageInfo.doc[DocData].tags_chat as List<string>).push(hashLabel);
+                    (imageInfo.doc.$tags_chat as List<string>).push(hashLabel);
                 });
             }
         });
@@ -201,10 +200,10 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.startLoading();
 
         for (const doc of this._selectedImages) {
-            for (let index = 0; index < (doc[DocData].tags_chat as List<string>).length; index++) {
-                const label = (doc[DocData].tags_chat as List<string>)[index];
+            for (let index = 0; index < (doc.$tags_chat as List<string>).length; index++) {
+                const label = (doc.$tags_chat as List<string>)[index];
                 const embedding = await gptGetEmbedding(label);
-                doc[DocData][`tags_embedding_${index + 1}`] = new List<number>(embedding);
+                doc[`$tags_embedding_${index + 1}`] = new List<number>(embedding);
             }
         }
 
@@ -215,13 +214,13 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
         // For each image, loop through the labels, and calculate similarity. Associate it with the
         // most similar one.
         this._selectedImages.forEach(doc => {
-            const embedLists = numberRange((doc[DocData].tags_chat as List<string>).length).map(n => Array.from(NumListCast(doc[DocData][`tags_embedding_${n + 1}`])));
+            const embedLists = numberRange((doc.$tags_chat as List<string>).length).map(n => Array.from(NumListCast(doc[`$tags_embedding_${n + 1}`])));
             const bestEmbedScore = (embedding: Opt<number[]>) => Math.max(...embedLists.map(l => (embedding && similarity(Array.from(embedding), l)!) || 0));
             const {label: mostSimilarLabelCollect} = 
                 this._labelGroups.map(label => ({ label, similarityScore: bestEmbedScore(labelToEmbedding.get(label)) }))
                            .reduce((prev, cur) => cur.similarityScore < 0.3 || cur.similarityScore <= prev.similarityScore ? prev: cur,
                                    { label: '', similarityScore: 0, }); // prettier-ignore
-            doc[DocData].data_label = mostSimilarLabelCollect; // The label most similar to the image's contents.
+            doc.$data_label = mostSimilarLabelCollect; // The label most similar to the image's contents.
         });
 
         this.endLoading();
@@ -322,7 +321,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                             await DocumentView.showDocument(doc, { willZoomCentered: true });
                                         }}></img>
                                     <div className="image-information-labels" onClick={() => this._props.addDocTab(doc, OpenWhere.addRightKeyvalue)}>
-                                        {(doc[DocData].tags_chat as List<string>).map(label => {
+                                        {(doc.$tags_chat as List<string>).map(label => {
                                             return (
                                                 <div key={Utils.GenerateGuid()} className="image-label" style={{ backgroundColor: SettingsManager.userVariantColor, borderColor: SettingsManager.userColor }}>
                                                     {label}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ad52db496..e3f4c6605 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -165,7 +165,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
                     error && console.log(error);
                     data &&
                         ClientUtils.convertDataUri(data, this._props.Document[Id] + '_icon_' + new Date().getTime()).then(returnedfilename => {
-                            this._props.Document[DocData].icon = new ImageField(returnedfilename);
+                            this._props.Document.$icon = new ImageField(returnedfilename);
                         });
                 })
             );
@@ -371,10 +371,9 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         const newCollection = creator
             ? creator(selected, { title: 'nested stack' })
             : ((doc: Doc) => {
-                  const docData = doc[DocData];
-                  docData.data = new List<Doc>(selected);
-                  docData.isGroup = makeGroup;
-                  docData.title = makeGroup ? 'grouping' : 'nested freeform';
+                  doc.$data = new List<Doc>(selected);
+                  doc.$isGroup = makeGroup;
+                  doc.$title = makeGroup ? 'grouping' : 'nested freeform';
                   doc._freeform_panX = doc._freeform_panY = 0;
                   return doc;
               })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
@@ -462,9 +461,9 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         const newColDim = 900;
         for (const label of labelGroups) {
             const newCollection = MarqueeView.getCollection([], undefined, false, this.Bounds);
-            newCollection[DocData].title = label + ' Collection';
-            newCollection._x = this.Bounds.left + x_offset;
-            newCollection._y = this.Bounds.top + y_offset;
+            newCollection.$title = label + ' Collection';
+            newCollection.x = this.Bounds.left + x_offset;
+            newCollection.y = this.Bounds.top + y_offset;
             newCollection._width = newColDim;
             newCollection._height = newColDim;
             newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2;
@@ -481,8 +480,8 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         }
 
         for (const doc of selectedImages) {
-            if (doc[DocData].data_label) {
-                Doc.AddDocToList(labelToCollection.get(doc[DocData].data_label as string)!, undefined, doc);
+            if (doc.$data_label) {
+                Doc.AddDocToList(labelToCollection.get(doc.$data_label as string)!, undefined, doc);
                 this._props.removeDocument?.(doc);
             }
         }
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 835c28daa..79d0ee88f 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,15 +1,12 @@
 /* eslint-disable @typescript-eslint/no-unused-vars */
 import { Colors } from '@dash/components';
 import { runInAction } from 'mobx';
-import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
+import { Doc, Opt, StrListCast } from '../../../fields/Doc';
 import { InkEraserTool, InkInkTool, InkProperty, InkTool } from '../../../fields/InkField';
-import { List } from '../../../fields/List';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { Gestures } from '../../../pen-gestures/GestureTypes';
-import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { Docs } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -17,30 +14,30 @@ import { UndoManager, undoable } from '../../util/UndoManager';
 import { GestureOverlay } from '../GestureOverlay';
 import { InkTranscription } from '../InkTranscription';
 import { PropertiesView } from '../PropertiesView';
+import { docSortings } from '../collections/CollectionSubView';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
 import {
     ActiveEraserWidth,
-    ActiveInkFillColor,
-    ActiveInkColor,
     ActiveHideTextLabels,
+    ActiveInkColor,
+    ActiveInkFillColor,
     ActiveInkWidth,
     ActiveIsInkMask,
     DocumentView,
-    SetActiveInkFillColor,
     SetActiveInkColor,
-    SetactiveHideTextLabels,
+    SetActiveInkFillColor,
     SetActiveInkWidth,
     SetActiveIsInkMask,
     SetEraserWidth,
+    SetactiveHideTextLabels,
 } from '../nodes/DocumentView';
 import { ImageBox } from '../nodes/ImageBox';
+import { OpenWhere } from '../nodes/OpenWhere';
 import { VideoBox } from '../nodes/VideoBox';
 import { WebBox } from '../nodes/WebBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
-import { OpenWhere } from '../nodes/OpenWhere';
-import { docSortings } from '../collections/CollectionSubView';
 
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function IsNoneSelected() {
@@ -101,10 +98,7 @@ ScriptingGlobals.add(function setBorderColor(color?: string, checkResult?: boole
             return (selected.lastElement() ?? Doc.UserDoc()).borderColor ?? defaultBorder();
         }
         if (!selected.length) setDefaultBorder(color ?? 'transparent');
-        else
-            selected.forEach(doc => {
-                doc[DocData].borderColor = color;
-            });
+        else selected.forEach(doc => (doc.$borderColor = color));
     }
     return '';
 });
@@ -114,7 +108,7 @@ ScriptingGlobals.add(function setBorderColor(color?: string, checkResult?: boole
 ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
     const selectedViews = DocumentView.Selected();
     const selectedDoc = selectedViews.lastElement()?.Document;
-    const defaultFill = selectedDoc?._layout_isSvg ? () => StrCast(selectedDoc[DocData].fillColor) : !Doc.ActiveTool || Doc.ActiveTool === InkTool.None ? () => StrCast(Doc.UserDoc().textBackgroundColor, 'transparent') : () => ActiveInkFillColor();
+    const defaultFill = selectedDoc?._layout_isSvg ? () => StrCast(selectedDoc.$fillColor) : !Doc.ActiveTool || Doc.ActiveTool === InkTool.None ? () => StrCast(Doc.UserDoc().textBackgroundColor, 'transparent') : () => ActiveInkFillColor();
     const setDefaultFill = !Doc.ActiveTool || Doc.ActiveTool === InkTool.None  ? (c: string) => { Doc.UserDoc().textBackgroundColor = c; }: SetActiveInkFillColor; // prettier-ignore
     if (Doc.ActiveTool !== InkTool.None && !selectedViews.lastElement()?.Document._layout_isSvg) {
         if (checkResult) return defaultFill();
@@ -149,10 +143,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
             return selected.lastElement()?._backgroundColor ?? defaultFill();
         }
         if (!selected.length) setDefaultFill(color ?? 'transparent');
-        else
-            selected.forEach(doc => {
-                doc[DocData][doc._layout_isSvg ? 'fillColor' : 'backgroundColor'] = color;
-            });
+        else selected.forEach(doc => (doc[doc._layout_isSvg ? '$fillColor' : '$backgroundColor'] = color));
     }
     return '';
 });
@@ -170,7 +161,7 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
     }
     if (DocumentView.Selected().length) {
         DocumentView.SelectedDocs().forEach(doc => {
-            doc[DocData].layout_headingColor = color === 'transparent' ? undefined : color;
+            doc.$layout_headingColor = color === 'transparent' ? undefined : color;
             doc.layout_showTitle = color === 'transparent' ? undefined : StrCast(doc.layout_showTitle, 'title');
         });
     } else {
@@ -470,23 +461,23 @@ ScriptingGlobals.add(function setInkProperty(option: InkProperty, value: string
     // prettier-ignore
     const map: Map<InkProperty, { checkResult: () => number|boolean|string|undefined; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
         [InkProperty.Mask, {
-            checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())),
-            setInk: (doc: Doc) => { doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask; },
+            checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected.$stroke_isInkMask) : ActiveIsInkMask())),
+            setInk: (doc: Doc) => { doc.$stroke_isInkMask = !doc.stroke_isInkMask; },
             setMode: () => SetActiveIsInkMask(value ? true : false)
         }],
         [InkProperty.Labels, {
-            checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_showLabel) : !ActiveHideTextLabels())),
-            setInk: (doc: Doc) => { doc[DocData].stroke_showLabel = value; },
+            checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected.$stroke_showLabel) : !ActiveHideTextLabels())),
+            setInk: (doc: Doc) => { doc.$stroke_showLabel = value; },
             setMode: () => SetactiveHideTextLabels(value? false : true),
         }],
         [ InkProperty.StrokeWidth, {
-            checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width, 1) : ActiveInkWidth()),
-            setInk: (doc: Doc) => { doc[DocData].stroke_width = NumCast(value); },
+            checkResult: () => (selected?._layout_isSvg ? NumCast(selected.$stroke_width, 1) : ActiveInkWidth()),
+            setInk: (doc: Doc) => { doc.$stroke_width = NumCast(value); },
             setMode: () => SetActiveInkWidth(value.toString()),
         }],
         [InkProperty.StrokeColor, {
-            checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()),
-            setInk: (doc: Doc) => { doc[DocData].color = String(value); },
+            checkResult: () => (selected?._layout_isSvg? StrCast(selected.$color) : ActiveInkColor()),
+            setInk: (doc: Doc) => { doc.$color = String(value); },
             setMode: () => SetActiveInkColor(StrCast(value))
         }],
         [ InkProperty.EraserWidth, {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 25e76e2a6..e0d59cc9d 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -296,7 +296,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 'Content-Type': 'application/json',
             },
         });
-        this.Document[DocData].phoneticTranscription = response.data['transcription'];
+        this.Document.$phoneticTranscription = response.data['transcription'];
     };
 
     // context menu
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index c0c6db4d3..992fbba66 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -80,8 +80,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             const front = Docs.Create.CenteredTextCreator('question', question, {}, img);
             const back = Docs.Create.CenteredTextCreator('answer', answer, {});
             if (useDoc) {
-                useDoc[DocData][frontKey] = front;
-                useDoc[DocData][backKey] = back;
+                useDoc['$' + frontKey] = front;
+                useDoc['$' + backKey] = back;
                 return useDoc;
             }
             return Docs.Create.FlashcardDocument(title, front, back, { _width: 300, _height: 300 });
@@ -475,7 +475,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         axios
             .post(
                 'http://localhost:105/recognize/', //
-                { file: DocCast(this.Document.audio)[DocData].url },
+                { file: DocCast(this.Document.audio).$url },
                 { headers: { 'Content-Type': 'application/json' } }
             )
             .then(response => {
@@ -520,7 +520,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                   action(resp => {
                       switch (resp && callType) {
                           case GPTCallType.CHATCARD:
-                              DocCast(this.dataDoc[this.backKey])[DocData].text = resp;
+                              DocCast(this.dataDoc[this.backKey]).$text = resp;
                               break;
                           case GPTCallType.QUIZDOC:
                               this._renderSide = this.backKey;
@@ -618,7 +618,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             const hrefBase64 = await imageUrlToBase64(u);
             const response = await gptImageLabel(hrefBase64, 'Answer the following question as a short flashcard response. Do not include a label.' + (this.dataDoc.text as RichTextField)?.Text);
 
-            DocCast(this.dataDoc[this.backKey])[DocData].text = response;
+            DocCast(this.dataDoc[this.backKey]).$text = response;
         } catch (error) {
             console.log('Error', error);
         }
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 7ef4bca6b..b31a0ec1e 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -160,8 +160,8 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                     DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
                         dragComplete: completeEv => {
                             if (!completeEv.aborted && completeEv.annoDragData && completeEv.annoDragData.linkSourceDoc && completeEv.annoDragData.dropDocument && completeEv.linkDocument) {
-                                completeEv.linkDocument[DocData].link_matchEmbeddings = true;
-                                completeEv.linkDocument[DocData].stroke_startMarker = true;
+                                completeEv.linkDocument.$link_matchEmbeddings = true;
+                                completeEv.linkDocument.$stroke_startMarker = true;
                                 this._props.docView?.()?._props.addDocument?.(completeEv.linkDocument);
                             }
                         },
diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx
index a49c69be3..3b666bad5 100644
--- a/src/client/views/nodes/DiagramBox.tsx
+++ b/src/client/views/nodes/DiagramBox.tsx
@@ -3,7 +3,6 @@ import { action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
 import { RichTextField } from '../../../fields/RichTextField';
 import { Cast, DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
 import { Gestures } from '../../../pen-gestures/GestureTypes';
@@ -46,7 +45,7 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable _errorMessage = '';
 
     @computed get mermaidcode() {
-        return StrCast(this.Document[DocData].text, RTFCast(this.Document[DocData].text)?.Text);
+        return StrCast(this.Document.$text, RTFCast(this.Document.$text)?.Text);
     }
 
     componentDidMount() {
@@ -89,7 +88,7 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     setMermaidCode = undoable((res: string) => {
-        this.Document[DocData].text = new RichTextField(
+        this.Document.$text = new RichTextField(
             JSON.stringify({
                 doc: {
                     type: 'doc',
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 46bd076d4..e9353b001 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -9,7 +9,7 @@ import { Fade, JackInTheBox } from 'react-awesome-reveal';
 import { ClientUtils, DivWidth, isTargetChildOf as isParentOf, lightOrDark, returnFalse, returnVal, simMouseEvent, simulateMouseClick } from '../../../ClientUtils';
 import { Utils, emptyFunction } from '../../../Utils';
 import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
-import { AclAdmin, AclEdit, AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
+import { AclAdmin, AclEdit, AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -40,6 +40,7 @@ import { FieldsDropdown } from '../FieldsDropdown';
 import { ObserverJsxParser } from '../ObservableReactComponent';
 import { PinProps } from '../PinFuncs';
 import { StyleProp } from '../StyleProp';
+import { TagsView } from '../TagsView';
 import { ViewBoxInterface } from '../ViewBoxInterface';
 import { GroupActive } from './CollectionFreeFormDocumentView';
 import { DocumentContentsView } from './DocumentContentsView';
@@ -52,7 +53,6 @@ import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { PresEffect, PresEffectDirection } from './trails/PresEnums';
 import SpringAnimation from './trails/SlideEffect';
 import { SpringType, springMappings } from './trails/SpringUtils';
-import { TagsView } from '../TagsView';
 
 export interface DocumentViewProps extends FieldViewSharedProps {
     hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
@@ -426,7 +426,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
     noOnClick = undoable(() => {
         this.Document.ignoreClick = false;
-        this.Document.onClick = this.Document[DocData].onClick = undefined;
+        this.Document.onClick = this.Document.$onClick = undefined;
     }, 'default on click');
 
     deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
@@ -758,7 +758,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     }}>
                     <DocumentContentsView
                         {...this._props}
-                        layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
+                        layoutFieldKey={StrCast(this._props.Document.layout_fieldKey, 'layout')}
                         pointerEvents={this.contentPointerEvents}
                         setContentViewBox={this.setContentView}
                         childFilters={this.childFilters}
@@ -1706,7 +1706,7 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff
 
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
-    const collectedLinks = DocListCast(linkCollection[DocData].data);
+    const collectedLinks = DocListCast(linkCollection.$data);
     let wid = NumCast(linkSource._width);
     let embedding: Doc | undefined;
     const links = Doc.Links(linkSource);
@@ -1729,7 +1729,7 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
 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);
+    const collectionDocs = DocListCast(collection.$data).concat(collection);
     let wid = 100;
     let created = false;
     const matchedDocs = matchedTags
@@ -1749,6 +1749,6 @@ ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
             return aset;
         }, new Set<Doc>());
 
-    created && (collection[DocData].data = new List<Doc>(Array.from(matchedDocs)));
+    created && (collection.$data = new List<Doc>(Array.from(matchedDocs)));
     return true;
 });
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5b06e9fc5..5d9718760 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -284,10 +284,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     crop = (region: Doc | undefined, addCrop?: boolean) => {
         if (!region) return undefined;
         const cropping = Doc.MakeCopy(region, true);
-        const regionData = region[DocData];
-        regionData.lockedPosition = true;
-        regionData.title = 'region:' + this.Document.title;
-        regionData.followLinkToggle = true;
+        region.$lockedPosition = true;
+        region.$title = 'region:' + this.Document.title;
+        region.$followLinkToggle = true;
         this.addDocument(region);
         const anchx = NumCast(cropping.x);
         const anchy = NumCast(cropping.y);
@@ -300,24 +299,23 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
         cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
         cropping.onClick = undefined;
-        const croppingProto = cropping[DocData];
-        croppingProto.annotationOn = undefined;
-        croppingProto.isDataDoc = true;
-        croppingProto.backgroundColor = undefined;
-        croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
-        croppingProto.type = DocumentType.IMG;
-        croppingProto.layout = ImageBox.LayoutString('data');
-        croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
-        croppingProto.data_nativeWidth = anchw;
-        croppingProto.data_nativeHeight = anchh;
-        croppingProto.freeform_scale = viewScale;
-        croppingProto.freeform_panX = anchx / viewScale;
-        croppingProto.freeform_panY = anchy / viewScale;
-        croppingProto.freeform_scale_min = viewScale;
-        croppingProto.freeform_panX_min = anchx / viewScale;
-        croppingProto.freeform_panX_max = anchw / viewScale;
-        croppingProto.freeform_panY_min = anchy / viewScale;
-        croppingProto.freeform_panY_max = anchh / viewScale;
+        cropping.$annotationOn = undefined;
+        cropping.$isDataDoc = true;
+        cropping.$backgroundColor = undefined;
+        cropping.$proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+        cropping.$type = DocumentType.IMG;
+        cropping.$layout = ImageBox.LayoutString('data');
+        cropping.$data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
+        cropping.$data_nativeWidth = anchw;
+        cropping.$data_nativeHeight = anchh;
+        cropping.$freeform_scale = viewScale;
+        cropping.$reeform_panX = anchx / viewScale;
+        cropping.$freeform_panY = anchy / viewScale;
+        cropping.$freeform_scale_min = viewScale;
+        cropping.$freeform_panX_min = anchx / viewScale;
+        cropping.$freeform_panX_max = anchw / viewScale;
+        cropping.$freeform_panY_min = anchy / viewScale;
+        cropping.$freeform_panY_max = anchh / viewScale;
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
             cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 9795febbe..32c9efbd9 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -105,7 +105,7 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     if (setResult) setResult?.(value);
                     else target[key] = field;
                 };
-                const res = script.run({ this: Doc.Layout(doc), _setCacheResult_ }, console.log);
+                const res = script.run({ this: doc, _setCacheResult_ }, console.log);
                 if (!res.success) {
                     if (key) target[key] = script.originalScript;
                     return false;
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index d5dc256d9..8bf65b637 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -252,7 +252,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     fontSize={fontSize}
                                     GetValue={() => linkDesc}
                                     SetValue={action(val => {
-                                        this.Document[DocData].link_description = val;
+                                        this.Document.$link_description = val;
                                         return true;
                                     })}
                                 />
@@ -262,8 +262,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     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))}
+                                    val={StrCast(this.Document.$link_description)}
+                                    setVal={action(val => (this.Document.$link_description = val))}
                                     fillWidth
                                 /> */}
                             </div>
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index ff95f8547..6bec9c53d 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,7 +1,6 @@
 import { action, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { DocData } from '../../../fields/DocSymbols';
 import { StrCast } from '../../../fields/Types';
 import { LinkManager } from '../../util/LinkManager';
 import './LinkDescriptionPopup.scss';
@@ -48,7 +47,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
     onDismiss = (add: boolean) => {
         this.display = false;
         if (add) {
-            LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink[DocData].link_description = this.description);
+            LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink.$link_description = this.description);
         }
         this.description = '';
     };
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 06b75e243..1f72206fc 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -129,15 +129,14 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         cropping._width = anchw;
         cropping._height = anchh;
         cropping.onClick = undefined;
-        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
-        croppingProto.type = DocumentType.IMG;
-        croppingProto.layout = ImageBox.LayoutString('data');
-        croppingProto.data = new ImageField(ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png'));
-        croppingProto.data_nativeWidth = anchw;
-        croppingProto.data_nativeHeight = anchh;
+        cropping.$annotationOn = undefined;
+        cropping.$isDataDoc = true;
+        cropping.$proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+        cropping.$type = DocumentType.IMG;
+        cropping.$layout = ImageBox.LayoutString('data');
+        cropping.$data = new ImageField(ClientUtils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png'));
+        cropping.$data_nativeWidth = anchw;
+        cropping.$data_nativeHeight = anchh;
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
         }
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 7ba313e92..53783e8a3 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -3,7 +3,6 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast } from '../../../../fields/Doc';
-import { DocData } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { BoolCast, DocCast } from '../../../../fields/Types';
@@ -99,7 +98,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
                   });
         screengrabber.overlayX = 70; // was -400
         screengrabber.overlayY = 590; // was 0
-        screengrabber[DocData][Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
+        screengrabber['$' + Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
         Doc.AddToMyOverlay(screengrabber); // just adds doc to overlay
         DocumentView.addViewRenderedCb(screengrabber, docView => {
             RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 9adee53e8..fa099178c 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -331,7 +331,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
         this._props.addDocument?.(imageSnapshot);
         DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
-        // link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);  // do we need to set an end time? should default to +0.1
+        // link && (DocCast(link.link_anchor_2).$timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);  // do we need to set an end time? should default to +0.1
         setTimeout(() => downX !== undefined && downY !== undefined && DocumentView.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
     };
 
@@ -918,11 +918,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     crop = (region: Doc | undefined, addCrop?: boolean) => {
         if (!region) return undefined;
         const cropping = Doc.MakeCopy(region, true);
-        const regionData = region[DocData];
-        regionData.backgroundColor = 'transparent';
-        regionData.lockedPosition = true;
-        regionData.title = 'region:' + this.Document.title;
-        regionData.followLinkToggle = true;
+        region.$backgroundColor = 'transparent';
+        region.$lockedPosition = true;
+        region.$title = 'region:' + this.Document.title;
+        region.$followLinkToggle = true;
         region._timecodeToHide = NumCast(region._timecodeToShow) + 0.0001;
         this.addDocument(region);
         const anchx = NumCast(cropping.x);
@@ -938,25 +937,24 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         cropping.timecodeToHide = undefined;
         cropping.timecodeToShow = undefined;
         cropping.onClick = undefined;
-        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
-        croppingProto.type = DocumentType.VID;
-        croppingProto.layout = VideoBox.LayoutString('data');
-        croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
-        croppingProto.data_nativeWidth = anchw;
-        croppingProto.data_nativeHeight = anchh;
-        croppingProto.videoCrop = true;
-        croppingProto.layout_currentTimecode = this.layoutDoc._layout_currentTimecode;
-        croppingProto.freeform_scale = viewScale;
-        croppingProto.freeform_scale_min = viewScale;
-        croppingProto.freeform_ = anchx / viewScale;
-        croppingProto.freeform_panY = anchy / viewScale;
-        croppingProto.freeform_panX_min = anchx / viewScale;
-        croppingProto.freeform_panX_max = anchw / viewScale;
-        croppingProto.freeform_panY_min = anchy / viewScale;
-        croppingProto.freeform_panY_max = anchh / viewScale;
+        cropping.$annotationOn = undefined;
+        cropping.$isDataDoc = true;
+        cropping.$proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
+        cropping.$type = DocumentType.VID;
+        cropping.$layout = VideoBox.LayoutString('data');
+        cropping.$data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
+        cropping.$data_nativeWidth = anchw;
+        cropping.$data_nativeHeight = anchh;
+        cropping.$videoCrop = true;
+        cropping.$layout_currentTimecode = this.layoutDoc._layout_currentTimecode;
+        cropping.$freeform_scale = viewScale;
+        cropping.$freeform_scale_min = viewScale;
+        cropping.$freeform_ = anchx / viewScale;
+        cropping.$freeform_panY = anchy / viewScale;
+        cropping.$freeform_panX_min = anchx / viewScale;
+        cropping.$freeform_panX_max = anchw / viewScale;
+        cropping.$freeform_panY_min = anchy / viewScale;
+        cropping.$freeform_panY_max = anchh / viewScale;
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
         }
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 1cbfd5fba..e3408696b 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -113,7 +113,7 @@ export class CalendarBox extends CollectionSubView() {
         if (!super.onInternalDrop(e, de)) return false;
         de.complete.docDragData?.droppedDocuments.forEach(doc => {
             const today = new Date().toISOString();
-            if (!doc.date_range) doc[DocData].date_range = `${today}|${today}`;
+            if (!doc.date_range) doc.$date_range = `${today}|${today}`;
         });
         return true;
     };
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
index 6e9307d37..b023b1de6 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
@@ -733,9 +733,9 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     const x2 = parseFloat(values[2]) * Doc.NativeWidth(doc);
                     const y2 = parseFloat(values[3]) * Doc.NativeHeight(doc) + foundChunk.startPage * Doc.NativeHeight(doc);
 
-                    const annotationKey = Doc.LayoutFieldKey(doc) + '_annotations';
+                    const annotationKey = '$' + Doc.LayoutFieldKey(doc) + '_annotations';
 
-                    const existingDoc = DocListCast(doc[DocData][annotationKey]).find(d => d.citation_id === citation.citation_id);
+                    const existingDoc = DocListCast(doc[annotationKey]).find(d => d.citation_id === citation.citation_id);
                     const highlightDoc = existingDoc ?? this.createImageCitationHighlight(x1, y1, x2, y2, citation, annotationKey, doc);
 
                     DocumentManager.Instance.showDocument(highlightDoc, { willZoomCentered: true }, () => {});
@@ -779,7 +779,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             _height: y2 - y1,
             backgroundColor: 'rgba(255, 255, 0, 0.5)',
         });
-        highlight_doc[DocData].citation_id = citation.citation_id;
+        highlight_doc.$citation_id = citation.citation_id;
         Doc.AddDocToList(pdfDoc[DocData], annotationKey, highlight_doc);
         highlight_doc.annotationOn = pdfDoc;
         Doc.SetContainer(highlight_doc, pdfDoc);
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index e0450b202..827db190a 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -6,7 +6,6 @@ import { EditorView } from 'prosemirror-view';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
-import { DocData } from '../../../../fields/DocSymbols';
 import { StrCast } from '../../../../fields/Types';
 import './DashFieldView.scss';
 import EquationEditor from './EquationEditor';
@@ -63,9 +62,9 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
                 }}>
                 <EquationEditor
                     ref={this._ref}
-                    value={StrCast(this._textBoxDoc[DocData][this._fieldKey])}
+                    value={StrCast(this._textBoxDoc['$' + this._fieldKey])}
                     onChange={str => {
-                        this._textBoxDoc[DocData][this._fieldKey] = str;
+                        this._textBoxDoc['$' + this._fieldKey] = str;
                     }}
                     autoCommands="pi theta sqrt sum prod alpha beta gamma rho"
                     autoOperatorNames="sin cos tan"
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 657e689bb..85bd95d15 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -24,8 +24,8 @@ import { PointerHandler } from './imageEditorUtils/PointerHandler';
 import { activeColor, bgColor, brushWidthOffset, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants';
 import { CutMode, CursorData, ImageDimensions, ImageEditTool, ImageToolType, Point } from './imageEditorUtils/imageEditorInterfaces';
 import { DocumentView } from '../DocumentView';
-import { DocData } from '../../../../fields/DocSymbols';
 import { SettingsManager } from '../../../util/SettingsManager';
+import { Upload } from '../../../../server/SharedMediaTypes';
 
 interface GenerativeFillProps {
     imageEditorOpen: boolean;
@@ -397,9 +397,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
             const newImgDoc = await createNewImgDoc(finalImg, firstDoc);
             if (newImgDoc) {
                 // set the image to transparent to remove the background / brushstrokes
-                const docData = newImgDoc[DocData];
-                docData.backgroundColor = 'transparent';
-                docData.disableMixBlend = true;
+                newImgDoc.$backgroundColor = 'transparent';
+                newImgDoc.$disableMixBlend = true;
                 if (firstDoc) setIsFirstDoc(false);
                 setEdits([...prevEdits, { url: finalImgURL, saveRes: undefined }]);
             }
@@ -476,7 +475,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
     const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean /*, parent?: Doc */): Promise<Doc | undefined> => {
         if (!imageRootDoc) return undefined;
         const { src } = img;
-        const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
+        const [result] = (await Networking.PostToServer('/uploadRemoteImage', { sources: [src] })) as Upload.ImageInformation[];
         const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
 
         if (firstDoc) {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index ec97e067a..23155ebf3 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -532,11 +532,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const setData = bestTargetView?.ComponentView?.setData;
             if (setData) setData(activeItem.config_data);
             else {
-                const bestTargetData = bestTarget[DocData];
-                const current = bestTargetData[fkey];
-                const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as FieldType)) : undefined;
-                if (hash) bestTargetData[fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current;
-                bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
+                const current = bestTarget['$' + fkey];
+                const hash = bestTarget['$' + fkey] ? stringHash(Field.toString(bestTarget['$' + fkey] as FieldType)) : undefined;
+                if (hash) bestTarget['$' + fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current;
+                bestTarget['$' + fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
             }
             bestTarget[fkey + '_usePath'] = activeItem.config_usePath;
             setTimeout(() => {
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index 1cceabed3..2283ef965 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -10,9 +10,11 @@ import { INode, parse } from 'svgson';
 import { imageUrlToBase64, setupMoveUpEvents } from '../../../ClientUtils';
 import { unimplementedFunction } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
 import { InkData, InkField, InkTool } from '../../../fields/InkField';
+import { List } from '../../../fields/List';
 import { BoolCast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { PointData } from '../../../pen-gestures/GestureTypes';
+import { Upload } from '../../../server/SharedMediaTypes';
 import { Networking } from '../../Network';
 import { GPTCallType, gptAPICall, gptDrawingColor } from '../../apis/gpt/GPT';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -26,9 +28,6 @@ import { MarqueeView } from '../collections/collectionFreeForm';
 import { ActiveInkArrowEnd, ActiveInkArrowStart, ActiveInkBezierApprox, ActiveInkColor, ActiveInkDash, ActiveInkFillColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../nodes/DocumentView';
 import { FireflyDimensionsMap, FireflyImageData, FireflyImageDimensions } from './FireflyConstants';
 import './SmartDrawHandler.scss';
-import { Upload } from '../../../server/SharedMediaTypes';
-import { PointData } from '../../../pen-gestures/GestureTypes';
-import { List } from '../../../fields/List';
 
 export interface DrawingOptions {
     text?: string;
@@ -153,9 +152,9 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
         this._display = false;
         this.ShowRegenerate = true;
         this._showEditBox = false;
-        const docData = this._selectedDocs[0][DocData];
-        this._lastResponse = StrCast(docData.drawingData);
-        this._lastInput = { text: StrCast(docData.ai_drawing_input), complexity: NumCast(docData.ai_drawing_complexity), size: NumCast(docData.ai_drawing_size), autoColor: BoolCast(docData.ai_drawing_colored), x: this._pageX, y: this._pageY };
+        const docData = this._selectedDocs[0];
+        this._lastResponse = StrCast(docData.$drawingData);
+        this._lastInput = { text: StrCast(docData.$ai_drawing_input), complexity: NumCast(docData.$ai_drawing_complexity), size: NumCast(docData.$ai_drawing_size), autoColor: BoolCast(docData.$ai_drawing_colored), x: this._pageX, y: this._pageY };
     };
 
     /**
@@ -404,7 +403,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
         const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
         try {
             const hrefBase64 = await imageUrlToBase64(hrefComplete);
-            const strokes = DocListCast(drawing[DocData].data);
+            const strokes = DocListCast(drawing.$data);
             const coords: string[] = [];
             strokes.forEach((stroke, i) => {
                 const inkingStroke = DocumentView.getDocumentView(stroke)?.ComponentView as InkingStroke;
@@ -423,14 +422,14 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
      */
     colorStrokes = undoable((res: string, drawing: Doc) => {
         const colorList = res.match(/\{.*?\}/g);
-        const strokes = DocListCast(drawing[DocData].data);
+        const strokes = DocListCast(drawing.$data);
         colorList?.forEach((colors, index) => {
             const strokeAndFill = colors.match(/#[0-9A-Fa-f]{6}/g);
             if (strokeAndFill && strokeAndFill.length == 2) {
-                strokes[index][DocData].color = strokeAndFill[0];
+                strokes[index].$color = strokeAndFill[0];
                 const inkStroke = DocumentView.getDocumentView(strokes[index])?.ComponentView as InkingStroke;
                 const { inkData } = inkStroke.inkScaledData();
-                InkingStroke.IsClosed(inkData) ? (strokes[index][DocData].fillColor = strokeAndFill[1]) : (strokes[index][DocData].fillColor = undefined);
+                InkingStroke.IsClosed(inkData) ? (strokes[index].$fillColor = strokeAndFill[1]) : (strokes[index].$fillColor = undefined);
             }
         });
     }, 'color strokes');
diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx
index e3305851a..080a05d42 100644
--- a/src/client/views/smartdraw/StickerPalette.tsx
+++ b/src/client/views/smartdraw/StickerPalette.tsx
@@ -9,7 +9,6 @@ import ReactLoading from 'react-loading';
 import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
 import { emptyFunction, numberRange } from '../../../Utils';
 import { Doc, DocListCast, returnEmptyDoclist } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
 import { ImageCast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -133,7 +132,7 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
         this._canInteract = true;
         this._opts = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 };
         this._gptRes = [];
-        this._props.Document[DocData].data = undefined;
+        this._props.Document.$data = undefined;
     });
 
     /**
@@ -143,8 +142,8 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
     @undoBatch
     generateDrawings = action(() => {
         this._isLoading = true;
-        const prevDrawings = DocListCast(this._props.Document[DocData].data);
-        this._props.Document[DocData].data = undefined;
+        const prevDrawings = DocListCast(this._props.Document.$data);
+        this._props.Document.$data = undefined;
         SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
         this._canInteract = false;
         Promise.all(
@@ -164,7 +163,7 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
     @action
     addDrawing = (drawing: Doc, opts: DrawingOptions, gptRes: string) => {
         this._gptRes.push(gptRes);
-        drawing[DocData].freeform_fitContentsToBox = true;
+        drawing.$freeform_fitContentsToBox = true;
         Doc.AddDocToList(this._props.Document, 'data', drawing);
     };
 
@@ -176,17 +175,16 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
     saveDrawing = () => {
         const cIndex = NumCast(this._props.Document.carousel_index);
         const focusedDrawing = DocListCast(this._props.Document.data)[cIndex];
-        const docData = focusedDrawing[DocData];
-        docData.title = this._opts.text?.match(/^(.*?)~~~.*$/)?.[1] || this._opts.text;
-        docData.ai_drawing_input = this._opts.text;
-        docData.ai_drawing_complexity = this._opts.complexity;
-        docData.ai_drawing_colored = this._opts.autoColor;
-        docData.ai_drawing_size = this._opts.size;
-        docData.ai_drawing_data = this._gptRes[cIndex];
-        docData.ai = 'gpt';
+        focusedDrawing.$title = this._opts.text?.match(/^(.*?)~~~.*$/)?.[1] || this._opts.text;
+        focusedDrawing.$ai_drawing_input = this._opts.text;
+        focusedDrawing.$ai_drawing_complexity = this._opts.complexity;
+        focusedDrawing.$ai_drawing_colored = this._opts.autoColor;
+        focusedDrawing.$ai_drawing_size = this._opts.size;
+        focusedDrawing.$ai_drawing_data = this._gptRes[cIndex];
+        focusedDrawing.$ai = 'gpt';
         focusedDrawing.width = this._opts.size;
-        docData.x = this._opts.x;
-        docData.y = this._opts.y;
+        focusedDrawing.x = this._opts.x;
+        focusedDrawing.y = this._opts.y;
         StickerPalette.addToPalette(focusedDrawing).then(() => this.resetPalette(true));
     };
 
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e2f708700..4bf04da5c 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -273,12 +273,12 @@ export class Doc extends RefField {
     public static AddToMyOverlay(doc: Doc)    { return Doc.ActiveDashboard ? Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
     public static RemFromMyOverlay(doc: Doc)  { return Doc.ActiveDashboard ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myOverlayDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
     public static AddToMyPublished(doc: Doc)  {
-        doc[DocData].title_custom = true;
-        doc[DocData].layout_showTitle = 'title'; 
+        doc.$title_custom = true;
+        doc.$layout_showTitle = 'title'; 
         Doc.ActiveDashboard ? Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
     public static RemFromMyPublished(doc: Doc){ 
-        doc[DocData].title_custom = false;
-        doc[DocData].layout_showTitle = undefined;
+        doc.$title_custom = false;
+        doc.$layout_showTitle = undefined;
         Doc.ActiveDashboard ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myPublishedDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
     public static IsComicStyle(doc?: Doc)     { return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic' ; } // prettier-ignore
 
@@ -629,7 +629,7 @@ export namespace Doc {
      */
     export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, ignoreProto = false) {
         const key = fieldKey || Doc.LayoutFieldKey(listDoc);
-        const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
+        const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc['$' + key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
         if (list) {
             const ind = list.indexOf(doc);
             if (ind !== -1) {
@@ -646,7 +646,7 @@ export namespace Doc {
      */
     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 || Doc.LayoutFieldKey(listDoc);
-        const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
+        const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc['$' + 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]);
@@ -691,7 +691,7 @@ export namespace Doc {
             Doc.SetLayout(embedding, Doc.MakeEmbedding(layout));
         }
         embedding.createdFrom = doc;
-        embedding.proto_embeddingId = doc[DocData].proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
+        embedding.proto_embeddingId = doc.$proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
         !Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`));
         embedding.author = ClientUtils.CurrentUserEmail();
 
@@ -699,9 +699,8 @@ export namespace Doc {
     }
 
     export function BestEmbedding(doc: Doc) {
-        const dataDoc = doc[DocData];
-        const availableEmbeddings = Doc.GetEmbeddings(dataDoc);
-        const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(d => !d.embedContainer && d.author === ClientUtils.CurrentUserEmail());
+        const availableEmbeddings = Doc.GetEmbeddings(doc);
+        const bestEmbedding = [...(doc[DocData] !== doc ? [doc] : []), ...availableEmbeddings].find(d => !d.embedContainer && d.author === ClientUtils.CurrentUserEmail());
         bestEmbedding && Doc.AddEmbedding(doc, doc);
         return bestEmbedding ?? Doc.MakeEmbedding(doc);
     }
@@ -1475,7 +1474,7 @@ export namespace Doc {
      * @returns
      */
     export function getDescription(doc: Doc) {
-        const curDescription = StrCast(doc[DocData][Doc.LayoutFieldKey(doc) + '_description']);
+        const curDescription = StrCast(doc['$' + Doc.LayoutFieldKey(doc) + '_description']);
         const docText = (async (tdoc:Doc) => {
             switch (tdoc.type) {
                 case DocumentType.PDF: return curDescription || StrCast(tdoc.text).split(/\s+/).slice(0, 50).join(' '); // first 50 words of pdf text
@@ -1484,7 +1483,7 @@ export namespace Doc {
                 case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutFieldKey(tdoc)]).Text;
                 default:               return StrCast(tdoc.title).startsWith("Untitled") ? "" : StrCast(tdoc.title);
         }}); // prettier-ignore
-        return docText(doc).then(text => (doc[DocData][Doc.LayoutFieldKey(doc) + '_description'] = text));
+        return docText(doc).then(text => (doc['$' + Doc.LayoutFieldKey(doc) + '_description'] = text));
     }
 
     // prettier-ignore
@@ -1737,7 +1736,7 @@ ScriptingGlobals.add(function idToDoc(id: string): Doc {
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function renameEmbedding(doc: Doc) {
-    return StrCast(doc[DocData].title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
+    return StrCast(doc.$title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function getProto(doc: Doc) {
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index b294ee8c6..6e2d7eb78 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -210,6 +210,7 @@ export class ComputedField extends ScriptField {
         this._lastComputedResult =
             this._cachedResult ??
             computedFn(() =>
+                ((val) => val instanceof Array ? new List<number>(val) : val)(
                 this.script.compiled &&
                 this.script.run(
                     {
@@ -220,7 +221,7 @@ export class ComputedField extends ScriptField {
                         _readOnly_: true,
                     },
                     console.log
-                ).result as FieldResult
+                ).result as FieldResult)
             )(); // prettier-ignore
         return this._lastComputedResult;
     };
-- 
cgit v1.2.3-70-g09d2


From 705975eb43e7904c62e7e847478f6d0dac60d443 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 23 Mar 2025 18:14:31 -0400
Subject: more _props.Document  to .Document refactoring.  type updates to
 prosemirrortransfer

---
 src/client/util/SharingManager.tsx                 |   2 +-
 src/client/views/DocComponent.tsx                  |  85 +++++++---
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/FieldsDropdown.tsx                |   4 +-
 src/client/views/FilterPanel.tsx                   |   4 +-
 src/client/views/LightboxView.tsx                  |   2 +-
 src/client/views/SidebarAnnos.tsx                  |  24 +--
 src/client/views/TemplateMenu.tsx                  |   1 -
 src/client/views/animationtimeline/Timeline.tsx    |  56 +++----
 .../collections/CollectionMasonryViewFieldRow.tsx  |   8 +-
 .../views/collections/CollectionNoteTakingView.tsx |  15 +-
 .../collections/CollectionNoteTakingViewColumn.tsx |  18 +--
 .../views/collections/CollectionPivotView.tsx      |   2 +-
 .../views/collections/CollectionStackingView.tsx   |  19 +--
 .../CollectionStackingViewFieldColumn.tsx          |  28 ++--
 src/client/views/collections/CollectionSubView.tsx |  10 +-
 .../views/collections/CollectionTimeView.tsx       |   2 +-
 .../views/collections/CollectionTreeView.tsx       |   4 +-
 src/client/views/collections/TabDocView.tsx        |  28 ++--
 .../CollectionFreeFormInfoUI.tsx                   |  14 +-
 .../CollectionFreeFormPannableContents.tsx         |   4 +-
 .../CollectionFreeFormRemoteCursors.tsx            |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  48 +++---
 .../collections/collectionFreeForm/MarqueeView.tsx |   1 +
 .../collectionSchema/SchemaCellField.tsx           |   4 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |   2 +-
 .../collectionSchema/SchemaTableCell.tsx           |  46 +++---
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   2 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  30 ++--
 .../nodes/DataVizBox/components/Histogram.tsx      |  13 +-
 .../nodes/DataVizBox/components/LineChart.tsx      |   8 +-
 .../views/nodes/DataVizBox/components/PieChart.tsx |  16 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |  10 +-
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 src/client/views/nodes/FieldView.tsx               |   4 +-
 src/client/views/nodes/ImageBox.tsx                |   2 +-
 src/client/views/nodes/KeyValueBox.tsx             |  10 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   2 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |   3 +-
 src/client/views/nodes/PDFBox.tsx                  |   3 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   2 -
 src/client/views/nodes/WebBox.tsx                  |   2 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  10 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    | 174 +++++++++++----------
 .../views/nodes/formattedText/SummaryView.tsx      |  46 +++---
 src/client/views/pdf/PDFViewer.tsx                 |  30 ++--
 src/client/views/search/SearchBox.tsx              |  16 +-
 src/client/views/smartdraw/StickerPalette.tsx      |  16 +-
 src/fields/RichTextUtils.ts                        |   3 +-
 src/fields/Types.ts                                |   5 -
 src/server/DashSession/DashSessionAgent.ts         |   4 +-
 52 files changed, 438 insertions(+), 414 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 3a248400b..962f51cd4 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -633,7 +633,7 @@ export class SharingManager extends React.Component<object> {
 
     private focusOn = (contents: string) => {
         const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
-        const docs = DocumentView.Selected().length > 1 ? DocumentView.Selected().map(docView => docView.props.Document) : [this.targetDoc];
+        const docs = DocumentView.Selected().length > 1 ? DocumentView.Selected().map(docView => docView.Document) : [this.targetDoc];
         return (
             <span
                 className="focus-span"
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index a311f6436..8f254ddcd 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -30,26 +30,40 @@ export function DocComponent<P extends DocComponentProps>() {
         }
 
         /**
-         * This is the document being rendered.  In the case of a compound template, it
-         * may not be the actual document rendered and it also may not be the 'real' root document.
-         * Rather, it specifies the shared properties of all layouts of the document (eg, x,y,)
+         * This is the doc that is being rendered. It will be either:
+         *   1) the same as Document if the root of a regular or compound Doc is rendered
+         *   2) the same as the layoutDoc if a component of a compound Doc is rendered.
+         * NOTE: it is very unlikely that you really want to use this method. Instead
+         *       consider: Document, layoutDoc, dataDoc
          */
-        get Document() {
-            return DocCast(this._props.Document.rootDocument, this._props.Document);
+        get _renderDoc() {
+            return this._props.Document;
         }
 
         /**
-         * This is the document being rendered.  It may be a template so it may or may no inherit from the data doc.
+         * This is the "root" Doc being rendered.  In the case of a compound template Doc,
+         * this is the outermost Doc that represents the entire compound Doc.  It is not
+         * necessarily the Doc being rendered in the current React component.
+         * This Doc inherits from the dataDoc, and may or may not inherit (or be) the layoutDoc.
+         */
+        get Document() {
+            return DocCast(this._renderDoc.rootDocument, this._renderDoc);
+        }
+        /**
+         * This is the document being rendered by the React component. In the
+         * case of a compound template, this will be the  expanded template Doc
+         * that represents the component of the compound Doc being rendered.
+         * This may or may not inherit from the data doc.
          */
         @computed get layoutDoc() {
-            return this._props.LayoutTemplateString ? this._props.Document : Doc.Layout(this._props.Document, this._props.LayoutTemplate?.());
+            return this._props.LayoutTemplateString ? this._renderDoc : Doc.Layout(this._renderDoc, this._props.LayoutTemplate?.());
         }
 
         /**
-         * This is the unique data repository for a dcoument that stores the intrinsic document data
+         * This is the unique data repository for a document that stores the intrinsic document data.
          */
         @computed get dataDoc() {
-            return this._props.Document[DocData];
+            return this._renderDoc[DocData];
         }
     }
     return Component;
@@ -75,25 +89,40 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
         }
 
         /**
-         * This is the document being rendered.  In the case of a compound template, it
-         * may not be the actual document rendered and it also may not be the 'real' root document.
-         * Rather, it specifies the shared properties of all layouts of the document (eg, x,y,)
+         * This is the doc that is being rendered. It will be either:
+         *   1) the same as Document if the root of a regular or compound Doc is rendered
+         *   2) the same as the layoutDoc if a component of a compound Doc is rendered.
+         * NOTE: it is very unlikely that you really want to use this method. Instead
+         *       consider: Document, layoutDoc, dataDoc
+         */
+        get _renderDoc() {
+            return this._props.Document;
+        }
+
+        /**
+         * This is the "root" Doc being rendered.  In the case of a compound template Doc,
+         * this is the outermost Doc that represents the entire compound Doc.  It is not
+         * necessarily the Doc being rendered in the current React component.
+         * This Doc inherits from the dataDoc, and may or may not inherit (or be) the layoutDoc.
          */
         get Document() {
-            return DocCast(this._props.Document.rootDocument, this._props.Document);
+            return DocCast(this._renderDoc.rootDocument, this._renderDoc);
         }
         /**
-         * This is the document being rendered.  It may be a template so it may or may no inherit from the data doc.
+         * This is the document being rendered by the React component. In the
+         * case of a compound template, this will be the  expanded template Doc
+         * that represents the component of the compound Doc being rendered.
+         * This may or may not inherit from the data doc.
          */
         @computed get layoutDoc() {
-            return Doc.Layout(this._props.Document);
+            return Doc.Layout(this._renderDoc);
         }
 
         /**
          * This is the unique data repository for a dcoument that stores the intrinsic document data
          */
         @computed get dataDoc() {
-            return this._props.Document.isTemplateForField || this._props.Document.isTemplateDoc ? (this._props.TemplateDataDocument ?? this._props.Document[DocData]) : this._props.Document[DocData];
+            return this._renderDoc.isTemplateForField || this._renderDoc.isTemplateDoc ? (this._props.TemplateDataDocument ?? this._renderDoc[DocData]) : this._renderDoc[DocData];
         }
 
         /**
@@ -133,25 +162,37 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
         }
 
         /**
-         * This is the document being rendered.  In the case of a compound template, it
-         * may not be the actual document rendered and it also may not be the 'real' root document.
-         * Rather, it specifies the shared properties of all layouts of the document (eg, x,y,)
+         * This is the doc that is being rendered. It will be either:
+         *   1) the same as Document if the root of a regular or compound Doc is rendered
+         *   2) the same as the layoutDoc if a component of a compound Doc is rendered.
+         * NOTE: it would unlikely that you really want to use this instead of the
+         *       other Doc options (Document, layoutDoc, dataDoc)
+         */
+        get _renderDoc() {
+            return this._props.Document;
+        }
+
+        /**
+         * This is the "root" Doc being rendered.  In the case of a compound template Doc,
+         * this is the outermost Doc that represents the entire compound Doc.  It is not
+         * necessarily the Doc being rendered in the current React component.
+         * This Doc inherits from the dataDoc, and may or may not inherit (or be) the layoutDoc.
          */
         @computed get Document() {
-            return DocCast(this._props.Document.rootDocument, this._props.Document);
+            return DocCast(this._renderDoc.rootDocument, this._renderDoc);
         }
         /**
          * This is the document being rendered.  It may be a template so it may or may no inherit from the data doc.
          */
         @computed get layoutDoc() {
-            return Doc.Layout(this._props.Document);
+            return Doc.Layout(this._renderDoc);
         }
 
         /**
          * This is the unique data repository for a dcoument that stores the intrinsic document data
          */
         @computed get dataDoc() {
-            return this._props.Document.isTemplateForField || this._props.Document.isTemplateDoc ? (this._props.TemplateDataDocument ?? this._props.Document[DocData]) : this._props.Document[DocData];
+            return this._renderDoc.isTemplateForField || this._renderDoc.isTemplateDoc ? (this._props.TemplateDataDocument ?? this._renderDoc[DocData]) : this._props.Document[DocData];
         }
 
         /**
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 92b4d6fbf..19b987cb5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -635,7 +635,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         if (DocumentView.Selected().length === 1) {
             const selected = DocumentView.Selected()[0];
             if (this._titleControlString.startsWith('$')) {
-                return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as FieldType) || '-unset-';
+                return Field.toJavascriptString(selected._renderDoc[this._titleControlString.substring(1)] as FieldType) || '-unset-';
             }
             return this._accumulatedTitle;
         }
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
index 407031b40..e7ab6a180 100644
--- a/src/client/views/FieldsDropdown.tsx
+++ b/src/client/views/FieldsDropdown.tsx
@@ -18,7 +18,7 @@ import './FilterPanel.scss';
 import { ObservableReactComponent } from './ObservableReactComponent';
 
 interface fieldsDropdownProps {
-    Document: Doc; // show fields for this Doc if set, otherwise for all docs in dashboard
+    Doc: Doc; // show fields for this Doc if set, otherwise for all docs in dashboard
     selectFunc: (value: string) => void;
     menuClose?: () => void;
     placeholder?: string | (() => string);
@@ -36,7 +36,7 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
 
     @computed get allDescendantDocs() {
         const allDocs = new Set<Doc>();
-        SearchUtil.foreachRecursiveDoc([this._props.Document], (depth, doc) => allDocs.add(doc));
+        SearchUtil.foreachRecursiveDoc([this._props.Doc], (depth, doc) => allDocs.add(doc));
         return Array.from(allDocs);
     }
 
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 4fc8d7a68..c3b3f9d0c 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -379,7 +379,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
             <div className="filterBox-treeView">
                 <div className="filterBox-select">
                     <div style={{ width: '100%' }}>
-                        <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} />
+                        <FieldsDropdown Doc={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} />
                     </div>
                     {/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */}
                     {/* <div className="filterBox-select-bool"> 
@@ -443,7 +443,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                 <div>
                     <div className="filterBox-select">
                         <div style={{ width: '100%' }}>
-                            <FieldsDropdown Document={this.Document} selectFunc={this._props.addHotKey} showPlaceholder placeholder="add a hotkey" addedFields={['acl_Guest', LinkedTo]} />
+                            <FieldsDropdown Doc={this.Document} selectFunc={this._props.addHotKey} showPlaceholder placeholder="add a hotkey" addedFields={['acl_Guest', LinkedTo]} />
                         </div>
                     </div>
                 </div>
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 5698da785..de2c7cd09 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -317,7 +317,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                             </GestureOverlay>
                         </div>
 
-                        {this._showPalette && <StickerPalette ref={r => (this._annoPaletteView = r)} Document={DocCast(Doc.UserDoc().myLightboxDrawings)} />}
+                        {this._showPalette && <StickerPalette ref={r => (this._annoPaletteView = r)} Doc={DocCast(Doc.UserDoc().myLightboxDrawings)} />}
                         {this.renderNavBtn(0, undefined, this._props.PanelHeight / 2 - 12.5, 'chevron-left', this._doc && this._history.length ? true : false, this.previous)}
                         {this.renderNavBtn(
                             this._props.PanelWidth - Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]),
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index b3f3a4478..3c0611f03 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -22,7 +22,7 @@ import { FieldViewProps } from './nodes/FieldView';
 
 interface ExtraProps {
     fieldKey: string;
-    Document: Doc;
+    Doc: Doc;
     layoutDoc: Doc;
     dataDoc: Doc;
     // usePanelWidth: boolean;
@@ -45,7 +45,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
     _stackRef = React.createRef<CollectionStackingView>();
     @computed get allMetadata() {
         const keys = new Map<string, FieldResult<FieldType>>();
-        DocListCast(this._props.Document[this.sidebarKey]).forEach(doc =>
+        DocListCast(this._props.Doc[this.sidebarKey]).forEach(doc =>
             SearchUtil.documentKeys(doc)
                 .filter(key => key[0] && key[0] !== '_' && key[0] === key[0].toUpperCase())
                 .map(key => keys.set(key, doc[key]))
@@ -54,7 +54,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
     }
     @computed get allHashtags() {
         const keys = new Set<string>();
-        DocListCast(this._props.Document[this.sidebarKey]).forEach(doc => StrListCast(doc.tags).forEach(tag => keys.add(tag)));
+        DocListCast(this._props.Doc[this.sidebarKey]).forEach(doc => StrListCast(doc.tags).forEach(tag => keys.add(tag)));
         return Array.from(keys.keys())
             .filter(key => key[0])
             .filter(key => !key.startsWith('_') && (key[0] === '#' || key[0] === key[0].toUpperCase()))
@@ -62,7 +62,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
     }
     @computed get allUsers() {
         const keys = new Set<string>();
-        DocListCast(this._props.Document[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
+        DocListCast(this._props.Doc[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
         return Array.from(keys.keys()).sort();
     }
 
@@ -72,7 +72,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
             .join(' ');
         const target = Docs.Create.TextDocument(startup, {
             title: '-note-',
-            annotationOn: this._props.Document,
+            annotationOn: this._props.Doc,
             _width: 200,
             _height: 50,
             _layout_fitWidth: true,
@@ -87,7 +87,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
             .filter(data => data.split(':')[0])
             .filter(data => !filterExlusions?.includes(data.split(':')[0]))
             .map(data => {
-                const key = '$'+data.split(':')[0];
+                const key = '$' + data.split(':')[0];
                 const val = Field.Copy(this.allMetadata.get(key));
                 target[key] = val;
                 return {
@@ -148,7 +148,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
         return target;
     };
     makeDocUnfiltered = (doc: Doc) => {
-        if (DocListCast(this._props.Document[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+        if (DocListCast(this._props.Doc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
             if (this.childFilters()) {
                 // if any child filters exist, get rid of them
                 this._props.layoutDoc._childFilters = new List<string>();
@@ -189,7 +189,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
         const renderTag = (tag: string) => {
             const active = this.childFilters().includes(`tags${Doc.FilterSep}${tag}${Doc.FilterSep}check`);
             return (
-                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, 'tags', tag, 'check', true, undefined, e.shiftKey)}>
+                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Doc, 'tags', tag, 'check', true, undefined, e.shiftKey)}>
                     {tag}
                 </div>
             );
@@ -197,7 +197,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
         const renderMeta = (tag: string) => {
             const active = this.childFilters().includes(`${tag}${Doc.FilterSep}${Doc.FilterAny}${Doc.FilterSep}exists`);
             return (
-                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}>
+                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Doc, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}>
                     {tag}
                 </div>
             );
@@ -205,7 +205,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
         const renderUsers = (user: string) => {
             const active = this.childFilters().includes(`author:${user}:check`);
             return (
-                <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, 'author', user, 'check', true, undefined, e.shiftKey)}>
+                <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Doc, 'author', user, 'check', true, undefined, e.shiftKey)}>
                     {user}
                 </div>
             );
@@ -216,9 +216,9 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                 style={{
                     position: 'absolute',
                     pointerEvents: this._props.isContentActive() ? 'all' : undefined,
-                    top: this._props.Document.type !== DocumentType.RTF && StrCast(this._props.Document._layout_showTitle) === 'title' ? 15 : 0,
+                    top: this._props.Doc.type !== DocumentType.RTF && StrCast(this._props.Doc._layout_showTitle) === 'title' ? 15 : 0,
                     right: 0,
-                    background: this._props.styleProvider?.(this._props.Document, this._props, StyleProp.WidgetColor) as string,
+                    background: this._props.styleProvider?.(this._props.Doc, this._props, StyleProp.WidgetColor) as string,
                     width: `100%`,
                     height: '100%',
                 }}>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 680c8ed0e..1266a11c1 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -80,7 +80,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         const addedTypes = DocListCast(Cast(Doc.UserDoc().template_clickFuncs, Doc, null)?.data);
         const templateMenu: Array<JSX.Element> = [];
         templateMenu.push(<OtherToggle key="default" name={firstDoc.layout instanceof Doc ? StrCast(firstDoc.layout.title) : 'Default'} checked={templateName === 'layout'} toggle={this.toggleDefault} />);
-        // eslint-disable-next-line no-return-assign
         addedTypes.concat(noteTypes).map(template => (template.treeView_Checked = this.templateIsUsed(firstDoc, template)));
         this._addedKeys &&
             Array.from(this._addedKeys)
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index 15683ebf2..cd2c7df1b 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -46,7 +46,7 @@ import { Id } from '../../../fields/FieldSymbols';
  */
 
 @observer
-export class Timeline extends ObservableReactComponent<FieldViewProps> {
+export class Timeline extends ObservableReactComponent<FieldViewProps & { Doc: Doc }> {
     // readonly constants
     private readonly DEFAULT_TICK_SPACING: number = 50;
     private readonly MAX_TITLE_HEIGHT = 75;
@@ -57,7 +57,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
     private DEFAULT_CONTAINER_HEIGHT: number = 330;
     private MIN_CONTAINER_HEIGHT: number = 205;
 
-    constructor(props: FieldViewProps) {
+    constructor(props: FieldViewProps & { Doc: Doc }) {
         super(props);
         makeObservable(this);
     }
@@ -90,11 +90,11 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
      */
     @computed
     private get children(): Doc[] {
-        const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this._props.Document.type) as unknown as DocumentType);
+        const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this._props.Doc.type) as unknown as DocumentType);
         if (annotatedDoc) {
-            return DocListCast(this._props.Document[Doc.LayoutFieldKey(this._props.Document) + '_annotations']);
+            return DocListCast(this._props.Doc[Doc.LayoutFieldKey(this._props.Doc) + '_annotations']);
         }
-        return DocListCast(this._props.Document[this._props.fieldKey]);
+        return DocListCast(this._props.Doc[this._props.fieldKey]);
     }
 
     /// //////lifecycle functions////////////
@@ -104,21 +104,21 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; // check if relHeight is less than Maxheight. Else, just set relheight to max
         this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; // offset
         this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; // twice the titleheight + offset
-        if (!this._props.Document.AnimationLength) {
+        if (!this._props.Doc.AnimationLength) {
             // if animation length did not exist
-            this._props.Document.AnimationLength = this._time; // set it to default time
+            this._props.Doc.AnimationLength = this._time; // set it to default time
         } else {
-            this._time = NumCast(this._props.Document.AnimationLength); // else, set time to animationlength stored from before
+            this._time = NumCast(this._props.Doc.AnimationLength); // else, set time to animationlength stored from before
         }
         this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); // the entire length of the timeline div (actual div part itself)
         this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; // the visible length of the timeline (the length that you current see)
         this._visibleStart = this._infoContainer.current!.scrollLeft; // where the div starts
-        this._props.Document.isATOn = !this._props.Document.isATOn; // turns the boolean on, saying AT (animation timeline) is on
+        this._props.Doc.isATOn = !this._props.Doc.isATOn; // turns the boolean on, saying AT (animation timeline) is on
         this.toggleHandle();
     }
 
     componentWillUnmount() {
-        this._props.Document.AnimationLength = this._time; // save animation length
+        this._props.Doc.AnimationLength = this._time; // save animation length
     }
     /// //////////////////////////////////////////////
 
@@ -224,7 +224,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
      */
     @action
     onPanDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, e => this.changeCurrentBarX(this._trackbox.current!.scrollLeft + e.clientX - this._trackbox.current!.getBoundingClientRect().left));
+        setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, movEv => this.changeCurrentBarX(this._trackbox.current!.scrollLeft + movEv.clientX - this._trackbox.current!.getBoundingClientRect().left));
     };
 
     /**
@@ -241,7 +241,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
             this._visibleStart -= e.movementX;
             this._totalLength -= e.movementX;
             this._time -= RegionHelpers.convertPixelTime(e.movementX, 'mili', 'time', this._tickSpacing, this._tickIncrement);
-            this._props.Document.AnimationLength = this._time;
+            this._props.Doc.AnimationLength = this._time;
         }
         return false;
     };
@@ -259,8 +259,8 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         setupMoveUpEvents(
             this,
             e,
-            action(e => {
-                const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom;
+            action(movEv => {
+                const offset = movEv.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom;
                 this._containerHeight = clamp(this.MIN_CONTAINER_HEIGHT, this._containerHeight + offset, this.MAX_CONTAINER_HEIGHT);
                 return false;
             }),
@@ -358,7 +358,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         const size = 40 * scale; // 50 is default
         const iconSize = 25;
         const width: number = this._props.PanelWidth();
-        const modeType = this._props.Document.isATOn ? 'Author' : 'Play';
+        const modeType = this._props.Doc.isATOn ? 'Author' : 'Play';
 
         // decides if information should be omitted because the timeline is very small
         // if its less than 950 pixels then it's going to be overlapping
@@ -397,7 +397,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
                             tickIncrement={this._tickIncrement}
                             time={this._time}
                             parent={this}
-                            isAuthoring={BoolCast(this._props.Document.isATOn)}
+                            isAuthoring={BoolCast(this._props.Doc.isATOn)}
                             currentBarX={this._currentBarX}
                             totalLength={this._totalLength}
                             visibleLength={this._visibleLength}
@@ -418,10 +418,10 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
                     </div>
                     <div className="time-box overview-tool" style={{ display: 'flex' }}>
                         {this.timeIndicator(lengthString, totalTime)}
-                        <div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this._props.Document)}>
+                        <div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this._props.Doc)}>
                             <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
                         </div>
-                        <div className="resetView-tool" style={{ display: this._props.Document.isATOn ? 'flex' : 'none' }} title="Set Default View" onClick={() => this.setView(this._props.Document)}>
+                        <div className="resetView-tool" style={{ display: this._props.Doc.isATOn ? 'flex' : 'none' }} title="Set Default View" onClick={() => this.setView(this._props.Doc)}>
                             <FontAwesomeIcon icon="expand-arrows-alt" size="lg" />
                         </div>
                     </div>
@@ -431,17 +431,17 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
     };
 
     timeIndicator(lengthString: string, totalTime: number) {
-        if (this._props.Document.isATOn) {
-            return <div key="time-text" className="animation-text" style={{ visibility: this._props.Document.isATOn ? 'visible' : 'hidden', display: this._props.Document.isATOn ? 'flex' : 'none' }}>{`Total: ${this.toReadTime(totalTime)}`}</div>;
+        if (this._props.Doc.isATOn) {
+            return <div key="time-text" className="animation-text" style={{ visibility: this._props.Doc.isATOn ? 'visible' : 'hidden', display: this._props.Doc.isATOn ? 'flex' : 'none' }}>{`Total: ${this.toReadTime(totalTime)}`}</div>;
         } else {
             const ctime = `Current: ${this.getCurrentTime()}`;
             const ttime = `Total: ${this.toReadTime(this._time)}`;
             return (
                 <div style={{ flexDirection: 'column' }}>
-                    <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this._props.Document.isATOn ? 'block' : 'none' }}>
+                    <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this._props.Doc.isATOn ? 'block' : 'none' }}>
                         {ctime}
                     </div>
-                    <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this._props.Document.isATOn ? 'block' : 'none' }}>
+                    <div className="animation-text" style={{ fontSize: '10px', width: '100%', display: !this._props.Doc.isATOn ? 'block' : 'none' }}>
                         {ttime}
                     </div>
                 </div>
@@ -467,8 +467,8 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         const roundToggleContainer = this._roundToggleContainerRef.current!;
         const timelineContainer = this._timelineContainer.current!;
 
-        this._props.Document.isATOn = !this._props.Document.isATOn;
-        if (!BoolCast(this._props.Document.isATOn)) {
+        this._props.Doc.isATOn = !this._props.Doc.isATOn;
+        if (!BoolCast(this._props.Doc.isATOn)) {
             // turning on playmode...
             roundToggle.style.transform = 'translate(0px, 0px)';
             roundToggle.style.animationName = 'turnoff';
@@ -543,7 +543,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
         // change visible and total width
         return (
             <div style={{ visibility: 'visible' }}>
-                <div key="timeline_wrapper" style={{ visibility: this._props.Document.isATOn ? 'visible' : 'hidden', left: '0px', top: '0px', position: 'absolute', width: '100%', transform: 'translate(0px, 0px)' }}>
+                <div key="timeline_wrapper" style={{ visibility: this._props.Doc.isATOn ? 'visible' : 'hidden', left: '0px', top: '0px', position: 'absolute', width: '100%', transform: 'translate(0px, 0px)' }}>
                     <div key="timeline_container" className="timeline-container" ref={this._timelineContainer} style={{ height: `${this._containerHeight}px`, top: `0px` }}>
                         <div key="timeline_info" className="info-container" onPointerDown={this.onPanDown} ref={this._infoContainer} onWheel={this.onWheelZoom}>
                             {this.drawTicks()}
@@ -551,7 +551,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
                                 <div key="timeline_scrubberhead" className="scrubberhead" onPointerDown={this.onScrubberDown} />
                             </div>
                             <div key="timeline_trackbox" className="trackbox" ref={this._trackbox} style={{ width: `${this._totalLength}px` }}>
-                                {[...this.children, this._props.Document].map(doc => (
+                                {[...this.children, this._props.Doc].map(doc => (
                                     <Track
                                         key={doc[Id]}
                                         ref={ref => this.mapOfTracks.push(ref)}
@@ -563,7 +563,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
                                         time={this._time}
                                         tickSpacing={this._tickSpacing}
                                         tickIncrement={this._tickIncrement}
-                                        collection={this._props.Document}
+                                        collection={this._props.Doc}
                                         timelineVisible={true}
                                     />
                                 ))}
@@ -571,7 +571,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps> {
                         </div>
                         <div className="currentTime">Current: {this.getCurrentTime()}</div>
                         <div key="timeline_title" className="title-container" ref={this._titleContainer}>
-                            {[...this.children, this._props.Document].map(doc => (
+                            {[...this.children, this._props.Doc].map(doc => (
                                 <div key={doc[Id]} style={{ height: `${this._titleHeight}px` }} className="datapane" onPointerOver={() => Doc.BrushDoc(doc)} onPointerOut={() => Doc.UnBrushDoc(doc)}>
                                     <p>{StrCast(doc.title)}</p>
                                 </div>
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 4fe73895e..89ccf5a0f 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -22,7 +22,7 @@ import './CollectionStackingView.scss';
 interface CMVFieldRowProps {
     rows: () => number;
     headings: () => object[];
-    Document: Doc;
+    Doc: Doc;
     chromeHidden?: boolean;
     heading: string;
     headingObject: SchemaHeaderField | undefined;
@@ -73,7 +73,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
 
     createRowDropRef = (ele: HTMLDivElement | null) => {
         this._dropDisposer?.();
-        if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this), this._props.Document);
+        if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this), this._props.Doc);
         else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
         this._ele = ele;
     };
@@ -189,7 +189,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
     };
 
     headerMove = (e: PointerEvent) => {
-        const embedding = Doc.MakeEmbedding(this._props.Document);
+        const embedding = Doc.MakeEmbedding(this._props.Doc);
         const key = this._props.pivotField;
         let value = this.getValue(this.heading);
         value = typeof value === 'string' ? `"${value}"` : value;
@@ -289,7 +289,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
         const key = this._props.pivotField;
         const evContents = this.heading ? this.heading : this._props.type && this._props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`;
         const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine />;
-        return this._props.Document.miniHeaders ? (
+        return this._props.Doc.miniHeaders ? (
             <div className="collectionStackingView-miniHeader">{editableHeaderView}</div>
         ) : !this._props.headingObject ? null : (
             <div className="collectionStackingView-sectionHeader" ref={this._headerRef}>
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 4650727eb..2dabd3269 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -32,6 +32,7 @@ import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn
 import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import { Property } from 'csstype';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 
 /**
  * CollectionNoteTakingView is a column-based view for displaying documents. In this view, the user can (1)
@@ -437,10 +438,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     };
 
     @undoBatch
-    onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
-        if ((e.ctrlKey || fieldProps.Document._createDocOnCR) && ['Enter'].includes(e.key)) {
+    onKeyDown = (e: React.KeyboardEvent, textBox: FormattedTextBox) => {
+        if ((e.ctrlKey || textBox.Document._createDocOnCR) && ['Enter'].includes(e.key)) {
             e.stopPropagation?.();
-            const newDoc = Doc.MakeCopy(fieldProps.Document, true);
+            const newDoc = Doc.MakeCopy(textBox.Document, true);
             newDoc.$text = undefined;
             DocumentView.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
@@ -543,8 +544,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
             addDocument={this.addDocument}
             chromeHidden={this.chromeHidden}
             colHeaderData={this.colHeaderData}
-            Document={this.Document}
-            TemplateDataDocument={this._props.TemplateDataDocument}
+            Doc={this.Document}
+            TemplateDataDoc={this._props.TemplateDataDocument}
             resizeColumns={this.resizeColumns}
             renderChildren={this.children}
             numGroupColumns={this.numGroupColumns}
@@ -567,7 +568,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
 
     @undoBatch
     remColumn = (value: SchemaHeaderField) => {
-        const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
+        const colHdrData = Array.from(Cast(this.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
         if (value) {
             const index = colHdrData.indexOf(value);
             index !== -1 && colHdrData.splice(index, 1);
@@ -701,7 +702,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 {this.renderedSections}
                 <div className="collectionNotetaking-pivotField" style={{ right: 0, top: 0, position: 'absolute' }}>
                     <FieldsDropdown
-                        Document={this.Document}
+                        Doc={this.Document}
                         selectFunc={undoable(fieldKey => {
                             this.layoutDoc._pivotField = fieldKey;
                             this.removeEmptyColumns();
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index df9b5a1eb..f283b0abe 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -21,8 +21,8 @@ import './CollectionNoteTakingView.scss';
 import { DocumentView } from '../nodes/DocumentView';
 
 interface CSVFieldColumnProps {
-    Document: Doc;
-    TemplateDataDocument: Opt<Doc>;
+    Doc: Doc;
+    TemplateDataDoc: Opt<Doc>;
     backgroundColor?: () => string | undefined;
     docList: Doc[];
     heading: string;
@@ -65,7 +65,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
 
     // columnWidth returns the width of a column in absolute pixels
     @computed get columnWidth() {
-        if (this._props.Document._notetaking_columns_autoSize) return this._props.availableWidth / (this._props.colHeaderData?.length || 1);
+        if (this._props.Doc._notetaking_columns_autoSize) return this._props.availableWidth / (this._props.colHeaderData?.length || 1);
         if (!this._props.colHeaderData || !this._props.headingObject || this._props.colHeaderData.length === 1) return `${(this._props.availableWidth / this._props.PanelWidth()) * 100}%`;
         const i = this._props.colHeaderData.findIndex(hd => hd.heading === this._props.headingObject?.heading && hd.color === this._props.headingObject.color);
         return ((this._props.colHeaderData[i].width * this._props.availableWidth) / this._props.PanelWidth()) * 100 + '%';
@@ -81,7 +81,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
 
     createColumnDropRef = (ele: HTMLDivElement | null) => {
         this.dropDisposer?.();
-        if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document);
+        if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Doc);
         else if (this._ele) this.props.refList.slice(this.props.refList.indexOf(this._ele), 1);
         this._ele = ele;
     };
@@ -155,7 +155,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
     // all docs are added to the column directly to the left.
     @undoBatch
     deleteColumn = () => {
-        const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
+        const colHdrData = Array.from(Cast(this._props.Doc[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
         if (this._props.headingObject) {
             //          this._props.docList.forEach(d => (d['$'+this._props.pivotField] = undefined));
             colHdrData.splice(colHdrData.indexOf(this._props.headingObject), 1);
@@ -184,11 +184,11 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
         );
 
         ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
-            Doc.GetProto(this._props.Document)[name] = '';
+            Doc.GetProto(this._props.Doc)[name] = '';
             const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true });
             if (created) {
-                if (this._props.Document.isTemplateDoc) {
-                    Doc.MakeMetadataFieldTemplate(created, this._props.Document);
+                if (this._props.Doc.isTemplateDoc) {
+                    Doc.MakeMetadataFieldTemplate(created, this._props.Doc);
                 }
                 this._props.addDocument?.(created);
             }
@@ -267,7 +267,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
                 style={{
                     width: this.columnWidth,
                     background: this._hover && SnappingManager.IsDragging ? '#b4b4b4' : 'inherit',
-                    marginLeft: this._props.headings().findIndex(h => h[0] === this._props.headingObject) === 0 ? NumCast(this._props.Document.xMargin) : 0,
+                    marginLeft: this._props.headings().findIndex(h => h[0] === this._props.headingObject) === 0 ? NumCast(this._props.Doc.xMargin) : 0,
                 }}>
                 <div className="collectionNoteTakingViewFieldColumn" key={this._heading} ref={this.createColumnDropRef}>
                     {this.innards}
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
index 2600c0f57..4736070c3 100644
--- a/src/client/views/collections/CollectionPivotView.tsx
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -103,7 +103,7 @@ export class CollectionPivotView extends CollectionSubView() {
                 {this.contents}
                 <div style={{ right: 0, top: 0, position: 'absolute' }}>
                     <FieldsDropdown
-                        Document={this.Document}
+                        Doc={this.Document}
                         selectFunc={fieldKey => {
                             this.layoutDoc._pivotField = fieldKey;
                         }}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9972fe03d..883b0bbe3 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -34,6 +34,7 @@ import './CollectionStackingView.scss';
 import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import { computedFn } from 'mobx-utils';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 
 export type collectionStackingViewProps = {
     sortFunc?: (a: Doc, b: Doc) => number;
@@ -309,15 +310,15 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         return this._props.styleProvider?.(doc, props, property);
     };
     @undoBatch
-    onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
+    onKeyDown = (e: React.KeyboardEvent, textBox: FormattedTextBox) => {
         if (['Enter'].includes(e.key) && e.ctrlKey) {
             e.stopPropagation?.();
-            const layoutFieldKey = StrCast(fieldProps.fieldKey);
-            const newDoc = Doc.MakeCopy(fieldProps.Document, true);
-            const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)];
+            const layoutFieldKey = StrCast(textBox.fieldKey);
+            const newDoc = Doc.MakeCopy(textBox.Document, true);
+            const dataField = textBox.Document[Doc.LayoutFieldKey(newDoc)];
             newDoc['$' + Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
-            if (layoutFieldKey !== 'layout' && fieldProps.Document[layoutFieldKey] instanceof Doc) {
-                newDoc[layoutFieldKey] = fieldProps.Document[layoutFieldKey];
+            if (layoutFieldKey !== 'layout' && textBox.Document[layoutFieldKey] instanceof Doc) {
+                newDoc[layoutFieldKey] = textBox.Document[layoutFieldKey];
             }
             newDoc.$text = undefined;
             DocumentView.SetSelectOnLoad(newDoc);
@@ -574,8 +575,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 addDocument={this.addDocument}
                 chromeHidden={this.chromeHidden}
                 colHeaderData={this.colHeaderData}
-                Document={this.Document}
-                TemplateDataDocument={this._props.TemplateDataDocument}
+                Doc={this.Document}
+                TemplateDataDoc={this._props.TemplateDataDocument}
                 renderChildren={this.children}
                 columnWidth={this.columnWidth}
                 numGroupColumns={this.numGroupColumns}
@@ -610,7 +611,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 <div style={{ top: this.yMargin }}>
                     <CollectionMasonryViewFieldRow
                         showHandle={first}
-                        Document={this.Document}
+                        Doc={this.Document}
                         chromeHidden={this.chromeHidden}
                         pivotField={this.pivotField}
                         refList={this._refList}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 6f32dd2e0..3b9d167c6 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -29,8 +29,8 @@ import './CollectionStackingView.scss';
 
 // So this is how we are storing a column
 interface CSVFieldColumnProps {
-    Document: Doc;
-    TemplateDataDocument: Opt<Doc>;
+    Doc: Doc;
+    TemplateDataDoc: Opt<Doc>;
     docList: Doc[];
     heading: string;
     pivotField: string;
@@ -90,7 +90,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
     // is that the only way to have drop targets?
     createColumnDropRef = (ele: HTMLDivElement | null) => {
         this.dropDisposer?.();
-        if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document, this.onInternalPreDrop.bind(this));
+        if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Doc, this.onInternalPreDrop.bind(this));
         else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
         this._ele = ele;
     };
@@ -183,7 +183,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
     // TODO: I think this is where I'm supposed to edit stuff
     startDrag = (e: PointerEvent) => {
         // is MakeEmbedding a way to make a copy of a doc without rendering it?
-        const embedding = Doc.MakeEmbedding(this._props.Document);
+        const embedding = Doc.MakeEmbedding(this._props.Doc);
         embedding._width = this._props.columnWidth / (this._props.colHeaderData?.length || 1);
         embedding._pivotField = undefined;
         let value = this.getValue(this._heading);
@@ -230,7 +230,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         ContextMenu.Instance.clearItems();
         const layoutItems: ContextMenuProps[] = [];
         const docItems: ContextMenuProps[] = [];
-        const dataDoc = this._props.TemplateDataDocument || this._props.Document;
+        const dataDoc = this._props.TemplateDataDoc || this._props.Doc;
         const width = this._ele ? DivWidth(this._ele) : 0;
         const height = this._ele ? DivHeight(this._ele) : 0;
         DocUtils.addDocumentCreatorMenuItems(
@@ -250,10 +250,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                 docItems.push({
                     description: ':' + fieldKey,
                     event: () => {
-                        const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
+                        const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Doc));
                         if (created) {
-                            if (this._props.Document.isTemplateDoc) {
-                                Doc.MakeMetadataFieldTemplate(created, this._props.Document);
+                            if (this._props.Doc.isTemplateDoc) {
+                                Doc.MakeMetadataFieldTemplate(created, this._props.Doc);
                             }
                             return this._props.addDocument?.(created);
                         }
@@ -270,7 +270,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                     event: () => {
                         const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
                         if (created) {
-                            const container = this._props.Document.resolvedDataDoc ? Doc.GetProto(this._props.Document) : this._props.Document;
+                            const container = this._props.Doc.resolvedDataDoc ? Doc.GetProto(this._props.Doc) : this._props.Doc;
                             if (container.isTemplateDoc) {
                                 Doc.MakeMetadataFieldTemplate(created, container);
                                 return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
@@ -285,11 +285,11 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
         !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
         !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
         ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
-            Doc.GetProto(this._props.Document)[name] = '';
+            Doc.GetProto(this._props.Doc)[name] = '';
             const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true });
             if (created) {
-                if (this._props.Document.isTemplateDoc) {
-                    Doc.MakeMetadataFieldTemplate(created, this._props.Document);
+                if (this._props.Doc.isTemplateDoc) {
+                    Doc.MakeMetadataFieldTemplate(created, this._props.Doc);
                 }
                 this._props.addDocument?.(created);
             }
@@ -350,10 +350,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
             </div>
         ) : null;
         const templatecols = `${this._props.columnWidth / this._props.numGroupColumns}px `;
-        const { type } = this._props.Document;
+        const { type } = this._props.Doc;
         return (
             <>
-                {this._props.Document._columnsHideIfEmpty ? null : headingView}
+                {this._props.Doc._columnsHideIfEmpty ? null : headingView}
                 {this.collapsed ? null : (
                     <div
                         style={{
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 29caead5b..a43cf0755 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -9,7 +9,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DateCast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
+import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
@@ -85,9 +85,6 @@ export function CollectionSubView<X>() {
         constructor(props: X & SubCollectionViewProps) {
             super(props);
             makeObservable(this);
-            console.log(`propsTitle: ${this._props.Document.title} DocTitle: ${this.Document.title} LayoutTitle:${this.layoutDoc.title} DataTitle:${this.dataDoc.title}`);
-            console.log(`tempTitle: ${this._props.TemplateDataDocument?.title} LayouTResolve: ${DocCast(this.layoutDoc.resolvedDataDoc)?.title} propDocResolve: ${DocCast(this._props.Document.resolvedDataDoc)?.title}`);
-            console.log('Children:', this.childDocs, this.childLayoutPairs);
         }
 
         @observable _focusFilters: Opt<string[]> = undefined; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it
@@ -111,7 +108,6 @@ export function CollectionSubView<X>() {
         }
 
         get dataDoc() {
-            console.log(this._props.Document.title + ' isTemplate: ' + this.layoutDoc.isTemplateForField);
             return this._props.TemplateDataDocument instanceof Doc && this.layoutDoc.isTemplateForField //
                 ? this._props.TemplateDataDocument[DocData]
                 : this.layoutDoc.resolvedDataDoc
@@ -135,7 +131,7 @@ export function CollectionSubView<X>() {
 
         hasChildDocs = () => this.childLayoutPairs.map(pair => pair.layout);
         @computed get childLayoutPairs(): { layout: Doc; data: Doc }[] {
-            const { Document, TemplateDataDocument } = this._props;
+            const { Document: Document, TemplateDataDocument } = this._props;
             const validPairs = this.childDocs
                 .map(doc => Doc.GetLayoutDataDocPair(Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
                 .filter(
@@ -303,7 +299,7 @@ export function CollectionSubView<X>() {
             const dragData = de.complete.docDragData;
             if (dragData) {
                 const sourceDragAction = dragData.dropAction;
-                const sameCollection = !dragData.draggedDocuments.some(d => d.embedContainer !== this._props.Document);
+                const sameCollection = !dragData.draggedDocuments.some(d => d.embedContainer !== this._renderDoc);
                 dragData.dropAction = !sameCollection // if doc from another tree
                     ? sourceDragAction || targetDropAction // then use the source's dragAction otherwise the target's
                     : sourceDragAction === dropActionType.inPlace // if source drag is inPlace
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 98bd06221..fd562e64f 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -121,7 +121,7 @@ export class CollectionTimeView extends CollectionSubView() {
                 {this.contents}
                 <div style={{ right: 0, top: 0, position: 'absolute' }}>
                     <FieldsDropdown
-                        Document={this.Document}
+                        Doc={this.Document}
                         selectFunc={fieldKey => {
                             this.layoutDoc._pivotField = fieldKey;
                         }}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e93724dd4..b7f49ac20 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -241,7 +241,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         );
     }
 
-    onKey = (e: React.KeyboardEvent /* , fieldProps: FieldViewProps */) => {
+    onKey = (e: React.KeyboardEvent /* , textBox: FormattedTextBox */) => {
         if (this.outlineMode && e.key === 'Enter') {
             e.stopPropagation();
             this.makeTextCollection(this.treeChildren);
@@ -252,7 +252,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     get documentTitle() {
         return (
             <FormattedTextBox
-                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props}
                 fieldKey="text"
                 renderDepth={this._props.renderDepth + 1}
@@ -471,7 +470,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
                 {!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? (
                     <CollectionFreeFormView
-                        // eslint-disable-next-line react/jsx-props-no-spreading
                         {...this._props}
                         setContentViewBox={emptyFunction}
                         NativeWidth={returnZero}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 2d2c0fce5..620be2726 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -43,7 +43,7 @@ import './TabDocView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 
 interface TabMinimapViewProps {
-    document: Doc;
+    doc: Doc;
     tabView: () => DocumentView | undefined;
     addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
     PanelWidth: () => number;
@@ -100,15 +100,15 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
         return bounds === undefined ? bounds : { l: bounds.x + width / 2 - dim / 2, t: bounds.y + height / 2 - dim / 2, cx, cy, 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));
+        return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.doc._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));
+        return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.doc._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);
+    childLayoutTemplate = () => Cast(this._props.doc.childLayoutTemplate, Doc, null);
+    returnMiniSize = () => NumCast(this._props.doc._miniMapSize, 150);
     miniDown = (e: React.PointerEvent) => {
-        const doc = this._props.document;
+        const doc = this._props.doc;
         const miniSize = this.returnMiniSize();
         doc &&
             setupMoveUpEvents(
@@ -127,15 +127,15 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
     popup = () => {
         const { renderBounds } = this;
         if (!renderBounds) return <div />;
-        const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
-        const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
-        const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
-        const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
+        const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.doc._freeform_scale, 1) / renderBounds.dim) * 100;
+        const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.doc._freeform_scale, 1) / renderBounds.dim) * 100;
+        const miniLeft = () => 50 + ((NumCast(this._props.doc._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
+        const miniTop = () => 50 + ((NumCast(this._props.doc._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
         const miniSize = this.returnMiniSize();
         return (
             <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
                 <CollectionFreeFormView
-                    Document={this._props.document}
+                    Document={this._props.doc}
                     docViewPath={returnEmptyDocViewList}
                     childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
                     noOverlay // don't render overlay Docs since they won't scale
@@ -144,7 +144,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
                     select={emptyFunction}
                     isSelected={returnFalse}
                     dontRegisterView
-                    fieldKey={Doc.LayoutFieldKey(this._props.document)}
+                    fieldKey={Doc.LayoutFieldKey(this._props.doc)}
                     addDocument={returnFalse}
                     moveDocument={returnFalse}
                     removeDocument={returnFalse}
@@ -172,7 +172,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
         );
     };
     render() {
-        return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : (
+        return this._props.doc.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.doc)) || this._props.doc?._type_collection !== CollectionViewType.Freeform ? null : (
             <div className="miniMap-hidden">
                 <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
             </div>
@@ -631,7 +631,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                 }}>
                 {!this._activated || !this._document ? null : this.renderDocView(this._document)}
                 {this.disableMinimap() || !this._document ? null : (
-                    <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />
+                    <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} doc={this._document} tabView={this.tabView} />
                 )}
             </div>
         );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 35c6d30fe..89d2bf2c3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -12,8 +12,8 @@ import { CollectionFreeFormView } from './CollectionFreeFormView';
 import './CollectionFreeFormView.scss';
 
 export interface CollectionFreeFormInfoUIProps {
-    Document: Doc;
-    LayoutDoc: Doc;
+    Doc: Doc;
+    layoutDoc: Doc;
     childDocs: () => Doc[];
     close: () => void;
 }
@@ -23,7 +23,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
     public static Init() {
         CollectionFreeFormView.SetInfoUICreator((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => (
             //
-            <CollectionFreeFormInfoUI Document={doc} LayoutDoc={layout} childDocs={childDocs} close={close} />
+            <CollectionFreeFormInfoUI Doc={doc} layoutDoc={layout} childDocs={childDocs} close={close} />
         ));
     }
     _firstDocPos = { x: 0, y: 0 };
@@ -40,7 +40,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
     set currState(val) { runInAction(() => {this._currState = val;}); } // prettier-ignore
 
     componentWillUnmount(): void {
-        this._props.Document.$backgroundColor = this._originalbackground;
+        this._props.Doc.$backgroundColor = this._originalbackground;
     }
 
     setCurrState = (state: infoState) => {
@@ -51,10 +51,10 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio
     };
 
     setupStates = () => {
-        this._originalbackground = StrCast(this._props.Document.$backgroundColor);
+        this._originalbackground = StrCast(this._props.Doc.$backgroundColor);
         //  state entry functions
-        // const setBackground = (colour: string) => () => {this._props.Document.$backgroundColor = colour;} // prettier-ignore
-        // const setOpacity = (opacity: number) => () => {this._props.LayoutDoc.opacity = opacity;} // prettier-ignore
+        // const setBackground = (colour: string) => () => {this._props.Doc.$backgroundColor = colour;} // prettier-ignore
+        // const setOpacity = (opacity: number) => () => {this._props.layoutDoc.opacity = opacity;} // prettier-ignore
         //  arc transition trigger conditions
         const firstDoc = () => (this._props.childDocs().length ? this._props.childDocs()[0] : undefined);
         const numDocs = () => this._props.childDocs().length;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index bc9dd022c..2683d9439 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -7,7 +7,7 @@ import { ObservableReactComponent } from '../../ObservableReactComponent';
 import './CollectionFreeFormView.scss';
 
 export interface CollectionFreeFormPannableContentsProps {
-    Document: Doc;
+    Doc: Doc;
     viewDefDivClick?: ScriptField;
     children?: React.ReactNode | undefined;
     transition: () => string;
@@ -33,7 +33,7 @@ export class CollectionFreeFormPannableContents extends ObservableReactComponent
         makeObservable(this);
     }
     @computed get presPaths() {
-        return this._props.showPresPaths() ? CollectionFreeFormPannableContents._overlayPlugin?.(this._props.Document) : null;
+        return this._props.showPresPaths() ? CollectionFreeFormPannableContents._overlayPlugin?.(this._props.Doc) : null;
     }
     // rectangle highlight used when following trail/link to a region of a collection that isn't a document
     showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) =>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index f64c6715b..86310dca3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -13,7 +13,7 @@ import './CollectionFreeFormView.scss';
 @observer
 export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
     @computed protected get cursors(): CursorField[] {
-        const { Document } = this.props;
+        const { Document: Document } = this.props;
         const cursors = Cast(Document.cursors, listSpec(CursorField));
         if (!cursors) {
             return [];
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6030e146c..25cec9c0d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,10 +1,13 @@
-import { Bezier } from 'bezier-js';
 import { Button, Colors, Type } from '@dash/components';
+import { Slider } from '@mui/material';
+import { Bezier } from 'bezier-js';
 import { Property } from 'csstype';
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
+import { AiOutlineSend } from 'react-icons/ai';
+import ReactLoading from 'react-loading';
 import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
@@ -28,6 +31,7 @@ import { DragManager } from '../../../util/DragManager';
 import { dropActionType } from '../../../util/DropActionTypes';
 import { CompileScript } from '../../../util/Scripting';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
+import { SettingsManager } from '../../../util/SettingsManager';
 import { freeformScrollMode, SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoable, UndoManager } from '../../../util/UndoManager';
@@ -37,26 +41,26 @@ import { InkingStroke } from '../../InkingStroke';
 import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
 import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
 import {
+    ActiveEraserWidth,
     ActiveInkArrowEnd,
     ActiveInkArrowStart,
-    ActiveInkDash,
-    ActiveEraserWidth,
-    ActiveInkFillColor,
     ActiveInkBezierApprox,
     ActiveInkColor,
+    ActiveInkDash,
+    ActiveInkFillColor,
     ActiveInkWidth,
     ActiveIsInkMask,
     DocumentView,
     SetActiveInkColor,
     SetActiveInkWidth,
 } from '../../nodes/DocumentView';
-import { FieldViewProps } from '../../nodes/FieldView';
 import { FocusViewOptions } from '../../nodes/FocusViewOptions';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { OpenWhere } from '../../nodes/OpenWhere';
 import { PinDocView, PinProps } from '../../PinFuncs';
-import { StickerPalette } from '../../smartdraw/StickerPalette';
+import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler';
 import { DrawingOptions, SmartDrawHandler } from '../../smartdraw/SmartDrawHandler';
+import { StickerPalette } from '../../smartdraw/StickerPalette';
 import { StyleProp } from '../../StyleProp';
 import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
 import { TreeViewType } from '../CollectionTreeViewType';
@@ -67,11 +71,6 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable
 import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
-import ReactLoading from 'react-loading';
-import { SettingsManager } from '../../../util/SettingsManager';
-import { Slider } from '@mui/material';
-import { AiOutlineSend } from 'react-icons/ai';
-import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler';
 
 @observer
 class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> {
@@ -180,10 +179,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
     }
     @computed get nativeWidth() {
-        return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+        return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document);
     }
     @computed get nativeHeight() {
-        return this._props.NativeHeight?.() || Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+        return this._props.NativeHeight?.() || Doc.NativeHeight(this.Document);
     }
     @computed get centeringShiftX(): number {
         return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : this._props.PanelWidth() / 2 / this.nativeDimScaling; // shift so pan position is at center of window for non-overlay collections
@@ -285,8 +284,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     // freeform_panx, freeform_pany, freeform_scale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document.
     // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
-    panX = () => this.fitContentBounds?.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
-    panY = () => this.fitContentBounds?.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
+    panX = () => this.fitContentBounds?.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(this.Document.freeform_panX, 1));
+    panY = () => this.fitContentBounds?.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(this.Document.freeform_panY, 1));
     zoomScaling = () => this.fitContentBounds?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
     PanZoomCenterXf = () => (this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.centeringShiftX}px, ${this.centeringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`);
     ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy();
@@ -1489,20 +1488,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
      * @param below whether to place the new text Doc below or to the right of the one being typed into.
      * @returns whether the new text doc was created and added successfully
      */
-    createTextDocCopy = undoable((fieldProps: FieldViewProps, below: boolean) => {
-        const textDoc = DocCast(fieldProps.Document.rootDocument, fieldProps.Document);
+    createTextDocCopy = undoable((textBox: FormattedTextBox, below: boolean) => {
+        const textDoc = DocCast(textBox.Document.rootDocument, textBox.Document);
         const newDoc = Doc.MakeCopy(textDoc, true);
-        newDoc['$' + Doc.LayoutFieldKey(newDoc, fieldProps.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
+        newDoc['$' + Doc.LayoutFieldKey(newDoc, textBox._props.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
         newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
         newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
         DocumentView.SetSelectOnLoad(newDoc);
         return this.addDocument?.(newDoc);
     }, 'copied text note');
 
-    onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
-        if ((e.metaKey || e.ctrlKey || e.altKey || fieldProps.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
+    onKeyDown = (e: React.KeyboardEvent, textBox: FormattedTextBox) => {
+        if ((e.metaKey || e.ctrlKey || e.altKey || textBox.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
             e.stopPropagation?.();
-            return this.createTextDocCopy(fieldProps, !e.altKey && e.key !== 'Tab');
+            return this.createTextDocCopy(textBox, !e.altKey && e.key !== 'Tab');
         }
         return undefined;
     };
@@ -1685,7 +1684,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
      * since rendering a large collection of documents can be slow, at startup, docs are rendered in batches.
      * each doc's render() method will call the cutoff provider which will let the doc know if it should render itself yet, or wait
      */
-    renderCutoffProvider = computedFn((doc: Doc) => (this._props.Document.isTemplateDoc || this._props.Document.isTemplateForField ? false : !this._renderCutoffData.get(doc[Id] + '')));
+    renderCutoffProvider = computedFn((doc: Doc) => (this.Document.isTemplateDoc || this.Document.isTemplateForField ? false : !this._renderCutoffData.get(doc[Id] + '')));
 
     doEngineLayout(
         poolData: Map<string, PoolData>,
@@ -2115,7 +2114,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.incrementalRender(); // needs to happen synchronously or freshly typed text documents will flash and miss their first characters
         return (
             <CollectionFreeFormPannableContents
-                Document={this.Document}
+                Doc={this.Document}
                 brushedView={this.brushedView}
                 isAnnotationOverlay={this.isAnnotationOverlay}
                 transform={this.PanZoomCenterXf}
@@ -2133,6 +2132,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             <MarqueeView
                 {...this._props}
                 ref={this._marqueeViewRef}
+                Doc={this.Document}
                 ungroup={this.Document.isGroup ? this.promoteCollection : undefined}
                 nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge}
                 addDocTab={this.addDocTab}
@@ -2149,7 +2149,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 isAnnotationOverlay={this.isAnnotationOverlay}>
                 {this.layoutDoc._freeform_backgroundGrid ? this.backgroundGrid : null}
                 {this.pannableContents}
-                {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this._props} /> : null}
+                {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this._props} Doc={this._props.Document} /> : null}
             </MarqueeView>
         );
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e3f4c6605..eaa8826ed 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -32,6 +32,7 @@ import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
 import './MarqueeView.scss';
 
 interface MarqueeViewProps {
+    Doc: Doc;
     getContainerTransform: () => Transform;
     getTransform: () => Transform;
     activeDocuments: () => Doc[];
diff --git a/src/client/views/collections/collectionSchema/SchemaCellField.tsx b/src/client/views/collections/collectionSchema/SchemaCellField.tsx
index e89822b4c..daffdf1f5 100644
--- a/src/client/views/collections/collectionSchema/SchemaCellField.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaCellField.tsx
@@ -21,11 +21,11 @@ import DOMPurify from 'dompurify';
  */
 
 export interface SchemaCellFieldProps {
+    Doc: Doc;
     contents: FieldType | undefined;
     fieldContents?: FieldViewProps;
     editing?: boolean;
     oneLine?: boolean;
-    Document: Doc;
     fieldKey: string;
     // eslint-disable-next-line no-use-before-define
     refSelectModeInfo: { enabled: boolean; currEditing: SchemaCellField | undefined };
@@ -55,7 +55,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
         }); //must be moved to end of batch or else other docs aren't loaded, so render as d-1 in function
     }
 
-    get docIndex(){return DocumentView.getDocViewIndex(this._props.Document);} // prettier-ignore
+    get docIndex(){return DocumentView.getDocViewIndex(this._props.Doc);} // prettier-ignore
 
     get selfRefPattern() {
         return `d${this.docIndex}.${this._props.fieldKey}`;
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index da203abfa..c9853fab0 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -171,7 +171,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
                             isolatedSelection={this.isolatedSelection}
                             key={key}
                             rowSelected={this._props.isSelected}
-                            Document={this.Document}
+                            Doc={this.Document}
                             col={index}
                             fieldKey={key}
                             allowCRs={false} // to enter text with new lines, must use \n
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 1b4f200a3..e6fe46638 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -40,7 +40,7 @@ import { SchemaCellField } from './SchemaCellField';
  */
 
 export interface SchemaTableCellProps {
-    Document: Doc;
+    Doc: Doc;
     col: number;
     deselectCell: () => void;
     selectCell: (doc: Doc, col: number, shift: boolean, ctrl: boolean) => void;
@@ -71,7 +71,7 @@ export interface SchemaTableCellProps {
 }
 
 function selectedCell(props: SchemaTableCellProps) {
-    return props.isRowActive() && props.selectedCol() === props.col && props.selectedCells()?.filter(d => d === props.Document)?.length;
+    return props.isRowActive() && props.selectedCol() === props.col && props.selectedCells()?.filter(d => d === props.Doc)?.length;
 }
 
 @observer
@@ -84,11 +84,11 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
         makeObservable(this);
     }
 
-    get docIndex(){return DocumentView.getDocViewIndex(this._props.Document);} // prettier-ignore
+    get docIndex(){return DocumentView.getDocViewIndex(this._props.Doc);} // prettier-ignore
 
     get isDefault(){return SchemaColumnHeader.isDefaultField(this._props.fieldKey);} // prettier-ignore
 
-    get lockedInteraction(){return (this.isDefault || this._props.Document._lockedSchemaEditing);} // prettier-ignore
+    get lockedInteraction(){return (this.isDefault || this._props.Doc._lockedSchemaEditing);} // prettier-ignore
 
     get backgroundColor() {
         if (this.lockedInteraction) {
@@ -102,7 +102,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
         return true;
     };
     public static renderProps(props: SchemaTableCellProps) {
-        const { Document, fieldKey, /* getFinfo,*/ columnWidth, isRowActive } = props;
+        const { Doc: Document, fieldKey, /* getFinfo,*/ columnWidth, isRowActive } = props;
         let protoCount = 0;
         let doc: Doc | undefined = Document;
         while (doc) {
@@ -196,7 +196,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
                 <SchemaCellField
                     fieldKey={this._props.fieldKey}
                     refSelectModeInfo={this._props.refSelectModeInfo}
-                    Document={this._props.Document}
+                    Doc={this._props.Doc}
                     highlightCells={(text: string) => this._props.highlightCells(this.adjustSelfReference(text))}
                     getCells={(text: string) => this._props.eqHighlightFunc(this.adjustSelfReference(text))}
                     ref={r => selectedCell(this._props) && this._props.autoFocus && r?.setIsFocused(true)}
@@ -224,7 +224,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
 
     get getCellType() {
         const columnTypeStr = this._props.getFinfo(this._props.fieldKey)?.fieldType;
-        const cellValue = this._props.Document[this._props.fieldKey];
+        const cellValue = this._props.Doc[this._props.fieldKey];
 
         if (cellValue instanceof ImageField) return ColumnType.Image;
         if (cellValue instanceof DateField) return ColumnType.Date;
@@ -252,8 +252,8 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
         const sides: Array<string | undefined> = [];
         sides[0] = selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // left
         sides[1] = selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // right
-        sides[2] = !this._props.isolatedSelection(this._props.Document)[0] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // top
-        sides[3] = !this._props.isolatedSelection(this._props.Document)[1] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // bottom
+        sides[2] = !this._props.isolatedSelection(this._props.Doc)[0] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // top
+        sides[3] = !this._props.isolatedSelection(this._props.Doc)[1] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // bottom
         return sides;
     }
 
@@ -272,7 +272,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
                     if (this._props.refSelectModeInfo.enabled && !selectedCell(this._props)) {
                         e.stopPropagation();
                         e.preventDefault();
-                        this._props.selectReference(this._props.Document, this._props.col);
+                        this._props.selectReference(this._props.Doc, this._props.col);
                         return;
                     }
 
@@ -280,9 +280,9 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
                     const ctrl: boolean = e.ctrlKey;
                     if (this._props.isRowActive?.()) {
                         if (selectedCell(this._props) && ctrl) {
-                            this._props.selectCell(this._props.Document, this._props.col, shift, ctrl);
+                            this._props.selectCell(this._props.Doc, this._props.col, shift, ctrl);
                             e.stopPropagation();
-                        } else !selectedCell(this._props) && this._props.selectCell(this._props.Document, this._props.col, shift, ctrl);
+                        } else !selectedCell(this._props) && this._props.selectCell(this._props.Doc, this._props.col, shift, ctrl);
                     }
                 })}
                 style={{
@@ -321,8 +321,8 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
     }
 
     get url() {
-        const field = Cast(this._props.Document[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
-        const alts = DocListCast(this._props.Document[this._props.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
+        const field = Cast(this._props.Doc[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
+        const alts = DocListCast(this._props.Doc[this._props.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
         const altpaths = alts
             .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
             .filter(url => url)
@@ -359,7 +359,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
     };
 
     render() {
-        const aspect = Doc.NativeAspect(this._props.Document); // aspect ratio
+        const aspect = Doc.NativeAspect(this._props.Doc); // aspect ratio
         // let width = Math.max(75, this._props.columnWidth); // get a with that is no smaller than 75px
         // const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px
         const height = this._props.rowHeight() ? this._props.rowHeight() - (this._props.padding || 6) * 2 : undefined;
@@ -379,7 +379,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
     @observable _pickingDate: boolean = false;
     @computed get date(): DateField {
         // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
-        return DateCast(this._props.Document[this._props.fieldKey]);
+        return DateCast(this._props.Doc[this._props.fieldKey]);
     }
 
     handleChange = undoable((date: Date | null) => {
@@ -388,7 +388,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
         //     this.applyToDoc(this._document, this._props.row, this._props.col, script.run);
         // } else {
         // ^ DateCast is always undefined for some reason, but that is what the field should be set to
-        date && (this._props.Document[this._props.fieldKey] = new DateField(date));
+        date && (this._props.Doc[this._props.fieldKey] = new DateField(date));
         // }
     }, 'date change');
 
@@ -451,11 +451,11 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp
                     onPointerDown={e => e.stopPropagation()}
                     style={{ marginRight: 4 }}
                     type="checkbox"
-                    checked={BoolCast(this._props.Document[this._props.fieldKey])}
+                    checked={BoolCast(this._props.Doc[this._props.fieldKey])}
                     onChange={undoable((value: React.ChangeEvent<HTMLInputElement> | undefined) => {
                         if ((value?.nativeEvent as MouseEvent | PointerEvent).shiftKey) {
                             this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? ''));
-                        } else Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? ''));
+                        } else Doc.SetField(this._props.Doc, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? ''));
                     }, 'set bool cell')}
                 />
 
@@ -463,14 +463,14 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp
                     contents=""
                     fieldContents={fieldProps}
                     editing={selectedCell(this._props) ? undefined : false}
-                    GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)}
+                    GetValue={() => Field.toKeyValueString(this._props.Doc, this._props.fieldKey)}
                     SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => {
                         if (shiftDown && enterKey) {
                             this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
                             this._props.finishEdit?.();
                             return true;
                         }
-                        const set = Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined);
+                        const set = Doc.SetField(this._props.Doc, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Doc) ? true : undefined);
                         this._props.finishEdit?.();
                         return set;
                     }, 'set bool cell')}
@@ -538,10 +538,10 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC
                         }}
                         menuPortalTarget={this._props.menuTarget}
                         menuPosition="absolute"
-                        placeholder={StrCast(this._props.Document[this._props.fieldKey], 'select...')}
+                        placeholder={StrCast(this._props.Doc[this._props.fieldKey], 'select...')}
                         options={options}
                         isMulti={false}
-                        onChange={val => Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)}
+                        onChange={val => Doc.SetField(this._props.Doc, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)}
                     />
                 </div>
             </div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index ce1e9280a..6f86383c2 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -304,7 +304,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 ) : (
                     <DocumentView
                         {...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore
-                        Document={this._props.Document}
+                        Document={this._renderDoc}
                         renderDepth={this._props.renderDepth}
                         isContentActive={this._props.isContentActive}
                         childFilters={this._props.childFilters}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index d5e37b3b5..117eb05f8 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,43 +1,39 @@
+import { Colors, Toggle, ToggleType, Type } from '@dash/components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Checkbox } from '@mui/material';
-import { Colors, Toggle, ToggleType, Type } from '@dash/components';
 import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { ClientUtils, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
+import { returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
 import { emptyFunction } from '../../../../Utils';
-import { Doc, DocListCast, Field, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
+import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
-import { PrefetchProxy } from '../../../../fields/Proxy';
 import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { CsvField } from '../../../../fields/URLField';
-import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
+import { TraceMobx } from '../../../../fields/util';
 import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';
 import { DocUtils } from '../../../documents/DocUtils';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Docs } from '../../../documents/Documents';
-import { LinkManager } from '../../../util/LinkManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { ViewBoxAnnotatableComponent } from '../../DocComponent';
 import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { PinProps } from '../../PinFuncs';
 import { SidebarAnnos } from '../../SidebarAnnos';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup';
 import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { FocusViewOptions } from '../FocusViewOptions';
 import './DataVizBox.scss';
-import { Col, DataVizTemplateInfo, DocCreatorMenu, LayoutType} from './DocCreatorMenu/DocCreatorMenu';
+import { Col, DocCreatorMenu } from './DocCreatorMenu/DocCreatorMenu';
+import { TemplateFieldSize, TemplateFieldType } from './DocCreatorMenu/TemplateBackend';
 import { Histogram } from './components/Histogram';
 import { LineChart } from './components/LineChart';
 import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
-import { TemplateFieldSize, TemplateFieldType } from './DocCreatorMenu/TemplateBackend';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -440,10 +436,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         };
         if (!this.records.length) return 'no data/visualization';
         switch (this.dataVizView) {
-            case DataVizView.TABLE:     return <TableBox  {...sharedProps} specHighlightedRow={this._specialHighlightedRow} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
-            case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} vizBox={this} />;
-            case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} />;
-            case DataVizView.PIECHART:  return <PieChart  {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} 
+            case DataVizView.TABLE:     return <TableBox  {...sharedProps} Doc={this.Document} specHighlightedRow={this._specialHighlightedRow} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
+            case DataVizView.LINECHART: return <LineChart {...sharedProps} Doc={this.Document} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} vizBox={this} />;
+            case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} Doc={this.Document} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} />;
+            case DataVizView.PIECHART:  return <PieChart  {...sharedProps} Doc={this.Document} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} 
             margin={{ top: 10, right: 15, bottom: 15, left: 15 }} />;
             default:
         } // prettier-ignore
@@ -574,9 +570,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
         const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined);
 
-        cols.forEach(col => {
-            this.setColumnDefault(col, `${this.records[rowToCheck][col]}`);
-        });
+        cols.forEach(col => this.setColumnDefault(col, `${this.records[rowToCheck][col]}`));
     };
 
     updateGPTSummary = async () => {
@@ -706,7 +700,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         ref={this._sidebarRef}
                         {...this._props}
                         fieldKey={this.fieldKey}
-                        Document={this.Document}
+                        Doc={this.Document}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
                         usePanelWidth
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 5a9442d2f..776d65211 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -17,7 +17,7 @@ import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
 import './Chart.scss';
 
 export interface HistogramProps {
-    Document: Doc;
+    Doc: Doc;
     layoutDoc: Doc;
     axes: string[];
     titleCol: string;
@@ -88,7 +88,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
     }
 
     @computed get parentViz() {
-        return DocCast(this._props.Document.dataViz_parentViz);
+        return DocCast(this._props.Doc.dataViz_parentViz);
     }
 
     @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
@@ -126,7 +126,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
         const anchor = Docs.Create.ConfigDocument({
             title: 'histogram doc selection' + this._currSelected,
         });
-        PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
+        PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Doc);
         return anchor;
     };
 
@@ -416,11 +416,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
                 let barColor;
                 const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
                 barColors.forEach(each => {
-                    // eslint-disable-next-line prefer-destructuring
                     if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
                     else {
                         const range = StrCast(each[0]).split(' to ');
-                        // eslint-disable-next-line prefer-destructuring
                         if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
                     }
                 });
@@ -454,11 +452,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
                 let barColor;
                 const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
                 barColors.forEach(each => {
-                    // eslint-disable-next-line prefer-destructuring
                     if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
                     else {
                         const range = StrCast(each[0]).split(' to ');
-                        // eslint-disable-next-line prefer-destructuring
                         if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
                     }
                 });
@@ -471,7 +467,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
         this.updateSavedUI();
         this._histogramData;
         let curSelectedBarName = '';
-        let titleAccessor: any = 'dataViz_histogram_title';
+        let titleAccessor = 'dataViz_histogram_title';
         if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
         else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
         if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
@@ -503,7 +499,6 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
         let selectedBarColor;
         const barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
         barColors.forEach(each => {
-            // eslint-disable-next-line prefer-destructuring
             each[0] === curSelectedBarName && (selectedBarColor = each[1]);
         });
 
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index b55d509ff..6b047546c 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -22,7 +22,7 @@ export interface SelectedDataPoint extends DataPoint {
 }
 export interface LineChartProps {
     vizBox: DataVizBox;
-    Document: Doc;
+    Doc: Doc;
     layoutDoc: Doc;
     axes: string[];
     titleCol: string;
@@ -53,7 +53,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
     }
 
     @computed get titleAccessor() {
-        let titleAccessor: any = 'dataViz_lineChart_title';
+        let titleAccessor = 'dataViz_lineChart_title';
         if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
         else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
         return titleAccessor;
@@ -74,7 +74,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
         return this._props.axes[1] + ' vs. ' + this._props.axes[0] + ' Line Chart';
     }
     @computed get parentViz() {
-        return DocCast(this._props.Document.dataViz_parentViz);
+        return DocCast(this._props.Doc.dataViz_parentViz);
     }
     @computed get incomingHighlited() {
         // return selected x and y axes
@@ -113,7 +113,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
             //
             title: 'line doc selection' + (this._currSelected?.x ?? ''),
         });
-        PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
+        PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Doc);
         anchor.config_dataVizSelection = this._currSelected ? new List<number>([this._currSelected.x, this._currSelected.y]) : undefined;
         return anchor;
     };
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 86e6ad8e4..0ae70786f 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -16,7 +16,7 @@ import { PinProps, PinDocView } from '../../../PinFuncs';
 import './Chart.scss';
 
 export interface PieChartProps {
-    Document: Doc;
+    Doc: Doc;
     layoutDoc: Doc;
     axes: string[];
     titleCol: string;
@@ -83,7 +83,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
     }
 
     @computed get parentViz() {
-        return DocCast(this._props.Document.dataViz_parentViz);
+        return DocCast(this._props.Doc.dataViz_parentViz);
     }
 
     componentWillUnmount() {
@@ -114,7 +114,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
             //
             title: 'piechart doc selection' + this._currSelected,
         });
-        PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
+        PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Doc);
         return anchor;
     };
 
@@ -169,7 +169,6 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
                 // inside the slice of it crosses an odd number of edges
                 const showSelected = this.byCategory ? pieDataSet[index] : this._pieChartData[index];
                 let key = 'data'; // key that represents slice
-                // eslint-disable-next-line prefer-destructuring
                 if (Object.keys(showSelected)[0] === 'frequency') key = Object.keys(showSelected)[1];
                 if (changeSelectedVariables) {
                     let sameAsAny = false;
@@ -296,7 +295,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
             if (descriptionField) dataPointVal[descriptionField] = each[descriptionField];
             try {
                 dataPointVal[percentField] = Number(dataPointVal[percentField].replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, ''));
-            } catch (error) {
+            } catch {
                 /* empty */
             }
             possibleDataPointVals.push(dataPointVal);
@@ -306,7 +305,6 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
         // to make sure all important slice information is on 'd' object
         let addKey: any = false;
         if (pieDataSet.length && Object.keys(pieDataSet[0])[0] === 'frequency') {
-            // eslint-disable-next-line prefer-destructuring
             addKey = Object.keys(pieDataSet[0])[1];
         }
         arcs.append('path')
@@ -324,7 +322,6 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
                     const sliceTitle = dataPoint[this._props.axes[0]];
                     const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle;
                     sliceColors.forEach(each => {
-                        // eslint-disable-next-line prefer-destructuring
                         each[0] === accessByName && (sliceColor = each[1]);
                     });
                 }
@@ -337,7 +334,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
                 });
                 return selectThisData ? 'slice hover' : 'slice';
             })
-            // @ts-ignore
+            // @ts-expect-error  types don't match
             .attr('d', arc)
             .on('click', onPointClick)
             .on('mouseover', onHover)
@@ -388,7 +385,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
     };
 
     render() {
-        let titleAccessor: any = 'dataViz_pie_title';
+        let titleAccessor = 'dataViz_pie_title';
         if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
         else if (this._props.axes.length > 0) titleAccessor += this._props.axes[0];
         if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
@@ -420,7 +417,6 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
         let selectedSliceColor;
         const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
         sliceColors.forEach(each => {
-            // eslint-disable-next-line prefer-destructuring
             if (each[0] === curSelectedSliceName!) selectedSliceColor = each[1];
         });
 
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index b6183946a..b73123691 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -19,7 +19,7 @@ import './Chart.scss';
 const { DATA_VIZ_TABLE_ROW_HEIGHT } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
 
 interface TableBoxProps {
-    Document: Doc;
+    Doc: Doc;
     layoutDoc: Doc;
     records: { [key: string]: unknown }[];
     selectAxes: (axes: string[]) => void;
@@ -81,7 +81,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
     }
 
     @computed get parentViz() {
-        return DocCast(this._props.Document.dataViz_parentViz);
+        return DocCast(this._props.Doc.dataViz_parentViz);
     }
 
     @computed get columns() {
@@ -139,21 +139,21 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
             e,
             moveEv => {
                 // dragging off a column to create a brushed DataVizBox
-                const sourceAnchorCreator = () => this._props.docView?.()?.Document || this._props.Document;
+                const sourceAnchorCreator = () => this._props.docView?.()?.Document || this._props.Doc;
                 const targetCreator = (annotationOn: Doc | undefined) => {
                     const doc = this._props.docView?.()?.Document;
                     if (doc) {
                         const embedding = Doc.MakeEmbedding(doc);
                         embedding._dataViz = DataVizView.TABLE;
                         embedding._dataViz_axes = new List<string>([col]);
-                        embedding._dataViz_parentViz = this._props.Document;
+                        embedding._dataViz_parentViz = this._props.Doc;
                         embedding.annotationOn = annotationOn;
                         embedding.histogramBarColors = Field.Copy(this._props.layoutDoc.histogramBarColors);
                         embedding.defaultHistogramColor = this._props.layoutDoc.defaultHistogramColor;
                         embedding.pieSliceColors = Field.Copy(this._props.layoutDoc.pieSliceColors);
                         return embedding;
                     }
-                    return this._props.Document;
+                    return this._props.Doc;
                 };
                 if (this._props.docView?.() && !ClientUtils.isClick(moveEv.clientX, moveEv.clientY, downX, downY, Date.now())) {
                     DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e9353b001..c355e57d4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -758,7 +758,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     }}>
                     <DocumentContentsView
                         {...this._props}
-                        layoutFieldKey={StrCast(this._props.Document.layout_fieldKey, 'layout')}
+                        layoutFieldKey={StrCast(this._renderDoc.layout_fieldKey, 'layout')}
                         pointerEvents={this.contentPointerEvents}
                         setContentViewBox={this.setContentView}
                         childFilters={this.childFilters}
@@ -831,7 +831,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             onPointerDown={action(() => { this._changingTitleField = true; })} // prettier-ignore
             style={{ width: 'max-content', background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
             <FieldsDropdown
-                Document={this.Document}
+                Doc={this.Document}
                 placeholder={placeholder}
                 selectFunc={action((field: string | number) => {
                     if (this.layoutDoc.layout_showTitle) {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 2e40f39ed..abba99c3d 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -15,6 +15,7 @@ import { FocusViewOptions } from './FocusViewOptions';
 import { OpenWhere } from './OpenWhere';
 import { WebField } from '../../../fields/URLField';
 import { ContextMenuProps } from '../ContextMenuItem';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
 
 export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>;
 export type StyleProviderFuncType = (
@@ -83,8 +84,7 @@ export interface FieldViewSharedProps {
     onDoubleClickScript?: () => ScriptField;
     onPointerDownScript?: () => ScriptField;
     onPointerUpScript?: () => ScriptField;
-    // eslint-disable-next-line no-use-before-define
-    onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
+    onKey?: (e: React.KeyboardEvent, textBox: FormattedTextBox) => boolean | undefined;
     fitWidth?: (doc: Doc) => boolean | undefined;
     dontCenter?: 'x' | 'y' | 'xy' | undefined;
     searchFilterDocs: () => Doc[];
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5d9718760..d121b492f 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -648,7 +648,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                             onClick={action(async () => {
                                 this._regenerateLoading = true;
                                 if (this._fireflyRefStrength) {
-                                    DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title), this.Document)?.then(action(() => (this._regenerateLoading = false)));
+                                    DrawingFillHandler.drawingToImage(this.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title), this.Document)?.then(action(() => (this._regenerateLoading = false)));
                                 } else {
                                     SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput || StrCast(this.Document.title), true).then(
                                         action(newImgs => {
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 32c9efbd9..8911fac6d 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -58,8 +58,8 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
     onEnterKey = (e: React.KeyboardEvent): void => {
         if (e.key === 'Enter') {
             e.stopPropagation();
-            if (this._keyInput.current?.value && this._valInput.current?.value && this._props.Document) {
-                if (KeyValueBox.SetField(this._props.Document, this._keyInput.current.value, this._valInput.current.value)) {
+            if (this._keyInput.current?.value && this._valInput.current?.value && this.Document) {
+                if (KeyValueBox.SetField(this.Document, this._keyInput.current.value, this._valInput.current.value)) {
                     this._keyInput.current.value = '';
                     this._valInput.current.value = '';
                     document.body.focus();
@@ -137,7 +137,7 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
     rowHeight = () => 30;
 
     @computed get createTable() {
-        const doc = this._props.Document;
+        const doc = this.Document;
         if (!doc) {
             return (
                 <tr>
@@ -305,8 +305,8 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
         openItems.push({
             description: 'Default Perspective',
             event: () => {
-                this._props.addDocTab(this._props.Document, OpenWhere.close);
-                this._props.addDocTab(this._props.Document, OpenWhere.addRight);
+                this._props.addDocTab(this.Document, OpenWhere.close);
+                this._props.addDocTab(this.Document, OpenWhere.addRight);
             },
             icon: 'image',
         });
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 65d78f754..a563b7c1b 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1300,7 +1300,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         ref={this._sidebarRef}
                         {...this._props}
                         fieldKey={this.fieldKey}
-                        Document={this.Document}
+                        Doc={this.Document}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
                         usePanelWidth
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index ddeaf4d0d..e0efab576 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -798,7 +798,6 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                                           PanelHeight={returnOne}
                                           NativeWidth={returnOne}
                                           NativeHeight={returnOne}
-                                          onKey={undefined}
                                           onDoubleClickScript={undefined}
                                           childFilters={returnEmptyFilter}
                                           childFiltersByRanges={returnEmptyFilter}
@@ -830,7 +829,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
                         ref={this._sidebarRef}
                         {...this._props}
                         fieldKey={this.fieldKey}
-                        Document={this.Document}
+                        Doc={this.Document}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
                         usePanelWidth
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 5484179be..78ddafa88 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -535,7 +535,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 <SidebarAnnos
                     ref={this._sidebarRef}
                     {...this._props}
-                    Document={this.Document}
+                    Doc={this.Document}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
                     setHeight={emptyFunction}
@@ -606,6 +606,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         pdfBox={this}
                         sidebarAddDoc={this.sidebarAddDocument}
                         addDocTab={this.sidebarAddDocTab}
+                        Doc={this.Document}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
                         pdf={this._pdf}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 6289470b6..999f9c1cd 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -301,7 +301,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 <div className="videoBox-viewer">
                     <div style={{ position: 'relative', height: this.videoPanelHeight() }}>
                         <CollectionFreeFormView
-                            // eslint-disable-next-line react/jsx-props-no-spreading
                             {...this._props}
                             setContentViewBox={emptyFunction}
                             NativeWidth={returnZero}
@@ -329,7 +328,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                     <div style={{ background: SettingsManager.userColor, position: 'relative', height: this.formattedPanelHeight() }}>
                         {!(this.dataDoc[this.fieldKey + '_dictation'] instanceof Doc) ? null : (
                             <FormattedTextBox
-                                // eslint-disable-next-line react/jsx-props-no-spreading
                                 {...this._props}
                                 Document={DocCast(this.dataDoc[this.fieldKey + '_dictation'])}
                                 fieldKey="text"
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index e7a10cc29..4b3f96bcf 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1220,7 +1220,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         {...this._props}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                         fieldKey={this.fieldKey + '_' + this._urlHash}
-                        Document={this.Document}
+                        Doc={this.Document}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
                         setHeight={emptyFunction}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index aa2829aaf..bb0efa917 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -162,7 +162,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
                     this._expanded = !this._props.editable ? false : !this._expanded;
                 })}>
                 <SchemaTableCell
-                    Document={this._dashDoc}
+                    Doc={this._dashDoc}
                     col={0}
                     deselectCell={emptyFunction}
                     selectCell={() => (this._expanded ? true : undefined)}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9078648e9..925109bfb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -78,14 +78,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
-    public static MakeConfig(rules?: RichTextRules, props?: FormattedTextBoxProps) {
+    public static MakeConfig(rules?: RichTextRules, textBox?: FormattedTextBox) {
         return {
             schema,
             plugins: [
                 inputRules(rules?.inpRules ?? { rules: [] }),
-                ...(props ? [FormattedTextBox.richTextMenuPlugin(props)] : []),
+                ...(textBox?._props ? [FormattedTextBox.richTextMenuPlugin(textBox._props)] : []),
                 history(),
-                keymap(buildKeymap(schema, props ?? {})),
+                keymap(buildKeymap(schema, textBox)),
                 keymap(baseKeymap),
                 new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }),
                 new Plugin({ view: () => new FormattedTextBoxComment() }),
@@ -153,7 +153,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     }
 
     // eslint-disable-next-line no-return-assign
-    @computed get config()                     { return FormattedTextBox.MakeConfig(this._rules = new RichTextRules(this.Document, this), this._props); } // prettier-ignore
+    @computed get config()                     { return FormattedTextBox.MakeConfig(this._rules = new RichTextRules(this.Document, this), this); } // prettier-ignore
     @computed get _recordingDictation()        { return this.dataDoc?.mediaState === mediaState.Recording; } // prettier-ignore
     @computed get SidebarShown()               {  return !!(this._showSidebar || this.layoutDoc._layout_showSidebar);  } // prettier-ignore
     @computed get allSidebarDocs()             { return DocListCast(this.dataDoc[this.sidebarKey]); } // prettier-ignore
@@ -1954,7 +1954,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 <SidebarAnnos
                     ref={this._sidebarRef}
                     {...this._props}
-                    Document={this.Document}
+                    Doc={this.Document}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
                     usePanelWidth
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 3c84e5a10..a75efd77b 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -1,8 +1,8 @@
 import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands';
 import { redo, undo } from 'prosemirror-history';
-import { Schema } from 'prosemirror-model';
+import { MarkType, Node, ResolvedPos, Schema } from 'prosemirror-model';
 import { liftListItem, sinkListItem, splitListItem, wrapInList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
+import { Command, EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state';
 import { liftTarget } from 'prosemirror-transform';
 import { EditorView } from 'prosemirror-view';
 import { ClientUtils } from '../../../../ClientUtils';
@@ -13,17 +13,18 @@ import { Docs } from '../../../documents/Documents';
 import { RTFMarkup } from '../../../util/RTFMarkup';
 import { DocumentView } from '../DocumentView';
 import { OpenWhere } from '../OpenWhere';
+import { FormattedTextBox } from './FormattedTextBox';
 
 const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
 
-export type KeyMap = { [key: string]: any };
+export type KeyMap = { [key: string]: Command };
 
 export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => {
     let mapStyle = assignedMapStyle;
-    tx2.doc.descendants((node: any, offset: any /* , index: any */) => {
+    tx2.doc.descendants((node: Node, offset: number /* , index: any */) => {
         if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) {
-            const { path } = tx2.doc.resolve(offset) as any;
-            let depth = Array.from(path).reduce((p: number, c: any) => p + (c.type === schema.nodes.ordered_list ? 1 : 0), 0);
+            const { path } = tx2.doc.resolve(offset) as unknown as { path: (number | Node)[] }; // bcz: can't access path .. need to FIX
+            let depth = Array.from(path).reduce((p: number, c: number | Node) => p + ((c as Node).type === schema.nodes.ordered_list ? 1 : 0), 0);
             if (node.type === schema.nodes.ordered_list) {
                 if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle;
                 depth++;
@@ -34,25 +35,25 @@ export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle
     return tx2;
 };
 
-export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMap {
-    const keys: { [key: string]: any } = {};
+export function buildKeymap<S extends Schema<string>>(schema: S, tbox?: FormattedTextBox): KeyMap {
+    const keys: { [key: string]: Command } = {};
 
-    function bind(key: string, cmd: any) {
+    function bind(key: string, cmd: Command) {
         keys[key] = cmd;
     }
 
     function onKey(): boolean | undefined {
         // bcz: this is pretty hacky -- prosemirror doesn't send us the keyboard event, but the 'event' variable is in scope.. so we access it anyway
-        // eslint-disable-next-line no-restricted-globals
-        return props.onKey?.(event, props);
+        return tbox?._props.onKey?.(event, tbox);
     }
 
-    const canEdit = (state: any) => {
-        const permissions = GetEffectiveAcl(props.TemplateDataDocument ?? props.Document[DocData]);
+    const canEdit = (state: EditorState) => {
+        if (!tbox) return true;
+        const permissions = GetEffectiveAcl(tbox._props.TemplateDataDocument ?? tbox.Document[DocData]);
         switch (permissions) {
             case AclAugment:
                 {
-                    const prevNode = state.selection.$cursor.nodeBefore;
+                    const prevNode = (state.selection as any).$cursor.nodeBefore;
                     const prevUser = !prevNode ? ClientUtils.CurrentUserEmail() : prevNode.marks.lastElement()?.attrs.userid;
                     if (prevUser !== ClientUtils.CurrentUserEmail()) {
                         return false;
@@ -64,7 +65,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
         return true;
     };
 
-    const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch);
+    const toggleEditableMark = (mark: MarkType) => (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && toggleMark(mark)(state, dispatch);
 
     // History commands
     bind('Mod-z', undo);
@@ -84,13 +85,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
     bind('Mod-U', toggleEditableMark(schema.marks.underline));
 
     // Commands for lists
-    bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any));
+    bind('Ctrl-i', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state, dispatch));
 
     bind('Ctrl-Tab', () => onKey() || true);
     bind('Alt-Tab', () => onKey() || true);
     bind('Meta-Tab', () => onKey() || true);
     bind('Meta-Enter', () => onKey() || true);
-    bind('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+    bind('Tab', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
         if (onKey()) return true;
         if (!canEdit(state)) return true;
         const ref = state.selection;
@@ -101,13 +102,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
                 const tx3 = updateBullets(tx2, schema);
                 marks && tx3.ensureMarks([...marks]);
                 marks && tx3.setStoredMarks([...marks]);
-                dispatch(tx3);
+                dispatch?.(tx3);
             })
         ) {
             // couldn't sink into an existing list, so wrap in a new one
             const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
             if (
-                !wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
+                !wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
                     const tx25 = updateBullets(tx2, schema);
                     const olNode = tx25.doc.nodeAt(range!.start)!;
                     const tx3 = tx25.setNodeMarkup(range!.start, olNode.type, olNode.attrs, marks);
@@ -115,16 +116,16 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
                     marks && tx3.ensureMarks([...marks]);
                     marks && tx3.setStoredMarks([...marks]);
                     const tx4 = tx3.setSelection(TextSelection.near(tx3.doc.resolve(state.selection.to + 2)));
-                    dispatch(tx4);
+                    dispatch?.(tx4);
                 })
             ) {
                 console.log('bullet promote fail');
             }
         }
-        return undefined;
+        return false;
     });
 
-    bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+    bind('Shift-Tab', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
         if (onKey()) return true;
         if (!canEdit(state)) return true;
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
@@ -134,72 +135,83 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
                 const tx3 = updateBullets(tx2, schema);
                 marks && tx3.ensureMarks([...marks]);
                 marks && tx3.setStoredMarks([...marks]);
-                dispatch(tx3);
+                dispatch?.(tx3);
             })
         ) {
             console.log('bullet demote fail');
         }
-        return undefined;
+        return false;
     });
 
     // Command to create a new Tab with a PDF of all the command shortcuts
-    bind('Mod-/', () => {
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    bind('Mod-/', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
         const newDoc = Docs.Create.PdfDocument(ClientUtils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
-        props.addDocTab(newDoc, OpenWhere.addRight);
+        tbox?._props.addDocTab(newDoc, OpenWhere.addRight);
+        return false;
     });
 
     // Commands to modify BlockType
-    bind('Ctrl->', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any)));
-    bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any));
-    bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any));
+    bind('Ctrl->', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && wrapIn(schema.nodes.blockquote)(state, dispatch));
+    bind('Alt-\\', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state, dispatch));
+    bind('Shift-Ctrl-\\', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && setBlockType(schema.nodes.code_block)(state, dispatch));
 
-    bind('Ctrl-m', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+    bind('Ctrl-m', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
         if (canEdit(state)) {
             const tr = state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: 'math' + Utils.GenerateGuid() }));
-            dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(tr.selection.$from.pos - 1))));
+            dispatch?.(tr.setSelection(new NodeSelection(tr.doc.resolve(tr.selection.$from.pos - 1))));
+            return true;
         }
+        return false;
     });
 
     for (let i = 1; i <= 6; i++) {
-        bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any));
+        bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state, dispatch));
     }
 
     // Command to create a horizontal break line
     const hr = schema.nodes.horizontal_rule;
-    bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()));
+    bind('Mod-_', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+        if (canEdit(state)) {
+            dispatch?.(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
+            return true;
+        }
+        return false;
+    });
 
     // Command to unselect all
-    bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => {
-        dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
-        (document.activeElement as any).blur?.();
+    bind('Escape', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+        dispatch?.(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
+        (document.activeElement as HTMLElement)?.blur?.();
         DocumentView.DeselectAll();
+        return true;
     });
 
     bind('Alt-Enter', () => onKey() || true);
     bind('Ctrl-Enter', () => onKey() || true);
-    bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => {
-        dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
+    bind('Cmd-a', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+        dispatch?.(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
         return true;
     });
     bind('Cmd-?', () => {
         RTFMarkup.Instance.setOpen(true);
         return true;
     });
-    bind('Cmd-e', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+    bind('Cmd-e', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
         if (!state.selection.empty) {
             const mark = state.schema.marks.summarizeInclusive.create();
             const tr = state.tr.addMark(state.selection.$from.pos, state.selection.$to.pos, mark);
             const content = tr.selection.content();
-            tr.selection.replaceWith(tr, schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }));
-            dispatch(tr);
+            tr.selection.replaceWith(tr, schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }, undefined, state.selection.$anchor.marks() ?? []));
+            dispatch?.(tr);
         }
         return true;
     });
-    bind('Cmd-]', (state: EditorState, dispatch: (tx: Transaction) => void) => {
-        const resolved = state.doc.resolve(state.selection.from) as any;
+    bind('Cmd-]', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+        const resolved = state.doc.resolve(state.selection.from) as ResolvedPos & { path: (Node | number)[] }; // bcz: this is bad. path is not publically visible
         const { tr } = state;
         if (resolved?.parent.type.name === 'paragraph') {
-            tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
+            tr.setNodeMarkup(resolved.path[resolved.path.length - 4] as number, schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
         } else {
             const node = resolved.nodeAfter;
             const sm = state.storedMarks || undefined;
@@ -207,14 +219,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
                 tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]);
             }
         }
-        dispatch(tr);
+        dispatch?.(tr);
         return true;
     });
-    bind('Cmd-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => {
-        const resolved = state.doc.resolve(state.selection.from) as any;
+    bind('Cmd-\\', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+        const resolved = state.doc.resolve(state.selection.from) as ResolvedPos & { path: (Node | number)[] }; // bcz: this is bad. path is not publically visible
         const { tr } = state;
         if (resolved?.parent.type.name === 'paragraph') {
-            tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
+            tr.setNodeMarkup(resolved.path[resolved.path.length - 4] as number, schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
         } else {
             const node = resolved.nodeAfter;
             const sm = state.storedMarks || undefined;
@@ -222,14 +234,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
                 tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]);
             }
         }
-        dispatch(tr);
+        dispatch?.(tr);
         return true;
     });
-    bind('Cmd-[', (state: EditorState, dispatch: (tx: Transaction) => void) => {
-        const resolved = state.doc.resolve(state.selection.from) as any;
+    bind('Cmd-[', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+        const resolved = state.doc.resolve(state.selection.from) as ResolvedPos & { path: (Node | number)[] }; // bcz: this is bad. path is not publically visible
         const { tr } = state;
         if (resolved?.parent.type.name === 'paragraph') {
-            tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
+            tr.setNodeMarkup(resolved.path[resolved.path.length - 4] as number, schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
         } else {
             const node = resolved.nodeAfter;
             const sm = state.storedMarks || undefined;
@@ -237,16 +249,16 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
                 tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]);
             }
         }
-        dispatch(tr);
+        dispatch?.(tr);
         return true;
     });
 
-    bind('Cmd-f', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+    bind('Cmd-f', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
         const content = state.tr.selection.empty ? undefined : state.tr.selection.content().content.textBetween(0, state.tr.selection.content().size + 1);
         const newNode = schema.nodes.footnote.create({}, content ? state.schema.text(content) : undefined);
         const { tr } = state;
         tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
-        dispatch(
+        dispatch?.(
             tr.setSelection(
                 new NodeSelection( // select the footnote node to open its display
                     tr.doc.resolve(
@@ -259,25 +271,25 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
         return true;
     });
 
-    bind('Ctrl-a', (state: EditorState, dispatch: (tx: Transaction) => void) => {
-        dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
+    bind('Ctrl-a', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => {
+        dispatch?.(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
         return true;
     });
 
     // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
-    const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => {
+    const backspace = (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined, view?: EditorView) => {
         if (onKey()) return true;
         if (!canEdit(state)) return true;
 
         if (
             !deleteSelection(state, (tx: Transaction) => {
-                dispatch(updateBullets(tx, schema));
+                dispatch?.(updateBullets(tx, schema));
             })
         ) {
             if (
                 !joinBackward(state, (tx: Transaction) => {
-                    dispatch(updateBullets(tx, schema));
-                    if (view.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) {
+                    dispatch?.(updateBullets(tx, schema));
+                    if (view?.state.selection.$anchor.node(-1)?.type === schema.nodes.list_item) {
                         // gets rid of an extra paragraph when joining two list items together.
                         joinBackward(view.state, (tx2: Transaction) => view.dispatch(tx2));
                     }
@@ -285,7 +297,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
             ) {
                 if (
                     !selectNodeBackward(state, (tx: Transaction) => {
-                        dispatch(updateBullets(tx, schema));
+                        dispatch?.(updateBullets(tx, schema));
                     })
                 ) {
                     return false;
@@ -299,7 +311,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
     // newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
     // command to break line
 
-    const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => {
+    const enter = (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined, view?: EditorView, once = true) => {
         if (onKey()) return true;
         if (!canEdit(state)) return true;
 
@@ -311,31 +323,31 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
             !state.selection.$from.node().content.size &&
             trange
         ) {
-            dispatch(state.tr.lift(trange, depth) as any);
+            dispatch?.(state.tr.lift(trange, depth));
             return true;
         }
 
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        if (!newlineInCode(state, dispatch as any)) {
-            const olNode = view.state.selection.$anchor.node(-2);
-            const liNode = view.state.selection.$anchor.node(-1);
+        if (!newlineInCode(state, dispatch)) {
+            const olNode = view?.state.selection.$anchor.node(-2);
+            const liNode = view?.state.selection.$anchor.node(-1);
             // prettier-ignore
             if (liNode?.type === schema.nodes.list_item && !liNode.textContent && 
-                olNode?.type === schema.nodes.ordered_list && once && view.state.selection.$from.depth === 3) 
+                olNode?.type === schema.nodes.ordered_list && once && view?.state.selection.$from.depth === 3) 
             {
                 // handles case of hitting enter at then end of a top-level empty list item - the result is to create a paragraph
-                for (let i = 0; i < 10 && view.state.selection.$from.depth > 1 && liftListItem(schema.nodes.list_item)(view.state, view.dispatch); i++);
+                for (let i = 0; i < 10 && view?.state.selection.$from.depth > 1 && liftListItem(schema.nodes.list_item)(view.state, view.dispatch); i++);
             } else if (
-                !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
+                !splitListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
                     const tx3 = updateBullets(tx2, schema);
                     marks && tx3.ensureMarks([...marks]);
                     marks && tx3.setStoredMarks([...marks]);
-                    dispatch(tx3);
+                    dispatch?.(tx3);
                     // removes an extra paragraph created when selecting text across two list items or splitting an empty list item
-                    !once && view.dispatch(view.state.tr.deleteRange(view.state.selection.from - 5, view.state.selection.from - 2));
+                    !once && view?.dispatch(view?.state.tr.deleteRange(view.state.selection.from - 5, view.state.selection.from - 2));
                 })
             ) {
-                if (once && view.state.selection.$from.node(-2)?.type === schema.nodes.ordered_list && view.state.selection.$from.node(-1)?.type === schema.nodes.list_item && view.state.selection.$from.node(-1)?.textContent === '') {
+                if (once && view?.state.selection.$from.node(-2)?.type === schema.nodes.ordered_list && view?.state.selection.$from.node(-1)?.type === schema.nodes.list_item && view.state.selection.$from.node(-1)?.textContent === '') {
                     // handles case of hitting enter on an empty list item which needs to create a second empty paragraph, then split it by calling enter() again
                     view.dispatch(view.state.tr.insert(view.state.selection.from, schema.nodes.paragraph.create({})));
                     enter(view.state, view.dispatch, view, false);
@@ -346,12 +358,12 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
                             const tonode = tx3.selection.$to.node();
                             if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) {
                                 const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks).setStoredMarks(marks || []);
-                                dispatch(tx4);
+                                dispatch?.(tx4);
                             }
 
-                            if (view.state.selection.$anchor.depth > 0 &&
-                                view.state.selection.$anchor.node(view.state.selection.$anchor.depth-1).type === schema.nodes.list_item &&
-                                view.state.selection.$anchor.nodeAfter?.type === schema.nodes.text && once) {
+                            if ((view?.state.selection.$anchor.depth ??0) > 0 &&
+                                view?.state.selection.$anchor.node(view.state.selection.$anchor.depth-1).type === schema.nodes.list_item &&
+                                view?.state.selection.$anchor.nodeAfter?.type === schema.nodes.text && once) {
                                 // if text is selected across list items, then we need to forcibly insert a new line since the splitBlock code joins the two list items.
                                 enter(view.state, dispatch, view, false);
                             }
@@ -368,14 +380,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMa
 
     // Command to create a blank space
     bind('Space', () => {
-        const editDoc = props.TemplateDataDocument ?? props.Document[DocData];
+        const editDoc = tbox?._props.TemplateDataDocument ?? tbox?.Document[DocData];
         if (editDoc && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(editDoc))) return true;
         return false;
     });
 
-    bind('Alt-ArrowUp', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinUp(state, dispatch as any));
-    bind('Alt-ArrowDown', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinDown(state, dispatch as any));
-    bind('Mod-BracketLeft', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && lift(state, dispatch as any));
+    bind('Alt-ArrowUp', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && joinUp(state, dispatch));
+    bind('Alt-ArrowDown', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && joinDown(state, dispatch));
+    bind('Mod-BracketLeft', (state: EditorState, dispatch: ((tx: Transaction) => void) | undefined) => canEdit(state) && lift(state, dispatch));
 
     const cmd = chainCommands(exitCode, (state, dispatch) => {
         if (dispatch) {
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
index 238267f6e..6dea891a0 100644
--- a/src/client/views/nodes/formattedText/SummaryView.tsx
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -1,12 +1,11 @@
 import { TextSelection } from 'prosemirror-state';
-import { Fragment, Node, Slice } from 'prosemirror-model';
+import { Attrs, Fragment, Node, Slice } from 'prosemirror-model';
 import * as ReactDOM from 'react-dom/client';
 import * as React from 'react';
+import { EditorView } from 'prosemirror-view';
 
-interface ISummaryView {}
 // currently nothing needs to be rendered for the internal view of a summary.
-// eslint-disable-next-line react/prefer-stateless-function
-export class SummaryViewInternal extends React.Component<ISummaryView> {
+export class SummaryViewInternal extends React.Component<object> {
     render() {
         return null;
     }
@@ -18,30 +17,30 @@ export class SummaryViewInternal extends React.Component<ISummaryView> {
 // method instead of changing prosemirror's text when the expand/elide buttons are clicked.
 export class SummaryView {
     dom: HTMLSpanElement; // container for label and value
-    root: any;
+    root: ReactDOM.Root;
 
-    constructor(node: any, view: any, getPos: any) {
+    constructor(node: Node, view: EditorView, getPos: () => number | undefined) {
         this.dom = document.createElement('span');
         this.dom.className = this.className(node.attrs.visibility);
-        this.dom.onpointerdown = (e: any) => {
+        this.dom.onpointerdown = (e: PointerEvent) => {
             this.onPointerDown(e, node, view, getPos);
         };
-        this.dom.onkeypress = function (e: any) {
+        this.dom.onkeypress = function (e: KeyboardEvent) {
             e.stopPropagation();
         };
-        this.dom.onkeydown = function (e: any) {
+        this.dom.onkeydown = function (e: KeyboardEvent) {
             e.stopPropagation();
         };
-        this.dom.onkeyup = function (e: any) {
+        this.dom.onkeyup = function (e: KeyboardEvent) {
             e.stopPropagation();
         };
-        this.dom.onmousedown = function (e: any) {
+        this.dom.onmousedown = function (e: MouseEvent) {
             e.stopPropagation();
         };
 
         const js = node.toJSON;
-        node.toJSON = function (...args: any[]) {
-            return js.apply(this, args);
+        node.toJSON = function (...args: unknown[]) {
+            return js.apply(this, args as []);
         };
 
         this.root = ReactDOM.createRoot(this.dom);
@@ -54,7 +53,7 @@ export class SummaryView {
     }
     selectNode() {}
 
-    updateSummarizedText(start: any, view: any) {
+    updateSummarizedText(start: number, view: EditorView) {
         const mtype = view.state.schema.marks.summarize;
         const mtypeInc = view.state.schema.marks.summarizeInclusive;
         let endPos = start;
@@ -65,7 +64,7 @@ export class SummaryView {
             // eslint-disable-next-line no-loop-func
             view.state.doc.nodesBetween(start, i, (node: Node /* , pos: number, parent: Node, index: number */) => {
                 if (node.isLeaf && !visited.has(node) && !skip) {
-                    if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
+                    if (node.marks.find(m => m.type === mtype || m.type === mtypeInc)) {
                         visited.add(node);
                         endPos = i + node.nodeSize - 1;
                     } else skip = true;
@@ -75,21 +74,18 @@ export class SummaryView {
         return TextSelection.create(view.state.doc, start, endPos);
     }
 
-    onPointerDown = (e: any, node: any, view: any, getPos: any) => {
+    onPointerDown = (e: PointerEvent, node: Node, view: EditorView, getPos: () => number | undefined) => {
         const visible = !node.attrs.visibility;
-        const attrs = { ...node.attrs, visibility: visible };
-        let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
-        if (!visible) {
-            // update summarized text and save in attrs
-            textSelection = this.updateSummarizedText(getPos() + 1, view);
-            attrs.text = textSelection.content();
-            attrs.textslice = attrs.text.toJSON();
-        }
+        const textSelection = visible //
+            ? TextSelection.create(view.state.doc, (getPos() ?? 0) + 1)
+            : this.updateSummarizedText((getPos() ?? 0) + 1, view); // update summarized text and save in attrs
+        const text = textSelection.content();
+        const attrs = { ...node.attrs, visibility: visible, ...(!visible ? { text, textslice: text.toJSON() } : {}) } as Attrs;
         view.dispatch(
             view.state.tr
                 .setSelection(textSelection) // select the current summarized text (or where it will be if its collapsed)
                 .replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text) // collapse/expand it
-                .setNodeMarkup(getPos(), undefined, attrs)
+                .setNodeMarkup(getPos() ?? 0, undefined, attrs)
         ); // update the attrs
         e.preventDefault();
         e.stopPropagation();
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 2142adac8..73c2f5eb8 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -35,7 +35,7 @@ export * from 'pdfjs-dist/build/pdf.mjs';
 
 interface IViewerProps extends FieldViewProps {
     pdfBox: PDFBox;
-    Document: Doc;
+    Doc: Doc;
     dataDoc: Doc;
     layoutDoc: Doc;
     fieldKey: string;
@@ -113,8 +113,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
             () => this._props.layoutDoc._layout_autoHeight,
             layoutAutoHeight => {
                 if (layoutAutoHeight) {
-                    this._props.layoutDoc._nativeHeight = NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']);
-                    this._props.setHeight?.(NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
+                    this._props.layoutDoc._nativeHeight = NumCast(this._props.Doc[this._props.fieldKey + '_nativeHeight']);
+                    this._props.setHeight?.(NumCast(this._props.Doc[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
                 }
             }
         );
@@ -125,7 +125,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
             { fireImmediately: true }
         );
         this._disposers.curPage = reaction(
-            () => Cast(this._props.Document._layout_curPage, 'number', null),
+            () => Cast(this._props.Doc._layout_curPage, 'number', null),
             page => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
             { fireImmediately: true }
         );
@@ -181,7 +181,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     scrollFocus = (doc: Doc, scrollTop: number, options: FocusViewOptions) => {
         const mainCont = this._mainCont.current;
         let focusSpeed: Opt<number>;
-        if (doc !== this._props.Document && mainCont) {
+        if (doc !== this._props.Doc && mainCont) {
             const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
             const scrollTo = ClientUtils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
             if (scrollTo !== undefined && scrollTo !== this._props.layoutDoc._layout_scrollTop) {
@@ -216,11 +216,11 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
             { fireImmediately: true }
         );
         this._disposers.scroll = reaction(
-            () => Math.abs(NumCast(this._props.Document._layout_scrollTop)),
+            () => Math.abs(NumCast(this._props.Doc._layout_scrollTop)),
             pos => {
                 if (!this._ignoreScroll) {
                     this._showWaiting && this.setupPdfJsViewer();
-                    const viewTrans = quickScroll?.loc ?? StrCast(this._props.Document._viewTransition);
+                    const viewTrans = quickScroll?.loc ?? StrCast(this._props.Doc._viewTransition);
                     const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
                     const durationSecStr = viewTrans.match(/([0-9.]*)s/);
                     const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
@@ -371,9 +371,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         // if alt+left click, drag and annotate
         this._downX = e.clientX;
         this._downY = e.clientY;
-        if ((this._props.Document._freeform_scale || 1) !== 1) return;
+        if ((this._props.Doc._freeform_scale || 1) !== 1) return;
         if ((e.button !== 0 || e.altKey) && this._props.isContentActive()) {
-            this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Document);
+            this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Doc);
         }
         if (!e.altKey && e.button === 0 && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
             this._props.select(false);
@@ -433,7 +433,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     addDrawingAnnotation = (drawing: Doc) => {
         // drawing.x = this._props.pdfBox.ScreenToLocalBoxXf().TranslateX
         // const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
-        drawing.y = NumCast(drawing.y) + NumCast(this._props.Document.layout_scrollTop);
+        drawing.y = NumCast(drawing.y) + NumCast(this._props.Doc.layout_scrollTop);
         this._props.addDocument?.(drawing);
     };
 
@@ -478,7 +478,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     onClick = (e: React.MouseEvent) => {
         this._scrollStopper?.();
         if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < ClientUtils.DRAG_THRESHOLD) {
-            this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
+            this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Doc);
         }
         // e.stopPropagation();  // bcz: not sure why this was here.  We need to allow the DocumentView to get clicks to process doubleClicks
     };
@@ -506,7 +506,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     @computed get annotationLayer() {
         const inlineAnnos = this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).filter(anno => !anno.hidden);
         return (
-            <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
+            <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Doc), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
                 {inlineAnnos.map(anno => (
                     <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} containerDataDoc={this._props.dataDoc} annoDoc={anno} key={`${anno[Id]}-annotation`} />
                 ))}
@@ -597,7 +597,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                     onClick={this.onClick}
                     style={{
                         overflowX: NumCast(this._props.layoutDoc._freeform_scale, 1) !== 1 ? 'scroll' : undefined,
-                        height: !this._props.Document._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this._props.Document) : `100%`,
+                        height: !this._props.Doc._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this._props.Doc) : `100%`,
                     }}>
                     {this.pdfViewerDiv}
                     {this.annotationLayer}
@@ -606,12 +606,12 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                     {!this._mainCont.current || !this._annotationLayer.current || !this.props.pdfBox.DocumentView ? null : (
                         <MarqueeAnnotator
                             ref={this._marqueeref}
-                            Document={this._props.Document}
+                            Document={this._props.Doc}
                             getPageFromScroll={this.getPageFromScroll}
                             anchorMenuClick={this._props.anchorMenuClick}
                             scrollTop={0}
                             annotationLayerScaling={() => Pdfjs.PixelsPerInch.PDF_TO_CSS_UNITS}
-                            annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)}
+                            annotationLayerScrollTop={NumCast(this._props.Doc._layout_scrollTop)}
                             addDocument={this.addDocumentWrapper}
                             docView={this.props.pdfBox.DocumentView}
                             screenTransform={this.screenToMarqueeXf}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 8b7e77fba..1f6e80bd1 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -27,7 +27,7 @@ const MAX_ITERATIONS = 25;
 const ERROR = 0.03;
 
 export interface SearchBoxItemProps {
-    Document: Doc;
+    Doc: Doc;
     searchString: string;
     isLinkSearch: boolean;
     matchedKeys: string[];
@@ -64,7 +64,7 @@ export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps>
     });
 
     componentWillUnmount(): void {
-        const doc = this._props.Document;
+        const doc = this._props.Doc;
         DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
     }
 
@@ -79,23 +79,23 @@ export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps>
 
     render() {
         // eslint-disable-next-line no-use-before-define
-        const formattedType = SearchBox.formatType(StrCast(this._props.Document.type), StrCast(this._props.Document.type_collection));
-        const { title } = this._props.Document;
+        const formattedType = SearchBox.formatType(StrCast(this._props.Doc.type), StrCast(this._props.Doc.type_collection));
+        const { title } = this._props.Doc;
 
         return (
             <Tooltip placement="right" title={<div className="dash-tooltip">{title as string}</div>}>
                 <div
                     onClick={
                         this._props.isLinkSearch
-                            ? () => this.makeLink(this._props.Document)
+                            ? () => this.makeLink(this._props.Doc)
                             : e => {
-                                  this.onResultClick(this._props.Document);
+                                  this.onResultClick(this._props.Doc);
                                   e.stopPropagation();
                               }
                     }
                     style={{
                         fontWeight: Doc.Links(this._props.linkFrom).find(
-                            link => Doc.AreProtosEqual(Doc.getOppositeAnchor(link, this._props.linkFrom!), this._props.Document) || Doc.AreProtosEqual(DocCast(Doc.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Document)
+                            link => Doc.AreProtosEqual(Doc.getOppositeAnchor(link, this._props.linkFrom!), this._props.Doc) || Doc.AreProtosEqual(DocCast(Doc.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Doc)
                         )
                             ? 'bold'
                             : '',
@@ -449,7 +449,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
                 resultsJSX.push(
                     <SearchBoxItem
                         key={Document[Id]}
-                        Document={Document}
+                        Doc={Document}
                         selectItem={action((doc: Doc) => {
                             this._selectedResult = doc;
                         })}
diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx
index 080a05d42..2260d1f73 100644
--- a/src/client/views/smartdraw/StickerPalette.tsx
+++ b/src/client/views/smartdraw/StickerPalette.tsx
@@ -25,7 +25,7 @@ import { DrawingOptions, SmartDrawHandler } from './SmartDrawHandler';
 import './StickerPalette.scss';
 
 interface StickerPaletteProps {
-    Document: Doc;
+    Doc: Doc;
 }
 
 enum StickerPaletteMode {
@@ -132,7 +132,7 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
         this._canInteract = true;
         this._opts = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 };
         this._gptRes = [];
-        this._props.Document.$data = undefined;
+        this._props.Doc.$data = undefined;
     });
 
     /**
@@ -142,8 +142,8 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
     @undoBatch
     generateDrawings = action(() => {
         this._isLoading = true;
-        const prevDrawings = DocListCast(this._props.Document.$data);
-        this._props.Document.$data = undefined;
+        const prevDrawings = DocListCast(this._props.Doc.$data);
+        this._props.Doc.$data = undefined;
         SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
         this._canInteract = false;
         Promise.all(
@@ -164,7 +164,7 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
     addDrawing = (drawing: Doc, opts: DrawingOptions, gptRes: string) => {
         this._gptRes.push(gptRes);
         drawing.$freeform_fitContentsToBox = true;
-        Doc.AddDocToList(this._props.Document, 'data', drawing);
+        Doc.AddDocToList(this._props.Doc, 'data', drawing);
     };
 
     /**
@@ -173,8 +173,8 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
      * presses the "save drawing" button.
      */
     saveDrawing = () => {
-        const cIndex = NumCast(this._props.Document.carousel_index);
-        const focusedDrawing = DocListCast(this._props.Document.data)[cIndex];
+        const cIndex = NumCast(this._props.Doc.carousel_index);
+        const focusedDrawing = DocListCast(this._props.Doc.data)[cIndex];
         focusedDrawing.$title = this._opts.text?.match(/^(.*?)~~~.*$/)?.[1] || this._opts.text;
         focusedDrawing.$ai_drawing_input = this._opts.text;
         focusedDrawing.$ai_drawing_complexity = this._opts.complexity;
@@ -313,7 +313,7 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
         <>
             {this.renderCreateInput()}
             {this.renderCreateOptions()}
-            {this.renderDoc(this._props.Document, (r: DocumentView) => {
+            {this.renderDoc(this._props.Doc, (r: DocumentView) => {
                 this._docCarouselView = r;
             })}
             <div className="palette-buttons">
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index 42dd0d432..e16073ff4 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -20,6 +20,7 @@ import { Doc, Opt } from './Doc';
 import { Id } from './FieldSymbols';
 import { RichTextField } from './RichTextField';
 import { Cast, StrCast } from './Types';
+import { Upload } from '../server/SharedMediaTypes';
 
 export namespace RichTextUtils {
     const delimiter = '\n';
@@ -127,7 +128,7 @@ export namespace RichTextUtils {
                     return { baseUrl: embeddedObject.imageProperties!.contentUri! };
                 });
 
-                const uploads = await Networking.PostToServer('/googlePhotosMediaGet', { mediaItems });
+                const uploads = (await Networking.PostToServer('/googlePhotosMediaGet', { mediaItems })) as Upload.FileInformation[];
 
                 if (uploads.length !== mediaItems.length) {
                     throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: 'Error with internally uploading inlineObjects!' });
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index 474882959..af9cb1180 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -60,16 +60,13 @@ export type CastCtor = ToConstructor<FieldType> | ListSpec<FieldType>;
 type WithoutList<T extends FieldType> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
 
 export function Cast<T extends CastCtor>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
-// eslint-disable-next-line no-redeclare
 export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
-// eslint-disable-next-line no-redeclare
 export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined {
     if (field instanceof Promise) {
         return defaultVal === undefined ? (field.then(f => Cast(f, ctor) as any) as any) : defaultVal === null ? undefined : defaultVal;
     }
     if (field !== undefined && !(field instanceof Promise)) {
         if (typeof ctor === 'string') {
-            // eslint-disable-next-line valid-typeof
             if (typeof field === ctor) {
                 return field as ToType<T>;
             }
@@ -140,9 +137,7 @@ export function ImageCastWithSuffix(field: FieldResult, suffix: string, defaultV
 }
 
 export function FieldValue<T extends FieldType, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
-// eslint-disable-next-line no-redeclare
 export function FieldValue<T extends FieldType>(field: FieldResult<T>): Opt<T>;
-// eslint-disable-next-line no-redeclare
 export function FieldValue<T extends FieldType>(field: FieldResult<T>, defaultValue?: T): Opt<T> {
     return field instanceof Promise || field === undefined ? defaultValue : field;
 }
diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts
index 891316b80..8688ec049 100644
--- a/src/server/DashSession/DashSessionAgent.ts
+++ b/src/server/DashSession/DashSessionAgent.ts
@@ -213,9 +213,9 @@ export class DashSessionAgent extends AppliedSessionAgent {
             // indicate success or failure
             mainLog(`${error === null ? green('successfully dispatched') : red('failed to dispatch')} ${zipName} to ${cyan(to)}`);
             error && mainLog(red(error.message));
-        } catch (error: any) {
+        } catch (error: unknown) {
             mainLog(red('unable to dispatch zipped backup...'));
-            mainLog(red(error.message));
+            mainLog(red((error as { message: string }).message));
         }
     }
 }
-- 
cgit v1.2.3-70-g09d2


From c1a474fd2cf633fa204f1bb6dce2539f4b172d6c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 24 Mar 2025 18:28:20 -0400
Subject: fix onclick disabling

---
 src/client/views/nodes/DocumentView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c355e57d4..115aedbe8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -692,7 +692,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             .ScreenToLocalTransform()
             .translate(-NumCast(this.Document.borderWidth), -this.headerMargin - NumCast(this.Document.borderWidth))
             .scale(this._props.showAIEditor ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1);
-    onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHdlr;
+    onClickFunc = () => (this.disableClickScriptFunc ? undefined : this.onClickHdlr);
     setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height + 2 * NumCast(this.Document.borderWidth))); } // prettier-ignore
     setContentView = action((view: ViewBoxInterface<FieldViewProps>) => { this._componentView = view; }); // prettier-ignore
     isContentActive = (): boolean | undefined => this._isContentActive;
-- 
cgit v1.2.3-70-g09d2


From ab785dbb8c9e904b2f4bba8b4000b85db2d8d541 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 26 Mar 2025 14:50:49 -0400
Subject: fixes to field access on root doc, not data doc

---
 src/client/views/nodes/DocumentView.tsx | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 115aedbe8..3268ba0f7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -88,7 +88,7 @@ export interface DocumentViewProps extends FieldViewSharedProps {
 @observer
 export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps & { showAIEditor: boolean }>() {
     // this makes mobx trace() statements more descriptive
-    public get displayName()  { return 'DocumentViewInternal(' + this.Document.$title + ')'; } // prettier-ignore
+    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.
 
     /**
@@ -322,7 +322,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                         DocumentView.DeselectAll();
                         Doc.UnBrushDoc(this.Document);
                     } else this._singleClickFunc?.();
-                }, 'on double click: ' + this.Document.$title)();
+                }, 'on double click: ' + this.Document.title)();
                 this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
                 this._doubleClickTimeout = undefined;
                 this._singleClickFunc = undefined;
@@ -340,7 +340,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     if (this.layoutDoc.onDragStart && !(e.ctrlKey || e.button > 0)) stopPropagate = false;
                     preventDefault = false;
                 }
-                this._singleClickFunc = undoable(clickFunc ?? sendToBack ?? selectFunc, 'click: ' + this.Document.$title);
+                this._singleClickFunc = undoable(clickFunc ?? sendToBack ?? selectFunc, 'click: ' + this.Document.title);
                 const waitForDblClick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
                 if ((clickFunc && waitForDblClick !== 'never') || waitForDblClick === 'always') {
                     this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
@@ -519,7 +519,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
         if (e && !(e.nativeEvent instanceof simMouseEvent ? e.nativeEvent.dash : false)) {
             const onDisplay = () => {
-                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.
+                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')) {
@@ -545,7 +545,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     cm.addItem({ description: item.label, event: () => (item.method ? item.method() : item.script?.script.run({ this: this.Document, documentView: this, scriptContext: this._props.scriptContext })), icon: item.icon as IconProp })
             );
 
-        if (!this.Document.$isFolder) {
+        if (!this.Document.isFolder) {
             const templateDoc = Cast(this.Document[StrCast(this.Document.layout_fieldKey)], Doc, null);
             const appearance = cm.findByDescription('Appearance...');
             const appearanceItems = appearance?.subitems ?? [];
@@ -635,7 +635,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
         let documentationDescription: string | undefined;
         let documentationLink: string | undefined;
-        switch (this.Document.$type) {
+        switch (this.Document.type) {
             case DocumentType.COL:
                 documentationDescription = 'See collection documentation';
                 documentationLink = 'https://brown-dash.github.io/Dash-Documentation/views/';
@@ -746,7 +746,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     @computed get viewBoxContents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
-        const noBackground = this.Document.$isGroup && !this._componentView?.isUnstyledView?.() && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+        const noBackground = this.Document.isGroup && !this._componentView?.isUnstyledView?.() && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
         return (
             <>
                 <div
@@ -957,7 +957,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             : (this.docContents ?? (
                   <div
                       className="documentView-node"
-                      id={this.Document.$type !== DocumentType.LINK ? this._docView?.DocUniqueId : undefined}
+                      id={this.Document.type !== DocumentType.LINK ? this._docView?.DocUniqueId : undefined}
                       style={{
                           ...style,
                           background: this.backgroundBoxColor,
@@ -1015,7 +1015,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     borderRadius: this._componentView?.isUnstyledView?.() ? undefined : this.borderRounding,
                     pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
                 }}>
-                {this._componentView?.isUnstyledView?.() || this.Document.$type === DocumentType.CONFIG || !renderDoc ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
+                {this._componentView?.isUnstyledView?.() || this.Document.type === DocumentType.CONFIG || !renderDoc ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
                 {jsx}
             </div>
         );
@@ -1210,7 +1210,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     }
     @computed private get nativeScaling() {
         if (this.shouldNotScale) return 1;
-        const minTextScale = this.Document.$type === DocumentType.RTF ? 0.1 : 0;
+        const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
         const ai = this._showAIEditor && this.nativeWidth === this.layoutDoc.width ? 95 : 0;
         const effNW = Math.max(this.effectiveNativeWidth - ai, 1);
         const effNH = Math.max(this.effectiveNativeHeight - ai, 1);
@@ -1325,7 +1325,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     public startDragging = (x: number, y: number, dropAction: dropActionType | undefined, 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 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?.();
-- 
cgit v1.2.3-70-g09d2


From a3eab5f2b8eca6ff7a21ea9d043b90c953c35f03 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 26 Mar 2025 16:36:15 -0400
Subject: fix so that views use don't use the rootDocument, but rather the
 current rendering document.  got rid of resolvedDataDoc -- just use
 rootDocument[DocData]

---
 src/client/views/DocComponent.tsx                  | 44 +++++++++++-----------
 src/client/views/DocumentDecorations.tsx           |  2 +-
 .../views/collections/CollectionCarouselView.tsx   |  3 +-
 .../CollectionStackingViewFieldColumn.tsx          |  5 ++-
 src/client/views/collections/CollectionSubView.tsx | 12 +++---
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collectionGrid/CollectionGridView.tsx          |  5 ++-
 .../CollectionMulticolumnView.tsx                  |  5 ++-
 .../CollectionMultirowView.tsx                     |  5 ++-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/fields/Doc.ts                                  | 19 +++++-----
 14 files changed, 58 insertions(+), 52 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 3a868d1f9..e3d47317c 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -36,7 +36,7 @@ export function DocComponent<P extends DocComponentProps>() {
          * NOTE: it is very unlikely that you really want to use this method. Instead
          *       consider: Document, layoutDoc, dataDoc
          */
-        get _renderDoc() {
+        get Document() {
             return this._props.Document;
         }
 
@@ -46,8 +46,8 @@ export function DocComponent<P extends DocComponentProps>() {
          * necessarily the Doc being rendered in the current React component.
          * This Doc inherits from the dataDoc, and may or may not inherit (or be) the layoutDoc.
          */
-        get Document() {
-            return DocCast(this._renderDoc.rootDocument, this._renderDoc);
+        get rootDoc() {
+            return DocCast(this.Document.rootDocument, this.Document);
         }
         /**
          * This is the document being rendered by the React component. In the
@@ -56,14 +56,14 @@ export function DocComponent<P extends DocComponentProps>() {
          * This may or may not inherit from the data doc.
          */
         @computed get layoutDoc() {
-            return this._props.LayoutTemplateString ? this._renderDoc : Doc.Layout(this._renderDoc, this._props.LayoutTemplate?.());
+            return this._props.LayoutTemplateString ? this.Document : Doc.Layout(this.Document, this._props.LayoutTemplate?.());
         }
 
         /**
          * This is the unique data repository for a document that stores the intrinsic document data.
          */
         @computed get dataDoc() {
-            return this._renderDoc[DocData];
+            return this.Document[DocData];
         }
     }
     return Component;
@@ -92,10 +92,8 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
          * This is the doc that is being rendered. It will be either:
          *   1) the same as Document if the root of a regular or compound Doc is rendered
          *   2) the same as the layoutDoc if a component of a compound Doc is rendered.
-         * NOTE: it is very unlikely that you really want to use this method. Instead
-         *       consider: Document, layoutDoc, dataDoc
          */
-        get _renderDoc() {
+        get Document() {
             return this._props.Document;
         }
 
@@ -104,9 +102,12 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
          * this is the outermost Doc that represents the entire compound Doc.  It is not
          * necessarily the Doc being rendered in the current React component.
          * This Doc inherits from the dataDoc, and may or may not inherit (or be) the layoutDoc.
+         *
+         * NOTE: it is very unlikely that you really want to use this method. Instead
+         *       consider: Document, layoutDoc, dataDoc
          */
-        get Document() {
-            return DocCast(this._renderDoc.rootDocument, this._renderDoc);
+        get rootDoc() {
+            return DocCast(this.Document.rootDocument, this.Document);
         }
         /**
          * This is the document being rendered by the React component. In the
@@ -115,14 +116,14 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
          * This may or may not inherit from the data doc.
          */
         @computed get layoutDoc() {
-            return Doc.Layout(this._renderDoc);
+            return Doc.Layout(this.Document);
         }
 
         /**
          * This is the unique data repository for a dcoument that stores the intrinsic document data
          */
         @computed get dataDoc() {
-            return this._renderDoc.isTemplateForField || this._renderDoc.isTemplateDoc ? (this._props.TemplateDataDocument ?? this._renderDoc[DocData]) : this._renderDoc[DocData];
+            return this.Document.isTemplateForField || this.Document.isTemplateDoc ? (this._props.TemplateDataDocument ?? this.Document[DocData]) : this.Document[DocData];
         }
 
         /**
@@ -165,10 +166,8 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
          * This is the doc that is being rendered. It will be either:
          *   1) the same as Document if the root of a regular or compound Doc is rendered
          *   2) the same as the layoutDoc if a component of a compound Doc is rendered.
-         * NOTE: it would unlikely that you really want to use this instead of the
-         *       other Doc options (Document, layoutDoc, dataDoc)
          */
-        get _renderDoc() {
+        get Document() {
             return this._props.Document;
         }
 
@@ -177,24 +176,27 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
          * this is the outermost Doc that represents the entire compound Doc.  It is not
          * necessarily the Doc being rendered in the current React component.
          * This Doc inherits from the dataDoc, and may or may not inherit (or be) the layoutDoc.
+         *
+         * NOTE: it would unlikely that you really want to use this instead of the
+         *       other Doc options (Document, layoutDoc, dataDoc)
          */
-        @computed get Document() {
-            return DocCast(this._renderDoc.rootDocument, this._renderDoc);
+        @computed get rootDoc() {
+            return DocCast(this.Document.rootDocument, this.Document);
         }
         /**
          * This is the document being rendered.  It may be a template so it may or may no inherit from the data doc.
          */
         @computed get layoutDoc() {
-            return Doc.Layout(this._renderDoc);
+            return Doc.Layout(this.Document);
         }
 
         /**
          * This is the unique data repository for a dcoument that stores the intrinsic document data
          */
         @computed get dataDoc() {
-            return this._renderDoc.isTemplateForField || this._renderDoc.isTemplateDoc ? 
-                (this._props.TemplateDataDocument ?? this._renderDoc[DocData]) : 
-                this._props.Document[DocData]; // prettier-ignore
+            return this.Document.isTemplateForField || this.Document.isTemplateDoc ? 
+                (this._props.TemplateDataDocument ?? this.Document[DocData]) : 
+                this.Document[DocData]; // prettier-ignore
         }
 
         /**
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 19b987cb5..92b4d6fbf 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -635,7 +635,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         if (DocumentView.Selected().length === 1) {
             const selected = DocumentView.Selected()[0];
             if (this._titleControlString.startsWith('$')) {
-                return Field.toJavascriptString(selected._renderDoc[this._titleControlString.substring(1)] as FieldType) || '-unset-';
+                return Field.toJavascriptString(selected.Document[this._titleControlString.substring(1)] as FieldType) || '-unset-';
             }
             return this._accumulatedTitle;
         }
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index a7d217076..3fc56d7d9 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -16,6 +16,7 @@ import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionCarouselView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { DocData } from '../../../fields/DocSymbols';
 
 @observer
 export class CollectionCarouselView extends CollectionSubView() {
@@ -136,7 +137,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                 renderDepth={this._props.renderDepth + 1}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
-                TemplateDataDocument={DocCast(Doc.Layout(doc).resolvedDataDoc)}
+                TemplateDataDocument={DocCast(Doc.Layout(doc).rootDocument)?.[DocData]}
                 childFilters={this.childDocFilters}
                 focus={this.focus}
                 hideDecorations={BoolCast(this.layoutDoc.layout_hideDecorations)}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 3b9d167c6..e765b78b4 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -7,7 +7,7 @@ import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { RichTextField } from '../../../fields/RichTextField';
 import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, NumCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
@@ -26,6 +26,7 @@ import { EditableView } from '../EditableView';
 import { DocumentView } from '../nodes/DocumentView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import './CollectionStackingView.scss';
+import { DocData } from '../../../fields/DocSymbols';
 
 // So this is how we are storing a column
 interface CSVFieldColumnProps {
@@ -270,7 +271,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                     event: () => {
                         const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
                         if (created) {
-                            const container = this._props.Doc.resolvedDataDoc ? Doc.GetProto(this._props.Doc) : this._props.Doc;
+                            const container = DocCast(this._props.Doc.rootDocument)?.[DocData] ? Doc.GetProto(this._props.Doc) : this._props.Doc;
                             if (container.isTemplateDoc) {
                                 Doc.MakeMetadataFieldTemplate(created, container);
                                 return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e34ba93a9..cfd52f9ee 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -108,11 +108,11 @@ export function CollectionSubView<X>() {
         }
 
         get dataDoc() {
-            return this._props.TemplateDataDocument instanceof Doc && this._renderDoc.isTemplateForField //
+            return this._props.TemplateDataDocument instanceof Doc && this.Document.isTemplateForField //
                 ? Doc.GetProto(this._props.TemplateDataDocument)
-                : this.Document.resolvedDataDoc
-                  ? this._renderDoc
-                  : this.Document[DocData]; // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
+                : this.Document.rootDocument
+                  ? this.Document
+                  : this.Document[DocData]; // if the layout document has a rootDocument, then we don't want to get its parent which would be the unexpanded template
         }
 
         get childContainerViewPath() {
@@ -133,7 +133,7 @@ export function CollectionSubView<X>() {
         @computed get childLayoutPairs(): { layout: Doc; data: Doc }[] {
             const { TemplateDataDocument } = this._props;
             const validPairs = this.childDocs
-                .map(doc => Doc.GetLayoutDataDocPair(this._renderDoc, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
+                .map(doc => Doc.GetLayoutDataDocPair(this.Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
                 .filter(
                     pair =>
                         // filter out any documents that have a proto that we don't have permissions to
@@ -299,7 +299,7 @@ export function CollectionSubView<X>() {
             const dragData = de.complete.docDragData;
             if (dragData) {
                 const sourceDragAction = dragData.dropAction;
-                const sameCollection = !dragData.draggedDocuments.some(d => d.embedContainer !== this._renderDoc);
+                const sameCollection = !dragData.draggedDocuments.some(d => d.embedContainer !== this.Document);
                 dragData.dropAction = !sameCollection // if doc from another tree
                     ? sourceDragAction || targetDropAction // then use the source's dragAction otherwise the target's
                     : sourceDragAction === dropActionType.inPlace // if source drag is inPlace
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 1960e12bd..418c437d5 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -187,7 +187,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     @action
     addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
         const addDocRelativeTo = (adocs: Doc | Doc[]) => (adocs as Doc[]).reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true);
-        if (this.Document.resolvedDataDoc instanceof Promise) return false;
+        if (this.Document.rootDocument instanceof Promise) return false;
         const doclist = toList(docs);
         const res = relativeTo === undefined ? this._props.addDocument?.(doclist) || false : addDocRelativeTo(doclist);
         res &&
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 09a1b0c53..09d43c5b0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -286,7 +286,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
     panX = () => this.fitContentBounds?.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(this.Document.freeform_panX, 1));
     panY = () => this.fitContentBounds?.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(this.Document.freeform_panY, 1));
-    zoomScaling = () => this.fitContentBounds?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1));
+    zoomScaling = () => this.fitContentBounds?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.rootDocument)?.[this.scaleFieldKey], 1));
     PanZoomCenterXf = () => (this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.centeringShiftX}px, ${this.centeringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`);
     ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy();
     getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index e7f1de7f1..8393c719b 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
-import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
@@ -16,6 +16,7 @@ import { DocumentView } from '../../nodes/DocumentView';
 import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
 import './CollectionGridView.scss';
 import Grid, { Layout } from './Grid';
+import { DocData } from '../../../../fields/DocSymbols';
 
 @observer
 export class CollectionGridView extends CollectionSubView() {
@@ -192,7 +193,7 @@ export class CollectionGridView extends CollectionSubView() {
         <DocumentView
             {...this._props}
             Document={layout}
-            TemplateDataDocument={layout.resolvedDataDoc as Doc}
+            TemplateDataDocument={DocCast(layout.rootDocument)?.[DocData]}
             NativeWidth={returnZero}
             NativeHeight={returnZero}
             fitWidth={this._props.childLayoutFitWidth}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 8aae24df0..58582a142 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -6,7 +6,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaChevronRight } from 'react-icons/fa';
 import { Doc, DocListCast } from '../../../../fields/Doc';
-import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { DragManager } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
@@ -18,6 +18,7 @@ import ResizeBar from './MulticolumnResizer';
 import WidthLabel from './MulticolumnWidthLabel';
 import { dropActionType } from '../../../util/DropActionTypes';
 import { SnappingManager } from '../../../util/SnappingManager';
+import { DocData } from '../../../../fields/DocSymbols';
 
 interface WidthSpecifier {
     magnitude: number;
@@ -272,7 +273,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         return (
             <DocumentView
                 Document={childLayout}
-                TemplateDataDocument={childLayout.resolvedDataDoc as Doc}
+                TemplateDataDocument={DocCast(childLayout.rootDocument)?.[DocData]}
                 styleProvider={this._props.styleProvider}
                 containerViewPath={this.childContainerViewPath}
                 LayoutTemplate={this._props.childLayoutTemplate}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index bda8e91ac..f95928cf6 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -2,7 +2,7 @@ import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../../fields/Doc';
-import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { DragManager } from '../../../util/DragManager';
 import { dropActionType } from '../../../util/DropActionTypes';
 import { Transform } from '../../../util/Transform';
@@ -11,6 +11,7 @@ import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'
 import './CollectionMultirowView.scss';
 import HeightLabel from './MultirowHeightLabel';
 import ResizeBar from './MultirowResizer';
+import { DocData } from '../../../../fields/DocSymbols';
 
 interface HeightSpecifier {
     magnitude: number;
@@ -253,7 +254,7 @@ export class CollectionMultirowView extends CollectionSubView() {
         return (
             <DocumentView
                 Document={layout}
-                TemplateDataDocument={layout.resolvedDataDoc as Doc}
+                TemplateDataDocument={DocCast(layout.rootDocument)?.[DocData]}
                 styleProvider={this._props.styleProvider}
                 containerViewPath={this.childContainerViewPath}
                 LayoutTemplate={this._props.childLayoutTemplate}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 6f86383c2..943d2175c 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -304,7 +304,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 ) : (
                     <DocumentView
                         {...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore
-                        Document={this._renderDoc}
+                        Document={this.Document}
                         renderDepth={this._props.renderDepth}
                         isContentActive={this._props.isContentActive}
                         childFilters={this._props.childFilters}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3268ba0f7..4ea2c66f3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -758,7 +758,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     }}>
                     <DocumentContentsView
                         {...this._props}
-                        layoutFieldKey={StrCast(this._renderDoc.layout_fieldKey, 'layout')}
+                        layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
                         pointerEvents={this.contentPointerEvents}
                         setContentViewBox={this.setContentView}
                         childFilters={this.childFilters}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 5f132ecdf..416cd577e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1882,7 +1882,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight;
                 };
 
-                if (this.Document === this.layoutDoc || this.layoutDoc.resolvedDataDoc) {
+                if (this.Document === this.layoutDoc || this.layoutDoc.rootDocument) {
                     setScrollHeight();
                 } else {
                     setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template.  So set a timeout and hope its arrived...
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 76d302db7..ba088d674 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -405,7 +405,7 @@ export class Doc extends RefField {
     }
     @computed get __DATA__(): Doc {
         const self = this[SelfProxy];
-        return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
+        return self.rootDocument && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).rootDocument, Doc, null) || self);
     }
     @computed get __LAYOUT__(): Doc | undefined {
         const self = this[SelfProxy];
@@ -871,15 +871,15 @@ export namespace Doc {
         const expandedLayoutFieldKey = 'layout_' + templateLayoutDoc.title + '(' + templateField + ')';
         let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
 
-        if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
+        if (templateLayoutDoc.rootDocument instanceof Promise) {
             expandedTemplateLayout = undefined;
             _pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
         } else if (expandedTemplateLayout === undefined && !_pendingMap.has(targetDoc[Id] + expandedLayoutFieldKey)) {
-            if (templateLayoutDoc.resolvedDataDoc === targetDoc[DocData]) {
+            if (DocCast(templateLayoutDoc.rootDocument)?.[DocData] === targetDoc[DocData]) {
                 expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
             } else {
                 // eslint-disable-next-line no-param-reassign
-                templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
+                templateLayoutDoc.rootDocument && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
                 if (!targetDoc[expandedLayoutFieldKey]) {
                     _pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
                     setTimeout(
@@ -888,7 +888,6 @@ export namespace Doc {
                             const dataDoc = Doc.GetProto(targetDoc);
                             newLayoutDoc.rootDocument = targetDoc;
                             newLayoutDoc.embedContainer = targetDoc;
-                            newLayoutDoc.resolvedDataDoc = dataDoc;
                             newLayoutDoc.acl_Guest = SharingPermissions.Edit;
                             if (dataDoc[templateField] === undefined && (templateLayoutDoc[templateField] as List<Doc>)?.length) {
                                 dataDoc[templateField] = ObjectField.MakeCopy(templateLayoutDoc[templateField] as List<Doc>);
@@ -912,9 +911,9 @@ export namespace Doc {
             console.log('Warning: GetLayoutDataDocPair childDoc not defined');
             return { layout: childDoc, data: childDoc };
         }
-        const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.isTemplateDoc(childDoc) && !Doc.isTemplateForField(childDoc)) ? undefined : containerDataDoc;
+        const data = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.isTemplateDoc(childDoc) && !Doc.isTemplateForField(childDoc)) ? undefined : containerDataDoc;
         const templateRoot = DocCast(containerDoc?.rootDocument);
-        return { layout: Doc.expandTemplateLayout(childDoc, templateRoot), data: resolvedDataDoc };
+        return { layout: Doc.expandTemplateLayout(childDoc, templateRoot), data };
     }
 
     export function FindReferences(infield: Doc | List<Doc>, references: Set<Doc>, system: boolean | undefined) {
@@ -1083,7 +1082,7 @@ export namespace Doc {
 
     export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined) {
         if (!Doc.AreProtosEqual(target[targetKey] as Doc, templateDoc)) {
-            if (target.resolvedDataDoc) {
+            if (target.rootDocument) {
                 target[targetKey] = new PrefetchProxy(templateDoc);
             } else {
                 titleTarget && (Doc.GetProto(target).title = titleTarget);
@@ -1301,7 +1300,7 @@ export namespace Doc {
             highlightedDocs.add(doc);
             doc[Highlight] = true;
             doc[Animation] = presentationEffect;
-            if (dataAndDisplayDocs && !doc.resolvedDataDoc) {
+            if (dataAndDisplayDocs && !doc.rootDocument) {
                 // if doc is a layout template then we don't want to highlight the proto since that will be the entire template, not just the specific layout field
                 highlightedDocs.add(doc[DocData]);
                 doc[DocData][Highlight] = true;
@@ -1331,7 +1330,7 @@ export namespace Doc {
               : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc
                 ? doc.dragFactory
                 : Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc
-                  ? Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc
+                  ? Cast(Doc.Layout(doc), Doc, null).rootDocument
                       ? Doc.Layout(doc).proto
                       : Doc.Layout(doc)
                   : undefined;
-- 
cgit v1.2.3-70-g09d2


From 902780aa40b0234aeeeb3aca25af9fa553b3553c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 27 Mar 2025 13:44:33 -0400
Subject: fixed tag label height.  fixed dropbox authorization for firefly.

---
 src/client/views/TagsView.tsx                     |  2 +-
 src/client/views/nodes/DocumentView.tsx           |  9 ++++++---
 src/client/views/smartdraw/DrawingFillHandler.tsx | 13 ++++++++-----
 3 files changed, 15 insertions(+), 9 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 7d2a6be5e..f1bcaac2c 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -357,7 +357,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                     <div className="tagsView-list">
                         {this._props.Views.length === 1 && !this.View.showTags ? null : ( //
                             <IconButton
-                                style={{ width: '8px' }}
+                                style={{ width: '8px', height: this._props.Views.lastElement().TagBtnHeight }}
                                 tooltip="Close Menu"
                                 onPointerDown={e =>
                                     setupMoveUpEvents(this, e, returnFalse, emptyFunction, upEv => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4ea2c66f3..eae3454ba 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -719,7 +719,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
     @observable _aiWinHeight = 88;
 
-    private _tagsBtnHeight = 22;
+    TagsBtnHeight = 22;
     @computed get currentScale() {
         const viewXfScale = this._props.DocumentView!().screenToLocalScale();
         const x = NumCast(this.Document._height) / viewXfScale / 80;
@@ -735,11 +735,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     /**
      * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size.
      */
-    @computed get maxWidgetSize() { return Math.min(this._tagsBtnHeight * this.viewScaling, 0.25 * Math.min(NumCast(this.Document._width), NumCast(this.Document._height))); } // prettier-ignore
+    @computed get maxWidgetSize() { return Math.min(this.TagsBtnHeight * this.viewScaling, 0.25 * Math.min(NumCast(this.Document._width), NumCast(this.Document._height))); } // prettier-ignore
     /**
      * How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content
      */
-    @computed get uiBtnScaling() {  return Math.max(this.maxWidgetSize / this._tagsBtnHeight, 1) * Math.min(1, this.viewScaling); } // prettier-ignore
+    @computed get uiBtnScaling() {  return Math.max(this.maxWidgetSize / this.TagsBtnHeight, 1) * Math.min(1, this.viewScaling); } // prettier-ignore
 
     aiContentsWidth = () => (this.aiContentsHeight() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1);
     aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight * this.uiBtnScaling);
@@ -1271,6 +1271,9 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     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
+    public get TagBtnHeight() {
+        return this._docViewInternal?.TagsBtnHeight;
+    }
 
     get LayoutFieldKey() {
         return Doc.LayoutFieldKey(this.Document, this._props.LayoutTemplateString);
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index f1d5f2cfe..34cc4f0e1 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -1,7 +1,7 @@
 import { imageUrlToBase64 } from '../../../ClientUtils';
 import { Doc, StrListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
-import { DocCast, ImageCast } from '../../../fields/Types';
+import { DocCast, ImageCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { Upload } from '../../../server/SharedMediaTypes';
 import { gptDescribeImage } from '../../apis/gpt/GPT';
@@ -44,7 +44,12 @@ export class DrawingFillHandler {
                     .then((prompt, newPrompt = user_prompt || prompt) =>
                         Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: dims.width, height: dims.height, structureUrl, strength, presets: styles, styleUrl })
                             .then(res => {
-                                const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { _width: 400, _height: 400 });
+                                const error = ('error' in res && (res.error as string)) || '';
+                                if (error.includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) {
+                                    window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus();
+                                    return;
+                                }
+                                const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { title: StrCast(drawing.title) + ' AI Images', _width: 400, _height: 400 });
                                 drawing.$ai_firefly_generatedDocs = genratedDocs;
                                 (res as Upload.ImageInformation[]).map(info =>
                                     Doc.AddDocToList(
@@ -69,9 +74,7 @@ export class DrawingFillHandler {
                                 if (!DocumentView.getFirstDocumentView(genratedDocs)) DocumentViewInternal.addDocTabFunc(genratedDocs, OpenWhere.addRight);
                             })
                             .catch(e => {
-                                if (e.toString().includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + e.toString().replace(/^[^"]*/, ''))) {
-                                    window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus();
-                                } else alert(e.toString());
+                                alert(e.toString());
                             })
                     ); // prettier-ignore:q
             }
-- 
cgit v1.2.3-70-g09d2


From 63772da2b6f07365023d10c5df93c1e8c4f0b6b6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 30 Mar 2025 11:16:37 -0400
Subject: changed Doc.Layout calls to doc[DocLayout].  fixed flashcard ui
 placement on card view.  fixed css scaling for styleprovider icons and
 annotation resizer

---
 src/client/util/DropConverter.ts                   |   4 +-
 src/client/views/DocComponent.tsx                  |   6 +-
 src/client/views/DocumentDecorations.scss          | 112 ++++++++++--------
 src/client/views/DocumentDecorations.tsx           |  18 +--
 src/client/views/PropertiesView.tsx                |  12 +-
 src/client/views/StyleProvider.scss                |  16 ++-
 src/client/views/StyleProvider.tsx                 |   6 +-
 .../views/collections/CollectionCarouselView.scss  |   4 +-
 .../views/collections/CollectionCarouselView.tsx   |   4 +-
 src/client/views/collections/TabDocView.scss       |  12 +-
 src/client/views/collections/TabDocView.tsx        |  27 ++++-
 src/client/views/collections/TreeView.tsx          |   6 +-
 .../CollectionFreeFormClusters.ts                  |  17 ++-
 .../CollectionFreeFormLayoutEngines.tsx            |   7 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  22 ++--
 .../collections/collectionFreeForm/MarqueeView.tsx |   3 +-
 .../collectionSchema/CollectionSchemaView.tsx      |   2 +-
 .../collectionSchema/SchemaTableCell.tsx           |   3 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   3 +-
 src/client/views/nodes/ComparisonBox.tsx           | 131 +--------------------
 src/client/views/nodes/DocumentContentsView.tsx    | 101 ++++++++++++----
 src/client/views/nodes/DocumentView.tsx            |  44 +------
 src/client/views/nodes/FieldView.tsx               |   9 +-
 src/client/views/nodes/ImageBox.tsx                |  40 ++++---
 src/client/views/nodes/KeyValueBox.tsx             |   3 +-
 src/client/views/nodes/KeyValuePair.tsx            |   3 +-
 src/client/views/nodes/PDFBox.scss                 |   6 +-
 src/client/views/nodes/PDFBox.tsx                  |   4 +-
 .../nodes/formattedText/FormattedTextBox.scss      |   5 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   3 +-
 .../views/nodes/importBox/ImportElementBox.tsx     |   2 -
 src/client/views/pdf/PDFViewer.tsx                 |   3 +-
 src/fields/Doc.ts                                  |  48 +++-----
 src/fields/util.ts                                 |  25 ++--
 34 files changed, 317 insertions(+), 394 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index b5d29be4c..7d3f63448 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,5 +1,5 @@
 import { Doc, DocListCast, StrListCast } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
+import { DocData, DocLayout } from '../../fields/DocSymbols';
 import { ObjectField } from '../../fields/ObjectField';
 import { RichTextField } from '../../fields/RichTextField';
 import { ComputedField, ScriptField } from '../../fields/ScriptField';
@@ -93,7 +93,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
     data?.draggedDocuments.forEach((doc, i) => {
         let dbox = doc;
         // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
-        if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes(FontIconBox.name)) {
+        if (doc.type === DocumentType.FONTICON || StrCast(doc[DocLayout].layout).includes(FontIconBox.name)) {
             if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) {
                 // dbox = Doc.MakeEmbedding(doc);  // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon
                 dbox = Doc.MakeEmbedding(dbox);
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index e3d47317c..d33f3b713 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -3,7 +3,7 @@ import * as React from 'react';
 import { returnFalse } from '../../ClientUtils';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData, DocLayout } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
 import { DocCast, toList } from '../../fields/Types';
 import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
@@ -116,7 +116,7 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
          * This may or may not inherit from the data doc.
          */
         @computed get layoutDoc() {
-            return Doc.Layout(this.Document);
+            return this.Document[DocLayout];
         }
 
         /**
@@ -187,7 +187,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
          * This is the document being rendered.  It may be a template so it may or may no inherit from the data doc.
          */
         @computed get layoutDoc() {
-            return Doc.Layout(this.Document);
+            return this.Document[DocLayout];
         }
 
         /**
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index a5afb1305..732c2645e 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -91,30 +91,71 @@ $resizeHandler: 8px;
             }
         }
 
-        .documentDecorations-closeButton {
+        .documentDecorations-closeWrapper {
             display: flex;
-            align-items: center;
-            justify-content: center;
-            background: #fb9d75;
-            border: solid 1.5px #a94442;
-            color: #fb9d75;
-            transition: 0.1s ease;
-            opacity: 1;
-            pointer-events: all;
-            width: 20px;
-            height: 20px;
-            min-width: 20px;
-            border-radius: 100%;
-            opacity: 0.5;
-            cursor: pointer;
-
-            &:hover {
-                color: #a94442;
+            .documentDecorations-closeButton {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                background: #fb9d75;
+                border: solid 1.5px #a94442;
+                color: #fb9d75;
+                transition: 0.1s ease;
                 opacity: 1;
+                pointer-events: all;
+                width: 20px;
+                height: 20px;
+                min-width: 20px;
+                border-radius: 100%;
+                opacity: 0.5;
+                cursor: pointer;
+
+                &:hover {
+                    color: #a94442;
+                    opacity: 1;
+                    width: 20px;
+                    min-width: 20px;
+                }
+
+                > svg {
+                    margin: 0;
+                }
             }
 
-            > svg {
-                margin: 0;
+            .documentDecorations-minimizeButton {
+                display: none;
+                align-items: center;
+                justify-content: center;
+                background: #ffdd00;
+                border: solid 1.5px #a94442;
+                color: #ffdd00;
+                transition: 0.1s ease;
+                font-size: 11px;
+                opacity: 1;
+                grid-column: 2;
+                pointer-events: all;
+                width: 20px;
+                height: 20px;
+                min-width: 20px;
+                border-radius: 100%;
+                opacity: 0.5;
+                cursor: pointer;
+
+                &:hover {
+                    color: #a94442;
+                    opacity: 1;
+                }
+
+                > svg {
+                    margin: 0;
+                }
+            }
+            &:hover {
+                width: 40px;
+                min-width: 40px;
+                .documentDecorations-minimizeButton {
+                    display: flex;
+                }
             }
         }
 
@@ -145,38 +186,9 @@ $resizeHandler: 8px;
             }
         }
 
-        .documentDecorations-minimizeButton {
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            background: #ffdd00;
-            border: solid 1.5px #a94442;
-            color: #ffdd00;
-            transition: 0.1s ease;
-            font-size: 11px;
-            opacity: 1;
-            grid-column: 2;
-            pointer-events: all;
-            width: 20px;
-            height: 20px;
-            min-width: 20px;
-            border-radius: 100%;
-            opacity: 0.5;
-            cursor: pointer;
-
-            &:hover {
-                color: #a94442;
-                opacity: 1;
-            }
-
-            > svg {
-                margin: 0;
-            }
-        }
-
         .documentDecorations-title {
             opacity: 1;
-            width: calc(100% - 60px); // = margin-left + margin-right
+            width: 100%;
             grid-column: 3;
             pointer-events: auto;
             overflow: hidden;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 92b4d6fbf..7842c233a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -320,7 +320,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
                 const dist = moveEv.clientX < x && moveEv.clientY < y ? 0 : Math.sqrt((moveEv.clientX - x) * (moveEv.clientX - x) + (moveEv.clientY - y) * (moveEv.clientY - y));
                 DocumentView.SelectedDocs().forEach(doc => {
-                    const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2);
+                    const docMax = Math.min(NumCast(doc._width) / 2, NumCast(doc._height) / 2);
                     const radius = Math.min(1, dist / maxDist) * docMax; // set radius based on ratio of drag distance to half diagonal distance of bounding box
                     doc._layout_borderRounding = `${radius}px`;
                 });
@@ -388,7 +388,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         DocumentView.Selected().forEach(dv => {
             const accumRot = (NumCast(dv.Document._rotation) / 180) * Math.PI;
             const localRotCtr = dv.screenToViewTransform().transformPoint(rcScreen.X, rcScreen.Y);
-            const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document.width) / 2, localRotCtr[1] - NumCast(dv.Document.height) / 2];
+            const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document._width) / 2, localRotCtr[1] - NumCast(dv.Document._height) / 2];
             const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot);
             const unrotatedDocPos = { x: NumCast(dv.Document.x) + localRotCtrOffset[0] - startRotCtr.x, y: NumCast(dv.Document.y) + localRotCtrOffset[1] - startRotCtr.y };
             infos.set(dv.Document, { unrotatedDocPos, startRotCtr, accumRot });
@@ -623,8 +623,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
                 doc.$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,
+                        X: NumCast(doc.x) - x + (NumCast(doc._width) * ipt.X) / width,
+                        Y: NumCast(doc.y) - y + (NumCast(doc._height) * ipt.Y) / height,
                     }))); // prettier-ignore
                 Doc.SetNativeWidth(doc, undefined);
                 Doc.SetNativeHeight(doc, undefined);
@@ -719,7 +719,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         // Radius constants
         const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
         const borderRadius = numberValue(Cast(seldocview.Document.layout_borderRounding, 'string', null));
-        const docMax = Math.min(NumCast(seldocview.Document.width) / 2, NumCast(seldocview.Document.height) / 2);
+        const docMax = Math.min(NumCast(seldocview.Document._width) / 2, NumCast(seldocview.Document._height) / 2);
         const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
         const radiusHandle = (borderRadius / docMax) * maxDist;
         const radiusHandleLocation = Math.min(radiusHandle, maxDist);
@@ -773,7 +773,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                     </span>
                 )}
                 {sharingMenu}
-                {!useLock ? null : (
+                {!useLock || hideTitle ? null : (
                     <Tooltip key="lock" title={<div className="dash-tooltip">toggle ability to interact with document</div>} placement="top">
                         <div className="documentDecorations-lock" style={{ color: seldocview.Document._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown}>
                             <FontAwesomeIcon size="sm" icon="lock" />
@@ -818,8 +818,10 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                     display: hideDeleteButton && hideTitle && hideOpenButton ? 'none' : undefined,
                                 }}
                                 onPointerDown={this.onContainerDown}>
-                                {hideDeleteButton ? null : topBtn('close', 'times', undefined, () => this.onCloseClick(true), 'Close')}
-                                {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, () => this.onCloseClick(undefined), 'Minimize')}
+                                <div className="documentDecorations-closeWrapper">
+                                    {hideDeleteButton ? null : topBtn('close', 'times', undefined, () => this.onCloseClick(true), 'Close')}
+                                    {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, () => this.onCloseClick(undefined), 'Minimize')}
+                                </div>
                                 {titleArea}
                                 {hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection, opption: in editor view)')}
                             </div>
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 7e9cd002b..7fcb15afe 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -13,7 +13,7 @@ import ResizeObserver from 'resize-observer-polyfill';
 import { ClientUtils, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
 import { Doc, DocListCast, Field, FieldResult, FieldType, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast, returnEmptyDoclist } from '../../fields/Doc';
-import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols';
+import { AclAdmin, DocAcl, DocData, DocLayout } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { InkField } from '../../fields/InkField';
 import { List } from '../../fields/List';
@@ -205,7 +205,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
             const ids = new Set<string>(reqdKeys);
             const docs: Doc[] =
                 DocumentView.Selected().length < 2 //
-                    ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc]
+                    ? [this.layoutFields ? this.selectedDoc[DocLayout] : this.dataDoc]
                     : DocumentView.Selected().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
             docs.forEach(doc =>
                 Object.keys(doc)
@@ -264,7 +264,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         const docs =
             DocumentView.Selected().length < 2 && this.selectedDoc
                 ? [this.layoutFields
-                        ? Doc.Layout(this.selectedDoc) //
+                        ? this.selectedDoc[DocLayout] //
                         : this.dataDoc!]
                 : DocumentView.Selected().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); // prettier-ignore
         docs.forEach(doc => {
@@ -330,7 +330,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
             return '-- multiple selected --';
         }
         if (this.selectedDoc) {
-            const layoutDoc = Doc.Layout(this.selectedDoc);
+            const layoutDoc = this.selectedDoc[DocLayout];
             const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes('FormattedTextBox') ? this.rtfHeight : this.docHeight;
             const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes('FormattedTextBox') ? this.rtfWidth : this.docWidth;
             return (
@@ -894,9 +894,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         });
     }
     @computed get borderColor() {
-        const doc = this.selectedDoc;
-        const layoutDoc = doc ? Doc.Layout(doc) : doc;
-        return StrCast(layoutDoc.color);
+        return StrCast(this.selectedDoc._color);
     }
     set borderColor(value) { this.selectedDoc && (this.selectedDoc.$color = value || undefined); } // prettier-ignore
 
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index ce00f6101..501b9a292 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -5,10 +5,10 @@
 .styleProvider-lock {
     z-index: 2; // has to be above title which is z-index 1
     font-size: 10;
-    width: 15;
-    height: 15;
+    width: 20;
+    height: 20;
     position: absolute;
-    right: -15;
+    right: -20;
     top: 0;
     background: black;
     pointer-events: all;
@@ -21,22 +21,26 @@
     cursor: default;
 }
 .styleProvider-filter {
-    right: 15;
+    right: 20;
     .styleProvider-filterShift {
         left: 0;
         top: 0;
         position: absolute;
     }
+    .dropdown-container {
+        width: 15px !important;
+        margin: auto !important;
+    }
 }
 .styleProvider-audio {
-    right: 30;
+    right: 40;
 }
 .styleProvider-paint-selected,
 .styleProvider-paint {
     top: 15;
 }
 .styleProvider-paint-selected {
-    right: -30;
+    right: -40;
 }
 .styleProvider-lock:hover,
 .styleProvider-audio:hover,
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index e25227c00..a59d39cfb 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -20,11 +20,13 @@ import { SnappingManager } from '../util/SnappingManager';
 import { undoable, UndoManager } from '../util/UndoManager';
 import { TreeSort } from './collections/TreeSort';
 import { Colors } from './global/globalEnums';
-import { DocumentView, DocumentViewProps } from './nodes/DocumentView';
+import { DocumentViewProps } from './nodes/DocumentContentsView';
+import { DocumentView } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
 import { StyleProp } from './StyleProp';
 import './StyleProvider.scss';
 import { styleProviderQuiz } from './StyleProviderQuiz';
+import { DocLayout } from '../../fields/DocSymbols';
 
 function toggleLockedPosition(doc: Doc) {
     UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
@@ -87,7 +89,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
     const isNonTransparent = property.includes(':nonTransparent');
     const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent');
     const isAnnotated = property.includes(':annotated');
-    const layoutDoc = doc ? Doc.Layout(doc) : doc;
+    const layoutDoc = doc?.[DocLayout];
     const isOpen = property.includes(':treeOpen');
     const boxBackground = property.includes(':docView'); // background color of docView's bounds can be different than the background of contents -- eg FontIconBox
     const {
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
index 544b3e262..1080aa703 100644
--- a/src/client/views/collections/CollectionCarouselView.scss
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -1,8 +1,10 @@
 .collectionCarouselView-outer {
     height: 100%;
-    position: relative;
+    position: absolute;
     overflow: hidden;
     display: flex;
+    transform-origin: top left;
+
     .collectionCarouselView-caption {
         height: 50;
         display: inline-block;
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 3fc56d7d9..c80ac23eb 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -137,7 +137,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                 renderDepth={this._props.renderDepth + 1}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
-                TemplateDataDocument={DocCast(Doc.Layout(doc).rootDocument)?.[DocData]}
+                TemplateDataDocument={doc[DocData]}
                 childFilters={this.childDocFilters}
                 focus={this.focus}
                 hideDecorations={BoolCast(this.layoutDoc.layout_hideDecorations)}
@@ -243,11 +243,9 @@ export class CollectionCarouselView extends CollectionSubView() {
                         color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string,
                         left: NumCast(this.layoutDoc._xMargin),
                         top: NumCast(this.layoutDoc._yMargin),
-                        transformOrigin: 'top left',
                         transform: `scale(${this.nativeScaling()})`,
                         width: `calc(${100 / this.nativeScaling()}% - ${(2 * NumCast(this.layoutDoc._xMargin)) / this.nativeScaling()}px)`,
                         height: `calc(${100 / this.nativeScaling()}% - ${(2 * NumCast(this.layoutDoc._yMargin)) / this.nativeScaling()}px)`,
-                        position: 'relative',
                     }}>
                     {this.content}
                     {this.navButtons}
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index 397e35ca9..931cdac2b 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -4,7 +4,6 @@
     height: 100%;
     width: 100%;
 }
-
 input.lm_title:focus,
 input.lm_title {
     max-width: unset !important;
@@ -22,7 +21,6 @@ input.lm_title {
 .lm_iconWrap {
     display: flex;
     color: black;
-    width: 15px;
     height: 15px;
     align-items: center;
     align-self: center;
@@ -30,6 +28,16 @@ input.lm_title {
     margin: 3px;
     border-radius: 20%;
 
+    width: auto;
+    svg:nth-of-type(2) {
+        display: none;
+    }
+    &:hover {
+        svg:nth-of-type(2) {
+            display: block;
+        }
+    }
+
     .moreInfoDot {
         background-color: white;
         border-radius: 100%;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 620be2726..568a08792 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -10,7 +10,7 @@ import ResizeObserver from 'resize-observer-polyfill';
 import { ClientUtils, DashColor, lightOrDark, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
 import { Doc, Opt, returnEmptyDoclist } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
+import { DocData, DocLayout } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { FieldId } from '../../../fields/RefField';
@@ -41,6 +41,7 @@ import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionView } from './CollectionView';
 import './TabDocView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { Tooltip } from '@mui/material';
 
 interface TabMinimapViewProps {
     doc: Doc;
@@ -311,7 +312,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     @observable _view: DocumentView | undefined = undefined;
     @observable _forceInvalidateScreenToLocal = 0; // screentolocal is computed outside of react using a dom resize ovbserver.  this hack allows the resize observer to trigger a react update
 
-    @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } // prettier-ignore
+    @computed get layoutDoc() { return this._document?.[DocLayout]; } // prettier-ignore
     @computed get isUserActivated() {  return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive; } // prettier-ignore
     @computed get isContentActive() { return this.isUserActivated || this._hovering; } // prettier-ignore
 
@@ -353,7 +354,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
 
             if (tab.element[0].children[1].children.length === 1) {
                 iconWrap.className = 'lm_iconWrap lm_moreInfo';
-                iconWrap.title = 'click for menu, drag to embed in document';
                 const dragBtnDown = (e: React.PointerEvent) => {
                     setupMoveUpEvents(
                         this,
@@ -377,7 +377,26 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                     );
                 };
 
-                const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />;
+                const docIcon = (
+                    <>
+                        <Tooltip title="click for menu, drag to embed in document">
+                            <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />
+                        </Tooltip>
+                        <Tooltip title="click to open in lightbox">
+                            <FontAwesomeIcon
+                                onPointerDown={dragBtnDown}
+                                icon="external-link-alt"
+                                onClick={() => {
+                                    if (doc.layout_fieldKey === 'layout_icon') {
+                                        const odoc = Doc.GetEmbeddings(doc).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(doc);
+                                        Doc.deiconifyView(odoc);
+                                    }
+                                    this.addDocTab(doc, OpenWhere.lightboxAlways);
+                                }}
+                            />
+                        </Tooltip>
+                    </>
+                );
                 const closeIcon = <FontAwesomeIcon icon="eye" />;
                 ReactDOM.createRoot(iconWrap).render(docIcon);
                 ReactDOM.createRoot(closeWrap).render(closeIcon);
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 9889766d5..4dc937864 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -7,7 +7,7 @@ import * as React from 'react';
 import { ClientUtils, lightOrDark, return18, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
 import { Doc, DocListCast, Field, FieldType, Opt, StrListCast, returnEmptyDoclist } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
+import { DocData, DocLayout } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
@@ -156,7 +156,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         return this.Document[DocData];
     }
     @computed get layoutDoc() {
-        return Doc.Layout(this.Document);
+        return this.Document[DocLayout];
     }
     @computed get fieldKey() {
         return StrCast(this.Document._treeView_FieldKey, Doc.LayoutFieldKey(this.Document));
@@ -1309,7 +1309,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1]));
                 const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView._props.parentTreeView : undefined);
                 const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
-                const childLayout = Doc.Layout(pair.layout);
+                const childLayout = pair.layout[DocLayout];
                 const rowHeight = () => {
                     const aspect = Doc.NativeAspect(childLayout);
                     return aspect ? Math.min(NumCast(childLayout._width), rowWidth()) / aspect : NumCast(childLayout._height);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
index 3838852dd..903d92c90 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
@@ -31,16 +31,14 @@ export class CollectionFreeFormClusters {
     get selectDocuments()    { return this._view.selectDocuments; } // prettier-ignore
 
     static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
-        const doc2Layout = Doc.Layout(doc2);
-        const doc1Layout = Doc.Layout(doc1);
         const x2 = NumCast(doc2.x) - clusterDistance;
         const y2 = NumCast(doc2.y) - clusterDistance;
-        const w2 = NumCast(doc2Layout._width) + clusterDistance;
-        const h2 = NumCast(doc2Layout._height) + clusterDistance;
+        const w2 = NumCast(doc2._width) + clusterDistance;
+        const h2 = NumCast(doc2._height) + clusterDistance;
         const x = NumCast(doc1.x) - clusterDistance;
         const y = NumCast(doc1.y) - clusterDistance;
-        const w = NumCast(doc1Layout._width) + clusterDistance;
-        const h = NumCast(doc1Layout._height) + clusterDistance;
+        const w = NumCast(doc1._width) + clusterDistance;
+        const h = NumCast(doc1._height) + clusterDistance;
         return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
     }
     handlePointerDown(probe: number[]) {
@@ -49,12 +47,11 @@ export class CollectionFreeFormClusters {
             .reduce((cluster, cd) => {
                 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;
                     const cy = NumCast(cd.y) - this._clusterDistance / 2;
-                    const cw = NumCast(layoutDoc._width) + this._clusterDistance;
-                    const ch = NumCast(layoutDoc._height) + this._clusterDistance;
-                    return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
+                    const cw = NumCast(cd._width) + this._clusterDistance;
+                    const ch = NumCast(cd._height) + this._clusterDistance;
+                    return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
                 }
                 return cluster;
             }, -1);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 4ea1de680..158bac7ba 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,5 +1,6 @@
 /* eslint-disable no-use-before-define */
 import { Doc, Field, FieldType, FieldResult } from '../../../../fields/Doc';
+import { DocLayout } from '../../../../fields/DocSymbols';
 import { Id, ToString } from '../../../../fields/FieldSymbols';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { RefField } from '../../../../fields/RefField';
@@ -237,7 +238,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
             payload: val,
         });
         val?.docs.forEach((doc, i) => {
-            const layoutDoc = Doc.Layout(doc);
+            const layoutDoc = doc[DocLayout];
             let wid = pivotAxisWidth;
             let hgt = pivotAxisWidth / (Doc.NativeAspect(layoutDoc) || 1);
             if (hgt > pivotAxisWidth) {
@@ -249,7 +250,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
                 y: -y + (pivotAxisWidth - hgt) / 2,
                 width: wid,
                 height: hgt,
-                backgroundColor: StrCast(layoutDoc.backgroundColor, 'white'),
+                backgroundColor: StrCast(doc.backgroundColor, 'white'),
                 pair: { layout: doc },
                 replica: val.replicas[i],
             });
@@ -362,7 +363,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc:
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
         keyDocs.forEach(doc => {
             const stack = findStack(x, stacking);
-            const layoutDoc = Doc.Layout(doc);
+            const layoutDoc = doc[DocLayout];
             let wid = pivotAxisWidth;
             let hgt = pivotAxisWidth / (Doc.NativeAspect(layoutDoc) || 1);
             if (hgt > pivotAxisWidth) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 09d43c5b0..f01fb8fc7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -11,7 +11,7 @@ import ReactLoading from 'react-loading';
 import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
-import { DocData, Height, Width } from '../../../../fields/DocSymbols';
+import { DocData, DocLayout, Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkData, InkEraserTool, InkField, InkInkTool, InkTool, Segment } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -286,7 +286,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
     panX = () => this.fitContentBounds?.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(this.Document.freeform_panX, 1));
     panY = () => this.fitContentBounds?.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(this.Document.freeform_panY, 1));
-    zoomScaling = () => this.fitContentBounds?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.rootDocument)?.[this.scaleFieldKey], 1));
+    zoomScaling = () => this.fitContentBounds?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.rootDocument)?.[this.scaleFieldKey], 1));
     PanZoomCenterXf = () => (this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.centeringShiftX}px, ${this.centeringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`);
     ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy();
     getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
@@ -440,7 +440,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
 
             docDragData.droppedDocuments.forEach((d, i) => {
-                const layoutDoc = Doc.Layout(d);
+                const layoutDoc = d[DocLayout];
                 const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], fromScreenXf.Rotate);
                 if (this.Document._currentFrame !== undefined) {
                     CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
@@ -1440,9 +1440,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
-        const layoutdoc = Doc.Layout(doc);
         const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
-        const pt2 = xf.transformPoint(NumCast(doc.x) + NumCast(layoutdoc._width), NumCast(doc.y) + NumCast(layoutdoc._height));
+        const pt2 = xf.transformPoint(NumCast(doc.x) + NumCast(doc._width), NumCast(doc.y) + NumCast(doc._height));
         const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
 
         if (scale !== undefined) {
@@ -1489,9 +1488,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
      * @returns whether the new text doc was created and added successfully
      */
     createTextDocCopy = undoable((textBox: FormattedTextBox, below: boolean) => {
-        const textDoc = DocCast(textBox.Document, textBox.Document);
+        const textDoc = DocCast(textBox.Document);
         const newDoc = Doc.MakeCopy(textDoc, true);
-        newDoc['$' + Doc.LayoutFieldKey(newDoc, textBox._props.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style
+        newDoc['$' + Doc.LayoutFieldKey(newDoc)] = undefined; // the copy should not copy the text contents of it source, just the render style
         newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
         newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
         DocumentView.SetSelectOnLoad(newDoc);
@@ -1600,14 +1599,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     getCalculatedPositions(pair: { layout: Doc; data?: Doc }): PoolData {
         const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min);
         const childDoc = pair.layout;
-        const childDocLayout = Doc.Layout(childDoc);
         const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values
-        const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
+        const contentFrameNumber = Cast(childDoc._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
         const { z, zIndex } = childDoc;
         const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber);
         const { x, y, autoDim, _width, _height, opacity, _rotation } =
             layoutFrameNumber === undefined // -1 for width/height means width/height should be PanelWidth/PanelHeight (prevents collectionfreeformdocumentview width/height from getting out of synch with panelWIdth/Height which causes detailView to re-render and lose focus because HTMLtag scaling gets set to a bad intermediate value)
-                ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this._props.childOpacity?.() }
+                ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDoc._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this._props.childOpacity?.() }
                 : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
         // prettier-ignore
         const rotation = Cast(_rotation,'number',
@@ -1625,8 +1623,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             zIndex: Cast(zIndex, 'number'),
             width: _width,
             height: _height,
-            transition: StrCast(childDocLayout.dataTransition),
-            showTags: BoolCast(childDocLayout.showTags) || BoolCast(this.Document.showChildTags) || BoolCast(this.Document._layout_showTags),
+            transition: StrCast(childDoc.dataTransition),
+            showTags: BoolCast(childDoc.showTags) || BoolCast(this.Document.showChildTags) || BoolCast(this.Document._layout_showTags),
             pointerEvents: Cast(childDoc.pointerEvents, 'string', null),
             pair,
             replica: '',
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index eaa8826ed..ca7e7a311 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -591,8 +591,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
     marqueeSelect = (selectBackgrounds: boolean = false, docType: DocumentType | undefined = undefined) => {
         const selection: Doc[] = [];
         const selectFunc = (doc: Doc) => {
-            const layoutDoc = Doc.Layout(doc);
-            const bounds = { left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(layoutDoc._width), height: NumCast(layoutDoc._height) };
+            const bounds = { left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) };
             if (!this._lassoFreehand) {
                 intersectRect(bounds, this.Bounds) && selection.push(doc);
             } else {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 05670562e..5803acca0 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -200,7 +200,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         this._props.setContentViewBox?.(this);
         document.addEventListener('keydown', this.onKeyDown);
 
-        Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1]));
+        Object.entries(this._documentOptions).forEach(pair => this.fieldInfos.set(pair[0], pair[1] as FInfo));
         this._keysDisposer = observe(
             this.dataDoc[this.fieldKey ?? 'data'] as List<Doc>,
             change => {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 72838074e..173984dc7 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -32,6 +32,7 @@ import { FInfotoColType } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaCellField } from './SchemaCellField';
+import { DocLayout } from '../../../../fields/DocSymbols';
 
 /**
  * SchemaTableCells make up the majority of the visual representation of the SchemaView.
@@ -104,7 +105,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
     public static renderProps(props: SchemaTableCellProps) {
         const { Doc: Document, fieldKey, /* getFinfo,*/ columnWidth, isRowActive } = props;
         let protoCount = 0;
-        const layoutDoc = fieldKey.startsWith('_') ? Doc.Layout(Document) : Document;
+        const layoutDoc = fieldKey.startsWith('_') ? Document[DocLayout] : Document;
         let doc = Document;
         while (doc) {
             if (Object.keys(doc).includes(fieldKey.replace(/^_/, ''))) break;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 943d2175c..53a3d3631 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -17,9 +17,10 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { DocComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProp';
 import './CollectionFreeFormDocumentView.scss';
-import { DocumentView, DocumentViewProps } from './DocumentView';
+import { DocumentView } from './DocumentView';
 import { FieldViewProps } from './FieldView';
 import { OpenWhere } from './OpenWhere';
+import { DocumentViewProps } from './DocumentContentsView';
 
 export enum GroupActive { // flags for whether a view is activate because of its relationship to a group
     group = 'group', // this is a group that is activated because it's on an active canvas, but is not part of some other group
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 482af336c..6d5891003 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -29,7 +29,6 @@ import '../pdf/GPTPopup/GPTPopup.scss';
 import './ComparisonBox.scss';
 import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { TraceMobx } from '../../../fields/util';
 
 const API_URL = 'https://api.unsplash.com/search/photos';
@@ -285,13 +284,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     };
 
     @action handleRenderGPTClick = () => {
-        const phonTrans = DocCast(this.Document.audio) ? DocCast(this.Document.audio).phoneticTranscription : undefined;
-        if (phonTrans) {
-            this._inputValue = StrCast(phonTrans);
-            this.askGPTPhonemes(this._inputValue);
-            this._renderSide = this.backKey;
-            this._outputValue = '';
-        } else if (this._inputValue) this.askGPT(GPTCallType.QUIZDOC);
+        if (this._inputValue) this.askGPT(GPTCallType.QUIZDOC);
     };
 
     onPointerMove = ({ movementX }: PointerEvent) => {
@@ -467,45 +460,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         this._props.DocumentView?.()._props.addDocument?.(newAudio);
     };
 
-    /**
-     * Gets the transcription of an audio recording by sending the
-     * recording to backend.
-     */
-    pushInfo = () =>
-        axios
-            .post(
-                'http://localhost:105/recognize/', //
-                { file: DocCast(this.Document.audio).$url },
-                { headers: { 'Content-Type': 'application/json' } }
-            )
-            .then(response => {
-                this.Document.phoneticTranscription = response.data.transcription;
-            });
-
-    /**
-     * Extracts the id of the youtube video url.
-     * @param url
-     * @returns
-     */
-    getYouTubeVideoId = (url: string) => {
-        const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=|\?v=)([^#&?]*).*/;
-        const match = url.match(regExp);
-        return match && match[2].length === 11 ? match[2] : null;
-    };
-
-    /**
-     * Gets the transcript of a youtube video by sending the video url to the backend.
-     * @returns transcription of youtube recording
-     */
-    youtubeUpload = async () =>
-        axios
-            .post(
-                'http://localhost:105/youtube/', //
-                { file: this.getYouTubeVideoId(this.frontText) },
-                { headers: { 'Content-Type': 'application/json' } }
-            )
-            .then(response => response.data.transcription);
-
     /**
      * Calls GPT for each flashcard type.
      */
@@ -539,45 +493,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     layoutWidth = () => NumCast(this.layoutDoc.width, 200);
     layoutHeight = () => NumCast(this.layoutDoc.height, 200);
 
-    /**
-     * Ask GPT for advice on how to improve speech by comparing the phonetic transcription of
-     * a users audio recording with the phonetic transcription of their intended sentence.
-     * @param phonemes
-     */
-    askGPTPhonemes = async (phonemes: string) => {
-        const sentence = this.frontText;
-        const phon6 = 'huː ɑɹ juː tədeɪ';
-        const phon4 = 'kamo estas hɔi';
-        const promptEng =
-            'Consider all possible phonetic transcriptions of the intended sentence "' +
-            sentence +
-            '" that is standard in American speech without showing the user. Compare each word in the following phonemes with those phonetic transcriptions without displaying anything to the user: "' +
-            phon6 +
-            '". Steps to do this: Align the words with each word in the intended sentence by combining the phonemes to get a pronunciation that resembles the word in order. Do not describe phonetic corrections with the phonetic alphabet - describe it by providing other examples of how it should sound. Note if a word or sound missing, including missing vowels and consonants. If there is an additional word that does not match with the provided sentence, say so. For each word, if any letters mismatch and would sound weird in American speech and they are not allophones of the same phoneme and they are far away from each on the ipa vowel chat and that pronunciation is not normal for the meaning of the word, note this difference and explain how it is supposed to sound. Only note the difference if they are not allophones of the same phoneme and if they are far away on the vowel chart. The goal is to be understood, not sound like a native speaker. Just so you know, "i" sounds like "ee" as in "bee", not "ih" as an "lick". Interpret "ɹ" as the same as "r". Interpret "ʌ" as the same as "ə". If "ɚ", "ɔː", and "ɔ" are options for pronunciation, do not choose "ɚ". Ignore differences with colons. Ignore redundant letters and words and sounds and the splitting of words; do not mention this since there could be repeated words in the sentence. Provide a response like this: "Lets work on improving the pronunciation of "coffee." You said "ceeffee," which is close, but we need to adjust the vowel sound. In American English, "coffee" is pronounced /ˈkɔːfi/, with a long "aw" sound. Try saying "kah-fee." Your intonation is good, but try putting a bit more stress on "like" in the sentence "I would like a coffee with milk." This will make your speech sound more natural. Keep practicing, and lets try saying the whole sentence again!"';
-        const promptSpa =
-            'Consider all possible phonetic transcriptions of the intended sentence "' +
-            'como estás hoy' +
-            '" that is standard in Spanish speech without showing the user. Compare each word in the following phonemes with those phonetic transcriptions without displaying anything to the user: "' +
-            phon4 +
-            '". Steps to do this: Align the words with each word in the intended sentence by combining the phonemes to get a pronunciation that resembles the word in order. Do not describe phonetic corrections with the phonetic alphabet - describe it by providing other examples of how it should sound. Note if a word or sound missing, including missing vowels and consonants. If there is an additional word that does not match with the provided sentence, say so. For each word, if any letters mismatch and would sound weird in Spanish speech and they are not allophones of the same phoneme and they are far away from each on the ipa vowel chat and that pronunciation is not normal for the meaning of the word, note this difference and explain how it is supposed to sound.  Only note the difference if they are not allophones of the same phoneme and if they are far away on the vowel chart; say good job if it would be understood by a native Spanish speaker. Just so you know, "i" sounds like "ee" as in "bee", not "ih" as an "lick". Interpret "ɹ" as the same as "r". Interpret "ʌ" as the same as "ə". Do not make "θ" and "f" interchangable. Do not make "n" and "ɲ" interchangable. Do not make "e" and "i" interchangable. If "ɚ", "ɔː", and "ɔ" are options for pronunciation, do not choose "ɚ". Ignore differences with colons. Ignore redundant letters and words and sounds and the splitting of words; do not mention this since there could be repeated words in the sentence. Identify "ɔi" sounds like "oy". Ignore accents and do not say anything to the user about this.';
-        const promptAll =
-            'Consider all possible phonetic transcriptions of the intended sentence "' +
-            sentence +
-            '" that is standard in ' +
-            this.convertAbr() +
-            ' speech without showing the user. Compare each word in the following phonemes with those phonetic transcriptions without displaying anything to the user: "' +
-            phonemes +
-            '". Steps to do this: Align the words with each word in the intended sentence by combining the phonemes to get a pronunciation that resembles the word in order. Do not describe phonetic corrections with the phonetic alphabet - describe it by providing other examples of how it should sound. Note if a word or sound missing, including missing vowels and consonants. If there is an additional word that does not match with the provided sentence, say so. For each word, if any letters mismatch and would sound weird in ' +
-            this.convertAbr() +
-            ' speech and they are not allophones of the same phoneme and they are far away from each on the ipa vowel chat and that pronunciation is not normal for the meaning of the word, note this difference and explain how it is supposed to sound. Just so you know, "i" sounds like "ee" as in "bee", not "ih" as an "lick". Interpret "ɹ" as the same as "r". Interpret "ʌ" as the same as "ə". Do not make "θ" and "f" interchangable. Do not make "n" and "ɲ" interchangable. Do not make "e" and "i" interchangable. If "ɚ", "ɔː", and "ɔ" are options for pronunciation, do not choose "ɚ". Ignore differences with colons. Ignore redundant letters and words and sounds and the splitting of words; do not mention this since there could be repeated words in the sentence. Provide a response like this: "Lets work on improving the pronunciation of "coffee." You said "cawffee," which is close, but we need to adjust the vowel sound. In American English, "coffee" is pronounced /ˈkɔːfi/, with a long "aw" sound. Try saying "kah-fee." Your intonation is good, but try putting a bit more stress on "like" in the sentence "I would like a coffee with milk." This will make your speech sound more natural. Keep practicing, and lets try saying the whole sentence again!"';
-
-        switch (this._recognition.lang) {
-            case 'en-US': this._outputValue = await gptAPICall(promptEng, GPTCallType.PRONUNCIATION); break;
-            case 'es-ES': this._outputValue = await gptAPICall(promptSpa, GPTCallType.PRONUNCIATION); break;
-            default:      this._outputValue = await gptAPICall(promptAll, GPTCallType.PRONUNCIATION); break;
-        } // prettier-ignore
-    };
-
     /**
      * Display a user's speech to text result.
      * @param e
@@ -637,31 +552,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
     };
 
-    testForTextFields = (whichSlot: string) => {
-        const slotData = Doc.Get(this.dataDoc, whichSlot, true);
-        const slotHasText = slotData instanceof RichTextField || typeof slotData === 'string';
-        const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim();
-        const altText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text.trim();
-        const layoutTemplateString =
-            slotHasText ? FormattedTextBox.LayoutString(whichSlot):
-            whichSlot === this.frontKey ? (subjectText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey) : undefined) :
-                                          altText !== undefined     ? FormattedTextBox.LayoutString(this.fieldKey + '_alternate'): undefined; // prettier-ignore
-
-        // A bit hacky to try out the concept of using GPT to fill in flashcards
-        //   If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string)
-        // and the fieldKey + "_alternate" has text that includes a GPT query (indicated by (( && )) ) that is parameterized (optionally) by the fieldKey text (this) or other metadata (this.<field>).
-        // eg., this.text_alternate is
-        //     "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))"
-        //     where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field
-        // The GPT call will put the "answer"  in the second slot of the comparison (eg., text_0)
-        if (whichSlot === this.backKey && !layoutTemplateString?.includes(whichSlot)) {
-            const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in Doc.setField but it doesn't know about the fieldKey ...
-            if (queryText?.match(/\(\(.*\)\)/)) {
-                Doc.SetField(this.Document, whichSlot, ':=' + queryText); // make the second slot be a computed field on the data doc that calls ChatGpt
-            }
-        }
-        return layoutTemplateString;
-    };
     childActiveFunc = () => this._childActive;
 
     contentScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1);
@@ -682,24 +572,21 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     displayDoc = (whichSlot: string) => {
         const whichDoc = DocCast(this.dataDoc[whichSlot]);
         const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
-        const layoutString = targetDoc ? '' : this.testForTextFields(whichSlot);
 
-        return targetDoc || layoutString ? (
+        return targetDoc ? (
             <>
                 <DocumentView
                     {...this._props}
-                    Document={layoutString ? this.Document : targetDoc}
+                    Document={targetDoc}
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
                     renderDepth={this.props.renderDepth + 1}
-                    LayoutTemplateString={layoutString}
                     containerViewPath={this._props.docViewPath}
                     ScreenToLocalTransform={this.contentScreenToLocalXf}
                     isDocumentActive={returnFalse}
                     isContentActive={this.childActiveFunc}
                     showTags={undefined}
                     fitWidth={this.childFitWidth} // set to returnTrue to make images fill the comparisonBox-- should be a user option
-                    ignoreUsePath={layoutString ? true : undefined}
                     moveDocument={whichSlot === this.frontKey ? this.moveDocFront : this.moveDocBack}
                     removeDocument={whichSlot === this.frontKey ? this.remDocFront : this.remDocBack}
                     dontSelect={returnTrue}
@@ -745,18 +632,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             </div>
             <div>
                 <div className="submit-button">
-                    {/* <div className="submit-buttonschema-header-button" onPointerDown={e => this.openContextMenu(e.clientX, e.clientY, false)}>
-                        <FontAwesomeIcon color="white" icon="caret-down" />
-                    </div> */}
-                    {/* <button className="submit-buttonrecord" onClick={this._listening ? this.stopListening : this.startListening} style={{ background: this._listening ? 'lightgray' : '' }}>
-                        {<FontAwesomeIcon icon="microphone" size="lg" />}
-                    </button> */}
-                    {/* <div className="submit-buttonschema-header-button" onPointerDown={e => this.openContextMenu(e.clientX, e.clientY, true)} style={{ left: '50px', zIndex: '100' }}>
-                        <FontAwesomeIcon color="white" icon="caret-down" />
-                    </div> */}
-                    {/* <button className="submit-buttonpronunciation" onClick={this.evaluatePronunciation}>
-                        Evaluate Pronunciation
-                    </button> */}
                     <button className="submit-buttonsubmit" type="button" onClick={this._renderSide === this.backKey ? () => this.animateFlipping(this.frontKey) : this.handleRenderGPTClick}>
                         {this._renderSide === this.backKey ? 'Redo the Question' : 'Submit'}
                     </button>
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index d1eae1784..6f004bed3 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -11,10 +11,85 @@ import { Cast, DocCast, StrCast } from '../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { ObservableReactComponent, ObserverJsxParser } from '../ObservableReactComponent';
 import './DocumentView.scss';
-import { FieldViewProps } from './FieldView';
+import { FieldViewProps, FieldViewSharedProps } from './FieldView';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
+import { Property } from 'csstype';
+
+interface DocOnlyProps {
+    LayoutTemplate?: () => Opt<Doc>;
+    LayoutTemplateString?: string;
+    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
+    hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+    hideDocumentButtonBar?: boolean;
+    hideOpenButton?: boolean;
+    hideDeleteButton?: boolean;
+    hideLinkAnchors?: boolean;
+    hideLinkButton?: boolean;
+    hideCaptions?: boolean;
+    contentPointerEvents?: Property.PointerEvents | 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
+    dontCenter?: 'x' | 'y' | 'xy';
+    showTags?: boolean;
+    showAIEditor?: boolean;
+    hideFilterStatus?: 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.
+    dragWhenActive?: boolean;
+    dontHideOnDrag?: boolean;
+    onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
+    DataTransition?: () => string | undefined;
+    NativeWidth?: () => number;
+    NativeHeight?: () => number;
+    contextMenuItems?: () => { script?: ScriptField; method?: () => void; filter?: ScriptField; label: string; icon: string }[];
+    dragConfig?: (data: DragManager.DocumentDragData) => void;
+    dragStarting?: () => void;
+    dragEnding?: () => void;
+
+    reactParent?: React.Component; // parent React component view (see CollectionFreeFormDocumentView)
+}
+const DocOnlyProps = [
+    'layoutFieldKey',
+    'LayoutTemplate',
+    'LayoutTemplateString',
+    'hideDecorations', // whether to suppress all DocumentDecorations when doc is selected
+    'hideResizeHandles', // whether to suppress resized handles on doc decorations when this document is selected
+    'hideTitle', // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+    'hideDecorationTitle', // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+    'hideDocumentButtonBar',
+    'hideOpenButton',
+    'hideDeleteButton',
+    'hideLinkAnchors',
+    'hideLinkButton',
+    'hideCaptions',
+    'contentPointerEvents', // 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
+    'dontCenter',
+    'showTags',
+    'showAIEditor',
+    'hideFilterStatus',
+    'childHideDecorationTitle',
+    'childHideResizeHandles',
+    'childDragAction', // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
+    'dragWhenActive',
+    'dontHideOnDrag',
+    'onClickScriptDisable', // undefined = only when selected
+    'DataTransition',
+    'NativeWidth',
+    'NativeHeight',
+    'contextMenuItems',
+    'dragConfig',
+    'dragStarting',
+    'dragEnding',
+
+    'reactParent', // parent React component view (see CollectionFreeFormDocumentView)
+];
+
+export interface DocumentViewProps extends DocOnlyProps, FieldViewSharedProps {}
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
-export interface JsxBindings {
+interface JsxBindings {
     props: BindingProps;
 }
 
@@ -77,7 +152,7 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
     }
 }
 
-export interface DocumentContentsViewProps extends FieldViewProps {
+interface DocumentContentsViewProps extends DocumentViewProps, FieldViewProps {
     layoutFieldKey: string;
 }
 @observer
@@ -118,24 +193,6 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
     }
 
     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
-            'hideResizeHandles',
-            'hideTitle',
-            'bringToFront',
-            'childContentPointerEvents',
-            'LayoutTemplateString',
-            'LayoutTemplate',
-            'showTags',
-            'layoutFieldKey',
-            'dontCenter',
-            'DataTransition',
-            'contextMenuItems',
-            // 'onClick', // don't need to omit this since it will be set
-            '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> = {
             ...this._props,
@@ -146,7 +203,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
         };
         return {
             props: {
-                ...OmitKeys(list, [...docOnlyProps], '').omit,
+                ...OmitKeys(list, DocOnlyProps, '').omit,
             } as BindingProps,
         };
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index eae3454ba..c3026d7be 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -43,10 +43,10 @@ import { StyleProp } from '../StyleProp';
 import { TagsView } from '../TagsView';
 import { ViewBoxInterface } from '../ViewBoxInterface';
 import { GroupActive } from './CollectionFreeFormDocumentView';
-import { DocumentContentsView } from './DocumentContentsView';
+import { DocumentContentsView, DocumentViewProps } from './DocumentContentsView';
 import { DocumentLinksButton } from './DocumentLinksButton';
 import './DocumentView.scss';
-import { FieldViewProps, FieldViewSharedProps } from './FieldView';
+import { FieldViewProps } from './FieldView';
 import { FocusViewOptions } from './FocusViewOptions';
 import { OpenWhere, OpenWhereMod } from './OpenWhere';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
@@ -54,39 +54,8 @@ import { PresEffect, PresEffectDirection } from './trails/PresEnums';
 import SpringAnimation from './trails/SlideEffect';
 import { SpringType, springMappings } from './trails/SpringUtils';
 
-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
-    hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
-    hideDocumentButtonBar?: boolean;
-    hideOpenButton?: boolean;
-    hideDeleteButton?: boolean;
-    hideLinkAnchors?: boolean;
-    hideLinkButton?: boolean;
-    hideCaptions?: boolean;
-    contentPointerEvents?: Property.PointerEvents | 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
-    dontCenter?: 'x' | 'y' | 'xy';
-    showTags?: boolean;
-    hideFilterStatus?: 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.
-    dragWhenActive?: boolean;
-    dontHideOnDrag?: boolean;
-    onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
-    DataTransition?: () => string | undefined;
-    NativeWidth?: () => number;
-    NativeHeight?: () => number;
-    contextMenuItems?: () => { script?: ScriptField; method?: () => void; filter?: ScriptField; label: string; icon: string }[];
-    dragConfig?: (data: DragManager.DocumentDragData) => void;
-    dragStarting?: () => void;
-    dragEnding?: () => void;
-
-    reactParent?: React.Component; // parent React component view (see CollectionFreeFormDocumentView)
-}
 @observer
-export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps & { showAIEditor: boolean }>() {
+export class DocumentViewInternal extends DocComponent<DocumentViewProps & FieldViewProps>() {
     // 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.
@@ -809,7 +778,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                         </div>
                     </>
                 )}
-                {this.widgetDecorations ?? null}
+                <div style={{ transformOrigin: 'top right', transform: `scale(${this.uiBtnScaling})`, position: 'absolute', top: 0, right: 0 }}>{this.widgetDecorations ?? null}</div>
             </>
         );
     }
@@ -1271,9 +1240,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     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
-    public get TagBtnHeight() {
-        return this._docViewInternal?.TagsBtnHeight;
-    }
+    public get TagBtnHeight()   { return this._docViewInternal?.TagsBtnHeight; } // prettier-ignore
+    public get UIBtnScaling()   { return this._docViewInternal?.uiBtnScaling; } // prettier-ignore
 
     get LayoutFieldKey() {
         return Doc.LayoutFieldKey(this.Document, this._props.LayoutTemplateString);
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 933868c46..74059bd12 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -48,8 +48,6 @@ export type StyleProviderFuncType = (
 export interface FieldViewSharedProps {
     Document: Doc;
     TemplateDataDocument?: Doc;
-    LayoutTemplateString?: string;
-    LayoutTemplate?: () => Opt<Doc>;
     renderDepth: number;
     scriptContext?: unknown; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
     screenXPadding?: () => number; // padding in screen space coordinates (used by text box to reflow around UI buttons in carouselView)
@@ -62,7 +60,8 @@ export interface FieldViewSharedProps {
     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
-    ignoreUsePath?: boolean; // ignore the usePath field for selecting the fieldKey (eg., on text docs)
+    suppressSetHeight?: boolean;
+    dontCenter?: 'x' | 'y' | 'xy' | undefined;
     LocalRotation?: () => number | undefined; // amount of rotation applied to freeformdocumentview containing document view
     containerViewPath?: () => DocumentView[];
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
@@ -86,7 +85,6 @@ export interface FieldViewSharedProps {
     onPointerUpScript?: () => ScriptField;
     onKey?: (e: KeyboardEvent, textBox: FormattedTextBox) => boolean | undefined;
     fitWidth?: (doc: Doc) => boolean | undefined;
-    dontCenter?: 'x' | 'y' | 'xy' | undefined;
     searchFilterDocs: () => Doc[];
     showTitle?: () => string;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
@@ -102,7 +100,6 @@ export interface FieldViewSharedProps {
     waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
     defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
     pointerEvents?: () => Opt<Property.PointerEvents>;
-    suppressSetHeight?: boolean;
 }
 
 /**
@@ -110,13 +107,13 @@ export interface FieldViewSharedProps {
  *  */
 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
     isHovering?: () => boolean;
+    fieldKey: string;
 
     // 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
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d89fe0bd4..030473be4 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -47,6 +47,7 @@ import './ImageBox.scss';
 import { OpenWhere } from './OpenWhere';
 import { RichTextField } from '../../../fields/RichTextField';
 
+const DefaultPath = '/assets/unknown-file-icon-hi.png';
 export class ImageEditorData {
     // eslint-disable-next-line no-use-before-define
     private static _instance: ImageEditorData;
@@ -426,7 +427,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         const lower = url.href.toLowerCase();
         if (url.protocol === 'data') return url.href;
         if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return ClientUtils.CorsProxy(url.href);
-        if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`;
+        if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith(DefaultPath)) return DefaultPath;
 
         const ext = extname(url.href);
         return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
@@ -440,7 +441,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @computed get nativeSize() {
         TraceMobx();
-        if (this.paths.length && this.paths[0].includes('icon-hi')) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0 };
+        if (this.paths.length && this.paths[0].includes(DefaultPath)) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0 };
         const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500));
         const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500));
         const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1);
@@ -527,7 +528,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @computed get paths() {
         const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey])); // retrieve the primary image URL that is being rendered from the data doc
         const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
-        const defaultUrl = new URL(ClientUtils.prepend('/assets/unknown-file-icon-hi.png'));
+        const defaultUrl = new URL(ClientUtils.prepend(DefaultPath));
         const altpaths =
             alts
                 ?.map(doc => (doc instanceof Doc ? (ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl) : defaultUrl))
@@ -703,21 +704,24 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
     screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
     marqueeDown = (e: React.PointerEvent) => {
-        if (!this.dataDoc[this.fieldKey]) {
-            this.chooseImage();
-        } else if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
-            setupMoveUpEvents(
-                this,
-                e,
-                action(moveEv => {
-                    MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this.marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
-                    return true;
-                }),
-                returnFalse,
-                () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
-                false
-            );
+        if (!e.altKey && e.button === 0) {
+            if (NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
+                setupMoveUpEvents(
+                    this,
+                    e,
+                    action(moveEv => {
+                        MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+                        this.marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
+                        return true;
+                    }),
+                    returnFalse,
+                    () => {
+                        if (!this.dataDoc[this.fieldKey]) this.chooseImage();
+                        else MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+                    },
+                    false
+                );
+            }
         }
     };
     @action
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index bb711a5fc..be897b3f3 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -22,6 +22,7 @@ import './KeyValueBox.scss';
 import { KeyValuePair } from './KeyValuePair';
 import { OpenWhere } from './OpenWhere';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { DocLayout } from '../../../fields/DocSymbols';
 
 export type KVPScript = {
     script: CompiledScript;
@@ -125,7 +126,7 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static SetField = undoable((doc: Doc, key: string, value: string, forceOnDelegateIn?: boolean, setResult?: (value: FieldResult) => void) => {
         const script = KeyValueBox.CompileKVPScript(value);
         if (!script) return false;
-        const ldoc = key.startsWith('_') ? Doc.Layout(doc) : doc;
+        const ldoc = key.startsWith('_') ? doc[DocLayout] : doc;
         const forceOnDelegate = forceOnDelegateIn || (ldoc !== doc && !value.startsWith('='));
         // an '=' value redirects a key targeting the render template to the root document.
         // also, if ldoc and doc are equal, allow redirecting to data document when not using an equal
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index e3c481a75..c9e0aea5a 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -16,6 +16,7 @@ import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider';
 import './KeyValueBox.scss';
 import './KeyValuePair.scss';
 import { OpenWhere } from './OpenWhere';
+import { DocLayout } from '../../../fields/DocSymbols';
 
 // Represents one row in a key value plane
 
@@ -60,7 +61,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
     };
 
     render() {
-        let doc = this._props.keyName.startsWith('_') ? Doc.Layout(this._props.doc) : this._props.doc;
+        let doc = this._props.keyName.startsWith('_') ? this._props.doc[DocLayout] : this._props.doc;
         const layoutField = doc !== this._props.doc;
         const key = layoutField ? this._props.keyName.replace(/^_/, '') : this._props.keyName;
         let protoCount = 0;
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index f2160feb7..eaea272dc 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -23,9 +23,9 @@
         // glr: This should really be the same component as text and PDFs
         .pdfBox-sidebarBtn {
             background: global.$black;
-            height: 25px;
-            width: 25px;
-            right: 5px;
+            height: 20px;
+            width: 20px;
+            right: 0px;
             color: global.$white;
             display: flex;
             position: absolute;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 78ddafa88..36d260fb9 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -499,8 +499,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 title="Toggle Sidebar"
                 style={{
                     display: !this._props.isContentActive() ? 'none' : undefined,
-                    top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
+                    top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 0,
                     backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
+                    transformOrigin: 'top right',
+                    transform: `scale(${this._props.DocumentView?.().UIBtnScaling || 1})`,
                 }}
                 onPointerDown={e => this.sidebarBtnDown(e, true)}>
                 <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index f9de4ab5a..547a2efa8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -141,8 +141,8 @@ audiotag:hover {
     position: absolute;
     top: 0;
     right: 0;
-    width: 17px;
-    height: 17px;
+    width: 20px;
+    height: 20px;
     font-size: 11px;
     border-radius: 3px;
     color: white;
@@ -153,6 +153,7 @@ audiotag:hover {
     align-items: center;
     cursor: grabbing;
     box-shadow: global.$standard-box-shadow;
+    transform-origin: top right;
     // transition: 0.2s;
     opacity: 0.3;
     &:hover {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 416cd577e..51b9a4333 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1941,6 +1941,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     backgroundColor,
                     color,
                     opacity: annotated ? 1 : undefined,
+                    transform: `scale(${this._props.DocumentView?.().UIBtnScaling ?? 1})`,
                 }}>
                 <FontAwesomeIcon icon="comment-alt" />
             </div>
@@ -2050,7 +2051,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         return this._fieldKey;
     }
     @computed get _fieldKey() {
-        const usePath = this._props.ignoreUsePath ? '' : StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]);
+        const usePath = StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]);
         return this._props.fieldKey + (usePath && (!usePath.includes(':hover') || this._props.isHovering?.() || this._props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
     }
     onPassiveWheel = (e: WheelEvent) => {
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 317719032..7e470a642 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -22,9 +22,7 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return (
             <div style={{ backgroundColor: 'pink' }}>
                 <DocumentView
-                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props} //
-                    LayoutTemplateString={undefined}
                     Document={this.Document}
                     isContentActive={returnFalse}
                     addDocument={returnFalse}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 73c2f5eb8..ed2f661e6 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -30,6 +30,7 @@ import { AnchorMenu } from './AnchorMenu';
 import { Annotation } from './Annotation';
 import { GPTPopup } from './GPTPopup/GPTPopup';
 import './PDFViewer.scss';
+import { DocumentViewProps } from '../nodes/DocumentContentsView';
 if (window?.Worker) GlobalWorkerOptions.workerSrc = 'files/pdf.worker.min.mjs';
 export * from 'pdfjs-dist/build/pdf.mjs';
 
@@ -521,7 +522,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
     transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
     opaqueFilter = () => [...this._props.childFilters(), ClientUtils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [ClientUtils.OpaqueBackgroundFilter])];
-    childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
+    childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps & DocumentViewProps>, property: string) => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
             const isInk = doc.layout_isSvg && !props?.LayoutTemplateString;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 65719374f..734af4ccd 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -64,7 +64,7 @@ export namespace Field {
                       .trim()
                       .replace(/^new List\((.*)\)$/, '$1');
         };
-        const notOnTemplate = !key.startsWith('_') || Doc.Layout(doc) === doc;
+        const notOnTemplate = !key.startsWith('_') || doc[DocLayout] === doc;
         const isOnDelegate = notOnTemplate && !Doc.IsDataProto(doc) && ((key.startsWith('_') && !Field.IsField(cfield)) || Object.keys(doc).includes(key.replace(/^_/, '')));
         return (isOnDelegate ? '=' : '') + (!Field.IsField(cfield) ? '' : valFunc(cfield));
     }
@@ -407,9 +407,9 @@ export class Doc extends RefField {
     }
     @computed get __DATA__(): Doc {
         const self = this[SelfProxy];
-        return self.rootDocument && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).rootDocument, Doc, null) || self);
+        return self.rootDocument && !self.isTemplateForField ? self : Doc.GetProto(Cast(self[DocLayout].rootDocument, Doc, null) || self);
     }
-    @computed get __LAYOUT__(): Doc | undefined {
+    @computed get __LAYOUT__(): Doc {
         const self = this[SelfProxy];
         const templateLayoutDoc = Cast(Doc.LayoutField(self), Doc, null);
         if (templateLayoutDoc) {
@@ -422,7 +422,7 @@ export class Doc extends RefField {
             }
             return Cast(self['layout_' + templateLayoutDoc.title + '(' + renderFieldKey + ')'], Doc, null) || templateLayoutDoc;
         }
-        return undefined;
+        return self;
     }
 
     public async [HandleUpdate](diff: { $set: { [key: string]: FieldType } } | { $unset?: unknown }) {
@@ -687,15 +687,11 @@ export namespace Doc {
     }
 
     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);
-        if (layout instanceof Doc && layout !== embedding && layout === Doc.Layout(embedding)) {
-            Doc.SetLayout(embedding, Doc.MakeEmbedding(layout));
-        }
+        const embedding = (!Doc.IsDataProto(doc) && doc.proto) || doc.type === DocumentType.CONFIG ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
         embedding.createdFrom = doc;
-        embedding.proto_embeddingId = doc.$proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
-        !Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`));
         embedding.author = ClientUtils.CurrentUserEmail();
+        embedding.proto_embeddingId = doc.$proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
+        embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`);
 
         return embedding;
     }
@@ -768,9 +764,8 @@ export namespace Doc {
                         // prune doc and do nothing
                     } else if (
                         !Doc.IsSystem(docAtKey) &&
-                        (key.includes('layout[') ||
-                            key.startsWith('layout') || //
-                            ['embedContainer', 'annotationOn', 'proto'].includes(key) ||
+                        (key.startsWith('layout') ||
+                            ['embedContainer', 'annotationOn', 'proto'].includes(key) || //
                             (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === ClientUtils.CurrentUserEmail()))
                     ) {
                         assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
@@ -1117,10 +1112,10 @@ export namespace Doc {
             Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue);
         }
         // get the layout string that the template uses to specify its layout
-        const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField)));
+        const templateFieldLayoutString = StrCast(Doc.LayoutField(templateField[DocLayout]));
 
         // change it to render the target metadata field instead of what it was rendering before and assign it to the template field layout document.
-        Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`);
+        templateField[DocLayout].layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`);
 
         return true;
     }
@@ -1157,16 +1152,13 @@ export namespace Doc {
     // a layout field or 'layout' is given.
     export function Layout(doc: Doc, template?: Doc): Doc {
         const expandedTemplate = template && Cast(doc['layout_' + template.title + '(' + StrCast(template.isTemplateForField, 'data') + ')'], Doc, null);
-        return expandedTemplate || doc[DocLayout] || doc;
-    }
-    export function SetLayout(doc: Doc, layout: Doc | string) {
-        doc[StrCast(doc.layout_fieldKey, 'layout')] = layout;
+        return expandedTemplate || doc[DocLayout];
     }
     export function LayoutField(doc: Doc) {
         return doc[StrCast(doc.layout_fieldKey, 'layout')];
     }
     export function LayoutFieldKey(doc: Doc, templateLayoutString?: string): string {
-        const match = StrCast(templateLayoutString || Doc.Layout(doc).layout).match(/fieldKey={'([^']+)'}/);
+        const match = StrCast(templateLayoutString || doc[DocLayout].layout).match(/fieldKey={'([^']+)'}/);
         return match?.[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) {
@@ -1325,17 +1317,7 @@ export namespace Doc {
     }
 
     export function getDocTemplate(doc?: Doc) {
-        return !doc
-            ? undefined
-            : doc.isTemplateDoc
-              ? doc
-              : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc
-                ? doc.dragFactory
-                : Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc
-                  ? Cast(Doc.Layout(doc), Doc, null).rootDocument
-                      ? Doc.Layout(doc).proto
-                      : Doc.Layout(doc)
-                  : undefined;
+        return !doc ? undefined : doc.isTemplateDoc ? doc : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc ? doc.dragFactory : doc[DocLayout].isTemplateDoc ? (doc[DocLayout].rootDocument ? doc[DocLayout].proto : doc[DocLayout]) : undefined;
     }
 
     export function toggleLockedPosition(doc: Doc) {
@@ -1737,7 +1719,7 @@ ScriptingGlobals.add(function idToDoc(id: string): Doc {
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function renameEmbedding(doc: Doc) {
-    return StrCast(doc.$title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
+    return StrCast(doc.title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function getProto(doc: Doc) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index abbe543e8..205c500a5 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -303,30 +303,23 @@ export function SetPropSetterCb(prop: string, propSetter: ((target: Doc, value:
 //
 // target should be either a Doc or ListImpl.  receiver should be a Proxy<Doc> Or List.
 //
-export function setter(target: ListImpl<FieldType> | Doc, inProp: string | symbol | number, value: unknown, receiver: Doc | ListImpl<FieldType>): boolean {
-    if (!inProp) {
+export function setter(target: ListImpl<FieldType> | Doc, prop: string | symbol | number, value: unknown, receiver: Doc | ListImpl<FieldType>): boolean {
+    if (!prop) {
         console.log('WARNING: trying to set an empty property.  This should be fixed. ');
         return false;
     }
-    let prop = inProp;
-    const effectiveAcl = inProp === 'constructor' || typeof inProp === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
+    const effectiveAcl = prop === 'constructor' || typeof prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
     if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin) return true;
     // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
     if (typeof prop === 'string' && prop.startsWith('acl_') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value as SharingPermissions))) return true;
 
-    if (typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('$')) {
-        prop = prop.substring(1);
-        if (target.__DATA__ instanceof Doc) {
-            target.__DATA__[prop] = value as FieldResult;
-            return true;
-        }
+    if (target instanceof Doc && typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('$')) {
+        target.__DATA__[prop.substring(1)] = value as FieldResult;
+        return true;
     }
-    if (typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('_')) {
-        if (!prop.startsWith('__')) prop = prop.substring(1);
-        if (target.__LAYOUT__ instanceof Doc) {
-            target.__LAYOUT__[prop] = value as FieldResult;
-            return true;
-        }
+    if (target instanceof Doc && typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('_') && !prop.startsWith('__')) {
+        target.__LAYOUT__[prop.substring(1)] = value as FieldResult;
+        return true;
     }
     if (target.__fieldTuples[prop] instanceof ComputedField) {
         if (target.__fieldTuples[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) {
-- 
cgit v1.2.3-70-g09d2


From f061db01d1936bf3e776b140786ba3bf1c6b71f3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 31 Mar 2025 14:29:57 -0400
Subject: moved audio annotation indicator/playback button to TagsView. 
 cleaned up tagsview colors and unobscured by bottom resizer

---
 .../components/src/components/Button/Button.scss   |  5 +-
 .../src/components/IconButton/IconButton.scss      |  5 +-
 src/client/util/DictationManager.ts                | 39 ++++++------
 src/client/views/DocViewUtils.ts                   |  3 -
 src/client/views/DocumentButtonBar.tsx             |  2 +-
 src/client/views/DocumentDecorations.tsx           |  4 +-
 src/client/views/StyleProvider.scss                |  5 --
 src/client/views/StyleProvider.tsx                 | 18 +-----
 src/client/views/TagsView.scss                     |  6 +-
 src/client/views/TagsView.tsx                      | 21 ++++---
 src/client/views/nodes/DocumentView.tsx            | 12 ++--
 src/client/views/nodes/IconTagBox.scss             |  1 -
 src/client/views/nodes/IconTagBox.tsx              | 69 +++++++++++++++-------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 14 files changed, 100 insertions(+), 94 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/packages/components/src/components/Button/Button.scss b/packages/components/src/components/Button/Button.scss
index b2fb48344..c86db9fad 100644
--- a/packages/components/src/components/Button/Button.scss
+++ b/packages/components/src/components/Button/Button.scss
@@ -63,13 +63,16 @@
             filter: opacity(0);
 
             &.active {
-                filter: opacity(0.5);
+                filter: opacity(0.3);
             }
         }
 
         &:hover {
             .background {
                 filter: opacity(0.3);
+                &.active {
+                    filter: opacity(0);
+                }
             }
         }
     }
diff --git a/packages/components/src/components/IconButton/IconButton.scss b/packages/components/src/components/IconButton/IconButton.scss
index 3f0dd26ea..f95d94ae4 100644
--- a/packages/components/src/components/IconButton/IconButton.scss
+++ b/packages/components/src/components/IconButton/IconButton.scss
@@ -54,13 +54,16 @@
             filter: opacity(0);
 
             &.active {
-                filter: opacity(0.5);
+                filter: opacity(0.3);
             }
         }
 
         &:hover {
             .background {
                 filter: opacity(0.3);
+                &.active {
+                    filter: opacity(0);
+                }
             }
         }
     }
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 44fbda319..2eef3da0e 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -356,23 +356,16 @@ export namespace DictationManager {
                 action: (target: DocumentView, matches: RegExpExecArray) => {
                     const count = interpretNumber(matches[1]);
                     const what = matches[2];
-                    const dataDoc = Doc.GetProto(target.Document);
-                    const fieldKey = 'data';
-                    if (isNaN(count)) {
-                        return;
-                    }
-                    for (let i = 0; i < count; i++) {
-                        let created: Doc | undefined;
-                        switch (what) {
-                            case 'image':
-                                created = Docs.Create.ImageDocument('https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg');
-                                break;
-                            case 'nested collection':
-                                created = Docs.Create.FreeformDocument([], {});
-                                break;
-                            default:
+                    if (!isNaN(count)) {
+                        for (let i = 0; i < count; i++) {
+                            const created = (() => {
+                                switch (what) {
+                                    case 'image':            return Docs.Create.ImageDocument('https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg');
+                                    case 'nested collection':return Docs.Create.FreeformDocument([], {});
+                                } // prettier-ignore
+                            })();
+                            created && Doc.AddDocToList(target.dataDoc, Doc.LayoutFieldKey(target.Document), created);
                         }
-                        created && Doc.AddDocToList(dataDoc, fieldKey, created);
                     }
                 },
                 restrictTo: [DocumentType.COL],
@@ -388,13 +381,15 @@ export namespace DictationManager {
             },
         ];
     }
-    export function recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
+    export function recordAudioAnnotation(doc: Doc, fieldIn: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
+        const field = '$' + fieldIn + '_audioAnnotations';
         let gumStream: MediaStream | undefined;
         let recorder: MediaRecorder | undefined;
         navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
-            let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
+            let audioTextAnnos = Cast(doc[field + '_text'], listSpec('string'), null);
             if (audioTextAnnos) audioTextAnnos.push('');
-            else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
+            else audioTextAnnos = doc[field + '_text'] = new List<string>(['']);
+            doc._layout_showTags = true;
             DictationManager.Controls.listen({
                 interimHandler: value => { audioTextAnnos[audioTextAnnos.length - 1] = value; }, // prettier-ignore
                 continuous: { indefinite: false },
@@ -415,16 +410,16 @@ export namespace DictationManager {
                 const [{ result }] = await Networking.UploadFilesToServer({ file: file as Blob & { name: string; lastModified: number; webkitRelativePath: string } });
                 if (!(result instanceof Error)) {
                     const audioField = new AudioField(result.accessPaths.agnostic.client);
-                    const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
+                    const audioAnnos = Cast(doc[field], listSpec(AudioField), null);
                     if (audioAnnos) audioAnnos.push(audioField);
-                    else dataDoc[field + '_audioAnnotations'] = new List([audioField]);
+                    else doc[field] = new List([audioField]);
                 }
             };
             recorder.start();
             const stopFunc = () => {
                 recorder?.stop();
                 DictationManager.Controls.stop(/* false */);
-                dataDoc.audioAnnoState = AudioAnnoState.stopped;
+                doc._audioAnnoState = AudioAnnoState.stopped;
                 gumStream?.getAudioTracks()[0].stop();
             };
             if (onRecording) onRecording(stopFunc);
diff --git a/src/client/views/DocViewUtils.ts b/src/client/views/DocViewUtils.ts
index 1f5f29c7e..5710187b5 100644
--- a/src/client/views/DocViewUtils.ts
+++ b/src/client/views/DocViewUtils.ts
@@ -1,6 +1,3 @@
-/* eslint-disable prefer-destructuring */
-/* eslint-disable default-param-last */
-/* eslint-disable no-use-before-define */
 import { runInAction } from 'mobx';
 import { Doc, SetActiveAudioLinker } from '../../fields/Doc';
 import { DocUtils } from '../documents/DocUtils';
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index b17dbc93d..9a57a5e6c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -358,7 +358,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                             view =>
                                 view &&
                                 DictationManager.recordAudioAnnotation(
-                                    view.dataDoc,
+                                    view.Document,
                                     view.LayoutFieldKey,
                                     stopFunc => {
                                         this._stopFunc = stopFunc;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 7842c233a..875eb45e0 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -834,7 +834,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                     <div key="c" className="documentDecorations-centerCont" />
                                     <div key="r" className="documentDecorations-rightResizer" onPointerDown={this.onPointerDown} />
                                     <div key="bl" className="documentDecorations-bottomLeftResizer" onPointerDown={this.onPointerDown} />
-                                    <div key="b" className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} />
+                                    {(seldocview.TagPanelHeight ?? 0) > 30 ? null : <div key="b" className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} />}
                                     <div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} />
                                 </>
                             )}
@@ -866,7 +866,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                     top: 30, // offset by height of documentButtonBar so that items can be clicked without overlap interference
                                     transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                 }}>
-                                {DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} /> : null}
+                                {DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} background={SnappingManager.userBackgroundColor ?? ''} /> : null}
                             </div>
                         </div>
 
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index 501b9a292..99796f1fb 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -1,5 +1,4 @@
 .styleProvider-filter,
-.styleProvider-audio,
 .styleProvider-paint,
 .styleProvider-paint-selected,
 .styleProvider-lock {
@@ -32,9 +31,6 @@
         margin: auto !important;
     }
 }
-.styleProvider-audio {
-    right: 40;
-}
 .styleProvider-paint-selected,
 .styleProvider-paint {
     top: 15;
@@ -43,7 +39,6 @@
     right: -40;
 }
 .styleProvider-lock:hover,
-.styleProvider-audio:hover,
 .styleProvider-filter:hover {
     opacity: 1;
 }
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index a59d39cfb..432a05146 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -1,7 +1,6 @@
 import { Dropdown, DropdownType, IconButton, IListItemProps, Shadows, Size, Type } from '@dash/components';
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@mui/material';
 import { action, untracked } from 'mobx';
 import { extname } from 'path';
 import * as React from 'react';
@@ -9,11 +8,11 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
 import { FaFilter } from 'react-icons/fa';
 import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils';
 import { Doc, Opt, StrListCast } from '../../fields/Doc';
+import { DocLayout } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { InkInkTool } from '../../fields/InkField';
 import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
-import { AudioAnnoState } from '../../server/SharedMediaTypes';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { IsFollowLinkScript } from '../documents/DocUtils';
 import { SnappingManager } from '../util/SnappingManager';
@@ -26,7 +25,6 @@ import { FieldViewProps } from './nodes/FieldView';
 import { StyleProp } from './StyleProp';
 import './StyleProvider.scss';
 import { styleProviderQuiz } from './StyleProviderQuiz';
-import { DocLayout } from '../../fields/DocSymbols';
 
 function toggleLockedPosition(doc: Doc) {
     UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
@@ -387,26 +385,12 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                     </div>
                 );
             };
-            const audio = () => {
-                const audioAnnoState = (audioDoc: Doc) => StrCast(audioDoc.audioAnnoState, AudioAnnoState.stopped);
-                const audioAnnosCount = (audioDoc: Doc) => StrListCast(audioDoc[fieldKey + 'audioAnnotations']).length;
-                if (!doc || renderDepth === -1 || !audioAnnosCount(doc)) return null;
-                const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: 'blue' };
-                return (
-                    <Tooltip title={<div>{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}</div>}>
-                        <div className="styleProvider-audio" onPointerDown={() => DocumentView.getFirstDocumentView(doc)?.playAnnotation()}>
-                            <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon='file-audio' size="sm" />
-                        </div>
-                    </Tooltip>
-                );
-            };
         
             return (
                 <>
                     {paint()}
                     {lock()}
                     {filter()}
-                    {audio()}
                 </>
             );
         }
diff --git a/src/client/views/TagsView.scss b/src/client/views/TagsView.scss
index b21d303fb..231d5e11a 100644
--- a/src/client/views/TagsView.scss
+++ b/src/client/views/TagsView.scss
@@ -12,7 +12,7 @@
         .tagsView-list {
             display: flex;
             flex-wrap: wrap;
-            height: 1;
+            height: 100%;
             .iconButton-container {
                 min-height: unset !important;
             }
@@ -57,10 +57,6 @@
     align-items: center;
 }
 
-.tagsView-editing-box {
-    margin-top: 20px;
-}
-
 .tagsView-input-box {
     margin: auto;
     align-self: center;
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index f1bcaac2c..39c952483 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, Colors, IconButton } from '@dash/components';
+import { Button, Colors, IconButton, Type } from '@dash/components';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import React from 'react';
@@ -20,6 +20,7 @@ import { DocumentView } from './nodes/DocumentView';
 import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
 import { IconTagBox } from './nodes/IconTagBox';
 import { Id } from '../../fields/FieldSymbols';
+import { StyleProp } from './StyleProp';
 
 /**
  * The TagsView is a metadata input/display panel shown at the bottom of a DocumentView in a freeform collection.
@@ -254,6 +255,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
 
 interface TagViewProps {
     Views: DocumentView[];
+    background: string;
 }
 
 /**
@@ -269,7 +271,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
 
     @observable _panelHeightDirty = 0;
     @observable _currentInput = '';
-    @observable _isEditing = !StrListCast(this.View.dataDoc.tags).length;
+    @observable _isEditing: boolean | undefined = undefined;
     _heightDisposer: IReactionDisposer | undefined;
     _lastXf = this.View.screenToContentsTransform();
 
@@ -295,7 +297,9 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
     }
 
     @computed get isEditing() {
-        return this._isEditing && (this._props.Views.length > 1 || (DocumentView.Selected().length === 1 && DocumentView.Selected().includes(this.View)));
+        const selected = DocumentView.Selected().length === 1 && DocumentView.Selected().includes(this.View);
+        if (this._isEditing === undefined) return selected && !StrListCast(this.View.dataDoc.tags).length && !StrListCast(this.View.dataDoc[Doc.LayoutFieldKey(this.View.Document) + '_audioAnnotations_text']).length;
+        return this._isEditing && (this._props.Views.length > 1 || selected);
     }
 
     /**
@@ -349,7 +353,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                 ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
                 style={{
                     display: SnappingManager.IsResizing === this.View.Document[Id] ? 'none' : undefined,
-                    backgroundColor: this.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT,
+                    backgroundColor: this.isEditing ? this._props.background : Colors.TRANSPARENT,
                     borderColor: this.isEditing ? Colors.BLACK : Colors.TRANSPARENT,
                     height: !this._props.Views.lastElement()?.isSelected() ? 0 : undefined,
                 }}>
@@ -361,14 +365,17 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                                 tooltip="Close Menu"
                                 onPointerDown={e =>
                                     setupMoveUpEvents(this, e, returnFalse, emptyFunction, upEv => {
-                                        this.setToEditing(!this._isEditing);
+                                        this.setToEditing(!this.isEditing);
                                         upEv.stopPropagation();
                                     })
                                 }
-                                icon={<FontAwesomeIcon icon={this._isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />}
+                                type={Type.TERT}
+                                background="transparent"
+                                color={this.View._props.styleProvider?.(this.View.Document, this.View.ComponentView?._props, StyleProp.FontColor) as string}
+                                icon={<FontAwesomeIcon icon={this.isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />}
                             />
                         )}
-                        <IconTagBox Views={this._props.Views} IsEditing={this._isEditing} />
+                        <IconTagBox Views={this._props.Views} IsEditing={this.isEditing} />
                         {Array.from(tagsList)
                             .filter(tag => (tag.startsWith('#') || tag.startsWith('@')) && !Doc.MyFilterHotKeys.some(key => key.toolType === tag))
                             .map(tag => (
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c3026d7be..070a13103 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -750,7 +750,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                             transform: `scale(${this.uiBtnScaling})`,
                             bottom: Number.isNaN(this.maxWidgetSize) ? undefined : this.maxWidgetSize,
                         }}>
-                        {this._props.DocumentView?.() && !this._props.docViewPath().slice(-2)[0].ComponentView?.isUnstyledView?.() ? <TagsView Views={[this._props.DocumentView?.()]} /> : null}
+                        {this._props.DocumentView?.() && !this._props.docViewPath().slice(-2)[0].ComponentView?.isUnstyledView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
                     </div>
                 ) : (
                     <>
@@ -774,7 +774,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                             ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
                             <div className="documentView-editorView-resizer" />
                             {this._componentView?.componentAIView?.() ?? null}
-                            {this._props.DocumentView?.() ? <TagsView Views={[this._props.DocumentView?.()]} /> : null}
+                            {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
                         </div>
                     </>
                 )}
@@ -1319,7 +1319,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     }
 
     public playAnnotation = () => {
-        const audioAnnoState = this.dataDoc.audioAnnoState ?? AudioAnnoState.stopped;
+        const audioAnnoState = this.Document._audioAnnoState ?? AudioAnnoState.stopped;
         const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
         const anno = audioAnnos?.lastElement();
         if (anno instanceof AudioField) {
@@ -1331,13 +1331,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                         autoplay: true,
                         loop: false,
                         volume: 0.5,
-                        onend: action(() => { this.dataDoc.audioAnnoState = AudioAnnoState.stopped; }), // prettier-ignore
+                        onend: action(() => { this.Document._audioAnnoState = AudioAnnoState.stopped; }), // prettier-ignore
                     });
-                    this.dataDoc.audioAnnoState = AudioAnnoState.playing;
+                    this.Document._audioAnnoState = AudioAnnoState.playing;
                     break;
                 case AudioAnnoState.playing:
                     (this.dataDoc[AudioPlay] as Howl)?.stop();
-                    this.dataDoc.audioAnnoState = AudioAnnoState.stopped;
+                    this.Document._audioAnnoState = AudioAnnoState.stopped;
                     break;
                 default:
             }
diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss
index 202b0c701..d6cf95958 100644
--- a/src/client/views/nodes/IconTagBox.scss
+++ b/src/client/views/nodes/IconTagBox.scss
@@ -4,7 +4,6 @@
     display: flex;
     position: relative;
     pointer-events: none;
-    background-color: rgb(218, 218, 218);
     border-radius: 50px;
     align-items: center;
     gap: 5px;
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index ddabd61e1..e3924eca7 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -1,22 +1,23 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import React from 'react';
-import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
-import { emptyFunction } from '../../../Utils';
-import { Doc } from '../../../fields/Doc';
+import { Doc, StrListCast } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
+import { AudioAnnoState } from '../../../server/SharedMediaTypes';
 import { undoable } from '../../util/UndoManager';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { TagItem } from '../TagsView';
 import { DocumentView } from './DocumentView';
 import './IconTagBox.scss';
+import { Size, Toggle, ToggleType, Type } from '@dash/components';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { StyleProp } from '../StyleProp';
 
 export interface IconTagProps {
     Views: DocumentView[];
-    IsEditing: boolean;
+    IsEditing: boolean | undefined;
 }
 
 /**
@@ -52,13 +53,46 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
      * @param key metadata icon button
      * @returns an icon for the metdata button
      */
-    getButtonIcon = (doc: Doc, key: Doc): JSX.Element => {
+    getButtonIcon = (dv: DocumentView, key: Doc): JSX.Element => {
         const icon = StrCast(key.icon) as IconProp;
         const tag = StrCast(key.toolType);
-        const isActive = TagItem.docHasTag(doc, tag);
-        const color = isActive ? '#4476f7' : '#323232'; // TODO should use theme colors
+        const color = dv._props.styleProvider?.(dv.layoutDoc, dv.ComponentView?._props, StyleProp.FontColor) as string;
+        return (
+            <Toggle
+                tooltip={`Click to add/remove the tag ${tag}`}
+                toggleStatus={TagItem.docHasTag(dv.Document, tag)}
+                toggleType={ToggleType.BUTTON}
+                icon={<FontAwesomeIcon className={`fontIconBox-icon-${ToggleType.BUTTON}`} icon={icon} color={color} />}
+                size={Size.XSMALL}
+                type={Type.PRIM}
+                onClick={() => this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag))}
+                color={color}
+            />
+        );
+    };
 
-        return <FontAwesomeIcon icon={icon} style={{ color, height: '20px', width: '20px' }} />;
+    /**
+     * Displays a button to play audio annotations on the document.
+     * NOTE: This should be generalized  --  audio should
+     * @returns
+     */
+    renderAudioButtons = (dv: DocumentView, anno: string) => {
+        const fcolor = dv._props.styleProvider?.(dv.layoutDoc, dv.ComponentView?._props, StyleProp.FontColor) as string;
+        const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: fcolor ?? 'blue', recording: 'red' };
+        const audioAnnoState = (audioDoc: Doc) => StrCast(audioDoc.audioAnnoState, AudioAnnoState.stopped);
+        const color = audioIconColors[audioAnnoState(this.View.Document)];
+        return (
+            <Toggle
+                tooltip={`click to play:${anno}`}
+                toggleStatus={true}
+                toggleType={ToggleType.BUTTON}
+                icon={<FontAwesomeIcon className={`fontIconBox-icon-${ToggleType.BUTTON}`} icon="file-audio" color={color} />}
+                size={Size.XSMALL}
+                type={Type.PRIM}
+                onClick={() => this.View?.playAnnotation()}
+                color={color}
+            />
+        );
     };
 
     /**
@@ -69,22 +103,15 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
             .map(key => ({ key, tag: StrCast(key.toolType) }))
             .filter(({ tag }) => this._props.IsEditing || TagItem.docHasTag(this.View.Document, tag) || (DocumentView.Selected().length === 1 && this.View.IsSelected))
             .map(({ key, tag }) => (
-                <Tooltip key={tag} title={<div className="dash-tooltip">Click to add/remove this card from the {tag} group</div>}>
-                    <button
-                        type="button"
-                        onPointerDown={e =>
-                            setupMoveUpEvents(this, e, returnFalse, emptyFunction, clickEv => {
-                                this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag));
-                                clickEv.stopPropagation();
-                            })
-                        }>
-                        {this.getButtonIcon(this.View.Document, key)}
-                    </button>
+                <Tooltip key={tag} title={<div className="dash-tooltip">Click to add/remove the {tag} tag</div>}>
+                    {this.getButtonIcon(this.View, key)}
                 </Tooltip>
             )); // prettier-ignore
 
-        return !buttons.length ? null : (
+        const audioannos = StrListCast(this.View.Document[Doc.LayoutFieldKey(this.View.Document) + '_audioAnnotations_text']);
+        return !buttons.length && !audioannos.length ? null : (
             <div className="card-button-container" style={{ fontSize: '50px' }}>
+                {audioannos.length ? this.renderAudioButtons(this.View, audioannos.lastElement()) : null}
                 {buttons}
             </div>
         );
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 51b9a4333..6955e5401 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -17,7 +17,7 @@ import { BsMarkdownFill } from 'react-icons/bs';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
 import { DateField } from '../../../../fields/DateField';
 import { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
 import { Id, ToString } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -246,7 +246,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     anchor.followLinkAudio = true;
                     let stopFunc: () => void = emptyFunction;
                     target.$mediaState = mediaState.Recording;
-                    DictationManager.recordAudioAnnotation(target[DocData], Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
+                    DictationManager.recordAudioAnnotation(target, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
 
                     const reactionDisposer = reaction(
                         () => target.mediaState,
-- 
cgit v1.2.3-70-g09d2


From ede7aaa19d903a7e55fc60d9bc213988ea602c97 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 3 Apr 2025 14:15:15 -0400
Subject: fixed image box button sizing. made drawing ai regen a little faster
 by not creating icons when there are no annotqations.

---
 src/client/apis/gpt/GPT.ts                        |  5 +-
 src/client/views/nodes/DocumentView.tsx           |  2 +-
 src/client/views/nodes/ImageBox.scss              |  7 +--
 src/client/views/nodes/ImageBox.tsx               | 23 ++++---
 src/client/views/smartdraw/DrawingFillHandler.tsx | 75 +++++++++++------------
 src/server/ApiManagers/FireflyManager.ts          |  1 -
 src/server/ApiManagers/UploadManager.ts           |  7 +--
 src/server/DashUploadUtils.ts                     | 14 ++---
 8 files changed, 62 insertions(+), 72 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 29b6ab989..140aebfe0 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -1,5 +1,6 @@
 import { ChatCompletionMessageParam, Image } from 'openai/resources';
 import { openai } from './setup';
+import { imageUrlToBase64 } from '../../../ClientUtils';
 
 export enum GPTDocCommand {
     AssignTags = 1,
@@ -310,7 +311,9 @@ const gptHandwriting = async (src: string): Promise<string> => {
     }
 };
 
-const gptDescribeImage = async (image: string): Promise<string> => {
+const gptDescribeImage = async (userPrompt: string, url: string): Promise<string> => {
+    if (userPrompt) return userPrompt;
+    const image = imageUrlToBase64(url);
     try {
         const response = await openai.chat.completions.create({
             model: 'gpt-4o',
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 070a13103..37f888ddd 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -702,7 +702,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
      */
     @computed get viewScaling() { return 1 / this.currentScale; } // prettier-ignore
     /**
-     * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size.
+     * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its nominal pixel size.
      */
     @computed get maxWidgetSize() { return Math.min(this.TagsBtnHeight * this.viewScaling, 0.25 * Math.min(NumCast(this.Document._width), NumCast(this.Document._height))); } // prettier-ignore
     /**
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 36abf707c..4a6e8eb49 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -142,11 +142,8 @@
     }
 }
 .imageBox-regenerateDropTarget {
-    right: 30;
-    border-radius: 50%;
-    svg {
-        border-radius: 50%;
-    }
+    right: 35;
+    transform-origin: 70px 35px;
 }
 
 .imageBox-fader img {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 75c298dc6..2b9a78596 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -45,7 +45,6 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { FocusViewOptions } from './FocusViewOptions';
 import './ImageBox.scss';
 import { OpenWhere } from './OpenWhere';
-import { RichTextField } from '../../../fields/RichTextField';
 import { ComputedField } from '../../../fields/ScriptField';
 
 const DefaultPath = '/assets/unknown-file-icon-hi.png';
@@ -218,9 +217,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     this._regenerateLoading = true;
                     const drag = de.complete.docDragData.draggedDocuments.lastElement();
                     const dragField = drag[Doc.LayoutFieldKey(drag)];
+                    const descText = RTFCast(dragField)?.Text || StrCast(dragField) || RTFCast(drag.text)?.Text || StrCast(drag.text) || StrCast(this.Document.title);
                     const oldPrompt = StrCast(this.Document.ai_firefly_prompt, StrCast(this.Document.title));
                     const newPrompt = (text: string) => (oldPrompt ? `${oldPrompt} ~~~ ${text}` : text);
-                    DrawingFillHandler.drawingToImage(this.Document, 90, newPrompt(dragField instanceof RichTextField ? dragField.Text : ''), drag)?.then(action(() => (this._regenerateLoading = false)));
+                    DrawingFillHandler.drawingToImage(this.Document, 90, newPrompt(descText), drag)?.then(action(() => (this._regenerateLoading = false)));
                     added = false;
                 } else if (de.altKey || !this.dataDoc[this.fieldKey]) {
                     const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
@@ -401,13 +401,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     // updateIcon = () => new Promise<void>(res => res());
-    updateIcon = (/* usePanelDimensions?: boolean */) => {
-        const contentDiv = this._mainCont;
-        return !contentDiv
+    updateIcon = (/* usePanelDimensions?: boolean */) =>
+        !this._mainCont || !DocListCast(this.dataDoc[this.annotationKey]).length
             ? new Promise<void>(res => res())
             : UpdateIcon(
                   this.layoutDoc[Id] + '_icon_' + new Date().getTime(),
-                  contentDiv,
+                  this._mainCont,
                   this._props.PanelWidth(), // usePanelDimensions ? this._props.PanelWidth() : NumCast(this.layoutDoc._width),
                   this._props.PanelHeight(), // usePanelDimensions ? this._props.PanelHeight() : NumCast(this.layoutDoc._height),
                   this._props.PanelWidth(),
@@ -422,7 +421,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                       this.dataDoc.icon_nativeHeight = nativeHeight;
                   }
               );
-    };
 
     choosePath = (url: URL) => {
         if (!url?.href) return '';
@@ -453,15 +451,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     /**
      * How much the content of the view is being scaled based on its nesting and its fit-to-width settings
      */
-    @computed get viewScaling() { return this.ScreenToLocalBoxXf().Scale * ( this._props.NativeDimScaling?.() || 1); } // prettier-ignore
+    @computed get viewScaling() { return this.ScreenToLocalBoxXf().Scale * (this._props.NativeDimScaling?.()??1); } // prettier-ignore
     /**
      * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size.
      */
-    @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth, 0.5 * Math.min(NumCast(this.Document.width)))* this.viewScaling; } // prettier-ignore
+    @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth, 0.2 * this._props.PanelWidth())*this.viewScaling; } // prettier-ignore
     /**
      * How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content
      */
-    @computed get uiBtnScaling() { return Math.min(this.maxWidgetSize / this._sideBtnWidth, 1); } // prettier-ignore
+    @computed get uiBtnScaling() { return Math.min(1/(this._props.NativeDimScaling?.()??1), this.maxWidgetSize / this._sideBtnWidth); } // prettier-ignore
 
     @computed get overlayImageIcon() {
         const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`];
@@ -518,10 +516,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         transform: `scale(${this.uiBtnScaling})`,
                         width: this._sideBtnWidth,
                         height: this._sideBtnWidth,
-                        background: 'transparent',
+                        background: 'black',
+                        color: 'white',
                         // color: SettingsManager.userBackgroundColor,
                     }}>
-                    {this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width="100%" height="100%" /> : <FontAwesomeIcon icon="portrait" color={SettingsManager.userColor} size="lg" />}
+                    {this._regenerateLoading ? <ReactLoading type="spin" width="100%" height="100%" /> : <FontAwesomeIcon icon="portrait" size="lg" />}
                 </div>
             </Tooltip>
         );
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index d8ef8308d..a91ec23b8 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -1,4 +1,3 @@
-import { imageUrlToBase64 } from '../../../ClientUtils';
 import { Doc, StrListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { DocCast, ImageCast, StrCast } from '../../../fields/Types';
@@ -42,44 +41,42 @@ export class DrawingFillHandler {
                 const { href } = ImageCast(imageField).url;
                 const hrefParts = href.split('.');
                 const structureUrl = `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`;
-                return imageUrlToBase64(structureUrl)
-                    .then(gptDescribeImage)
-                    .then((prompt, newPrompt = user_prompt || prompt) =>
-                        Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: dims.width, height: dims.height, structureUrl, strength, presets: styles, styleUrl })
-                            .then(res => {
-                                const error = ('error' in res && (res.error as string)) || '';
-                                if (error.includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) {
-                                    window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus();
-                                    return;
-                                }
-                                const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { title: StrCast(drawing.title) + ' AI Images', _width: 400, _height: 400 });
-                                drawing.$ai_firefly_generatedDocs = genratedDocs;
-                                (res as Upload.ImageInformation[]).map(info =>
-                                    Doc.AddDocToList(
-                                        genratedDocs,
-                                        undefined,
-                                        Docs.Create.ImageDocument(info.accessPaths.agnostic.client, {
-                                            ai: 'firefly',
-                                            tags: new List<string>(['@ai']),
-                                            title: newPrompt,
-                                            _data_usePath: 'alternate:hover',
-                                            data_alternates: new List<Doc>([drawing]),
-                                            ai_firefly_prompt: newPrompt,
-                                            _width: 500,
-                                            data_nativeWidth: info.nativeWidth,
-                                            data_nativeHeight: info.nativeHeight,
-                                        }),
-                                        undefined,
-                                        undefined,
-                                        true
-                                    )
-                                );
-                                if (!DocumentView.getFirstDocumentView(genratedDocs)) DocumentViewInternal.addDocTabFunc(genratedDocs, OpenWhere.addRight);
-                            })
-                            .catch(e => {
-                                alert(e.toString());
-                            })
-                    ); // prettier-ignore:q
+                return gptDescribeImage(user_prompt, structureUrl).then(newPrompt =>
+                    Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: dims.width, height: dims.height, structureUrl, strength, presets: styles, styleUrl })
+                        .then(res => {
+                            const error = ('error' in res && (res.error as string)) || '';
+                            if (error.includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) {
+                                window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus();
+                                return;
+                            }
+                            const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { title: StrCast(drawing.title) + ' AI Images', _width: 400, _height: 400 });
+                            drawing.$ai_firefly_generatedDocs = genratedDocs;
+                            (res as Upload.ImageInformation[]).map(info =>
+                                Doc.AddDocToList(
+                                    genratedDocs,
+                                    undefined,
+                                    Docs.Create.ImageDocument(info.accessPaths.agnostic.client, {
+                                        ai: 'firefly',
+                                        tags: new List<string>(['@ai']),
+                                        title: newPrompt,
+                                        _data_usePath: 'alternate:hover',
+                                        data_alternates: new List<Doc>([drawing]),
+                                        ai_firefly_prompt: newPrompt,
+                                        _width: 500,
+                                        data_nativeWidth: info.nativeWidth,
+                                        data_nativeHeight: info.nativeHeight,
+                                    }),
+                                    undefined,
+                                    undefined,
+                                    true
+                                )
+                            );
+                            if (!DocumentView.getFirstDocumentView(genratedDocs)) DocumentViewInternal.addDocTabFunc(genratedDocs, OpenWhere.addRight);
+                        })
+                        .catch(e => {
+                            alert(e.toString());
+                        })
+                ); // prettier-ignore:q
             }
         });
     };
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index e75ede9df..07428798c 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -117,7 +117,6 @@ export default class FireflyManager extends ApiManager {
     generateImage = (prompt: string = 'a realistic illustration of a cat coding', width: number = 2048, height: number = 2048, seed?: number) => {
         let body = `{  "prompt": "${prompt}", "size": { "width": ${width}, "height": ${height}} }`;
         if (seed) {
-            console.log('RECEIVED SEED', seed);
             body = `{  "prompt": "${prompt}", "size": { "width": ${width}, "height": ${height}}, "seeds": [${seed}]}`;
         }
         const fetched = this.getBearerToken().then(response =>
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index c9d5df547..7c55e4a42 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -126,11 +126,8 @@ export default class UploadManager extends ApiManager {
             secureHandler: async ({ req, res }) => {
                 const { sources } = req.body;
                 if (Array.isArray(sources)) {
-                    const results = await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source)));
-                    res.send(results);
-                    return;
-                }
-                res.send();
+                    res.send(await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source))));
+                } else res.send();
             },
         });
 
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index a2747257a..ed109d8f7 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -450,14 +450,12 @@ export namespace DashUploadUtils {
      * 3) the size of the image, in bytes (4432130)
      * 4) the content type of the image, i.e. image/(jpeg | png | ...)
      */
-    export const UploadImage = async (source: string, filename?: string, prefix: string = ''): Promise<Upload.ImageInformation | Error> => {
-        const result = await InspectImage(source);
-        if (result instanceof Error) {
-            return { name: result.name, message: result.message };
-        }
-        const outputFile = filename || result.filename || '';
-        return UploadInspectedImage(result, outputFile, prefix, isLocal().exec(source) || source.startsWith('data:') ? true : false);
-    };
+    export const UploadImage = (source: string, filename?: string, prefix: string = ''): Promise<Upload.ImageInformation | Error> =>
+        InspectImage(source).then(async result =>
+            result instanceof Error
+                ? ({ name: result.name, message: result.message } as Error) //
+                : UploadInspectedImage(result, filename || result.filename || '', prefix, isLocal().exec(source) || source.startsWith('data:') ? true : false)
+        );
 
     type md5 = 'md5';
     type falsetype = false;
-- 
cgit v1.2.3-70-g09d2


From 031a607100700f818f96b7fbf478f1b75292be9b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 5 Apr 2025 00:01:53 -0400
Subject: fixed sizing of  text box annotations sidebar button.  cleaned up
 TagsView to not overlap button bar below doc decorations or bottom resize
 handle

---
 src/client/views/AntimodeMenu.tsx                  |  2 +
 src/client/views/DocumentDecorations.scss          |  3 +-
 src/client/views/DocumentDecorations.tsx           | 34 +++++------
 src/client/views/SidebarAnnos.scss                 | 10 ++++
 src/client/views/SidebarAnnos.tsx                  |  7 +--
 src/client/views/TagsView.scss                     |  3 +-
 src/client/views/TagsView.tsx                      | 21 +++++--
 .../views/global/globalCssVariables.module.scss    |  2 +-
 src/client/views/nodes/DocumentView.scss           |  4 +-
 src/client/views/nodes/DocumentView.tsx            |  3 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 65 ++++++++++++++--------
 11 files changed, 98 insertions(+), 56 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 99dee6410..b5e56cad5 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -4,6 +4,7 @@ import { SnappingManager } from '../util/SnappingManager';
 import './AntimodeMenu.scss';
 import { ObservableReactComponent } from './ObservableReactComponent';
 
+// eslint-disable-next-line @typescript-eslint/no-empty-object-type
 export interface AntimodeMenuProps {}
 
 /**
@@ -162,6 +163,7 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends Observab
                     transitionDuration: this._transitionDuration,
                     transitionDelay: this._transitionDelay,
                     position: this.Pinned ? 'unset' : undefined,
+                    border: `${SnappingManager.userColor} solid 1px`,
                 }}>
                 {buttons}
             </div>
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 732c2645e..28cebe23c 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -41,6 +41,7 @@ $resizeHandler: 8px;
     .documentDecorations-tagsView {
         position: absolute;
         height: 100%;
+        width: 100%;
         pointer-events: all;
         border-radius: 50%;
         color: black;
@@ -327,7 +328,7 @@ $resizeHandler: 8px;
     .documentDecorations-rightResizer {
         pointer-events: auto;
         background: global.$medium-gray-dim;
-        //opacity: 0.2;
+        opacity: 0.2;
         &:hover {
             opacity: 1;
         }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 875eb45e0..452dd60ee 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -49,7 +49,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     static Instance: DocumentDecorations;
     private _resizeHdlId = '';
     private _keyinput = React.createRef<HTMLInputElement>();
-    private _resizeBorderWidth = 16;
+    private _resizeBorderWidth = 8;
     private _linkBoxHeight = 20 + 3; // link button height + margin
     private _titleHeight = 20;
     private _resizeUndo?: UndoManager.Batch;
@@ -98,11 +98,11 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const bounds = { ...this.Bounds };
         const leftBounds = this._props.boundsLeft;
         const topBounds = DocumentView.LightboxDoc() ? 0 : this._props.boundsTop;
-        bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
-        bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
+        bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth) + this._resizeBorderWidth;
+        bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth - this._titleHeight) + this._resizeBorderWidth + this._titleHeight;
         const borderRadiusDraggerWidth = 15;
-        bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth));
-        bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
+        bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth) - this._resizeBorderWidth - borderRadiusDraggerWidth));
+        bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth + this._linkBoxHeight) - this._resizeBorderWidth - this._linkBoxHeight));
         return bounds;
     }
 
@@ -790,10 +790,10 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 <div
                     className="documentDecorations-background"
                     style={{
-                        width: bounds.r - bounds.x + this._resizeBorderWidth + 'px',
-                        height: bounds.b - bounds.y + this._resizeBorderWidth + 'px',
-                        left: bounds.x - this._resizeBorderWidth / 2,
-                        top: bounds.y - this._resizeBorderWidth / 2,
+                        width: bounds.r - bounds.x + 2 * this._resizeBorderWidth + 'px',
+                        height: bounds.b - bounds.y + 2 * this._resizeBorderWidth + 'px',
+                        left: bounds.x - this._resizeBorderWidth,
+                        top: bounds.y - this._resizeBorderWidth,
                         transformOrigin,
                         background: SnappingManager.ShiftKey ? undefined : 'yellow',
                         pointerEvents: SnappingManager.ShiftKey || SnappingManager.IsResizing ? 'none' : 'all',
@@ -807,10 +807,10 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                         <div
                             className={`documentDecorations-container  ${this._showNothing ? 'showNothing' : ''}`}
                             style={{
-                                transform: `translate(${bounds.x - this._resizeBorderWidth / 2}px, ${bounds.y - this._resizeBorderWidth / 2 - this._titleHeight}px) rotate(${rotation}deg)`,
+                                transform: `translate(${bounds.x - this._resizeBorderWidth}px, ${bounds.y - this._resizeBorderWidth - this._titleHeight}px) rotate(${rotation}deg)`,
                                 transformOrigin,
-                                width: bounds.r - bounds.x + this._resizeBorderWidth + 'px',
-                                height: bounds.b - bounds.y + this._resizeBorderWidth + (this._showNothing ? 0 : this._titleHeight) + 'px',
+                                width: bounds.r - bounds.x + 2 * this._resizeBorderWidth + 'px',
+                                height: bounds.b - bounds.y + 2 * this._resizeBorderWidth + (this._showNothing ? 0 : this._titleHeight) + 'px',
                             }}>
                             <div
                                 className="documentDecorations-topbar"
@@ -834,7 +834,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                     <div key="c" className="documentDecorations-centerCont" />
                                     <div key="r" className="documentDecorations-rightResizer" onPointerDown={this.onPointerDown} />
                                     <div key="bl" className="documentDecorations-bottomLeftResizer" onPointerDown={this.onPointerDown} />
-                                    {(seldocview.TagPanelHeight ?? 0) > 30 ? null : <div key="b" className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} />}
+                                    {seldocview.TagPanelHeight !== 0 || seldocview.TagPanelEditing ? null : <div key="b" className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} />}
                                     <div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} />
                                 </>
                             )}
@@ -850,12 +850,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 />
                             )}
 
-                            {hideDocumentButtonBar || this._showNothing ? null : (
+                            {seldocview.TagPanelEditing || hideDocumentButtonBar || this._showNothing ? null : (
                                 <div
                                     className="link-button-container"
                                     style={{
-                                        top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
-                                        transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
+                                        top: DocumentView.Selected().length > 1 || !seldocview.showTags ? 0 : `${seldocview.TagPanelHeight}px`,
+                                        transform: `translate(${-this._resizeBorderWidth + 10}px, ${(seldocview.TagPanelHeight === 0 ? 2 * this._resizeBorderWidth : this._resizeBorderWidth) + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
                                     <DocumentButtonBar views={() => DocumentView.Selected()} />
                                 </div>
@@ -864,7 +864,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 className="documentDecorations-tagsView"
                                 style={{
                                     top: 30, // offset by height of documentButtonBar so that items can be clicked without overlap interference
-                                    transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
+                                    transform: `translate(${-this._resizeBorderWidth + 10}px, ${2 * this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                 }}>
                                 {DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} background={SnappingManager.userBackgroundColor ?? ''} /> : null}
                             </div>
diff --git a/src/client/views/SidebarAnnos.scss b/src/client/views/SidebarAnnos.scss
index d7de2b641..abfd04f11 100644
--- a/src/client/views/SidebarAnnos.scss
+++ b/src/client/views/SidebarAnnos.scss
@@ -1,3 +1,13 @@
+.sidebarAnnos-container {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    right: 0;
+    .sidebarAnnos-stacking {
+        width: 100%;
+        position: relative;
+    }
+}
 .sidebarAnnos-tagList {
     display: flex;
     flex-direction: row;
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 3c0611f03..b7e6318b1 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -213,14 +213,11 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
         // TODO: Calculation of the topbar is hardcoded and different for text nodes - it should all be the same and all be part of SidebarAnnos
         return !this._props.showSidebar ? null : (
             <div
+                className="sidebarAnnos-container"
                 style={{
-                    position: 'absolute',
                     pointerEvents: this._props.isContentActive() ? 'all' : undefined,
                     top: this._props.Doc.type !== DocumentType.RTF && StrCast(this._props.Doc._layout_showTitle) === 'title' ? 15 : 0,
-                    right: 0,
                     background: this._props.styleProvider?.(this._props.Doc, this._props, StyleProp.WidgetColor) as string,
-                    width: `100%`,
-                    height: '100%',
                 }}>
                 <div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight() }} onWheel={e => e.stopPropagation()}>
                     {this.allUsers.length > 1 ? this.allUsers.map(renderUsers) : null}
@@ -228,7 +225,7 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                     {Array.from(this.allMetadata.keys()).sort().map(renderMeta)}
                 </div>
 
-                <div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
+                <div className="sidebarAnnos-stacking" style={{ height: `calc(100% - ${this.filtersHeight()}px)` }}>
                     <CollectionStackingView
                         {...this._props}
                         setContentViewBox={emptyFunction}
diff --git a/src/client/views/TagsView.scss b/src/client/views/TagsView.scss
index 231d5e11a..d58b55ab5 100644
--- a/src/client/views/TagsView.scss
+++ b/src/client/views/TagsView.scss
@@ -2,13 +2,12 @@
     display: flex;
     flex-wrap: wrap;
     flex-direction: column;
-    border: 1px solid;
-    border-radius: 4px;
     width: 100%;
     position: relative;
     .tagsView-content {
         width: 100%;
         height: inherit;
+        position: absolute;
         .tagsView-list {
             display: flex;
             flex-wrap: wrap;
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 39c952483..79c4d8af1 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -267,7 +267,6 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
         super(props);
         makeObservable(this);
     }
-    InsetDist = 25; // how far tag panel is moved up to overlap DocumentView.
 
     @observable _panelHeightDirty = 0;
     @observable _currentInput = '';
@@ -298,8 +297,8 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
 
     @computed get isEditing() {
         const selected = DocumentView.Selected().length === 1 && DocumentView.Selected().includes(this.View);
-        if (this._isEditing === undefined) return selected && !StrListCast(this.View.dataDoc.tags).length && !StrListCast(this.View.dataDoc[Doc.LayoutFieldKey(this.View.Document) + '_audioAnnotations_text']).length;
-        return this._isEditing && (this._props.Views.length > 1 || selected);
+        if (this._isEditing === undefined) return selected && this.View.TagPanelEditing; // && !StrListCast(this.View.dataDoc.tags).length && !StrListCast(this.View.dataDoc[Doc.LayoutFieldKey(this.View.Document) + '_audioAnnotations_text']).length;
+        return this._isEditing && (this._props.Views.length > 1 || (selected && this.View.TagPanelEditing));
     }
 
     /**
@@ -309,7 +308,10 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
     @action
     setToEditing = (editing = true) => {
         this._isEditing = editing;
-        editing && this._props.Views.length === 1 && this.View.select(false);
+        if (this._props.Views.length === 1) {
+            this.View.TagPanelEditing = editing;
+            editing && this.View.select(false);
+        }
     };
 
     /**
@@ -350,7 +352,16 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
         return this.View.ComponentView?.isUnstyledView?.() || (!this.View.showTags && this._props.Views.length === 1) ? null : (
             <div
                 className="tagsView-container"
-                ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
+                ref={r =>
+                    r &&
+                    new ResizeObserver(
+                        action(() => {
+                            if (this._props.Views.length === 1) {
+                                this.View.TagPanelHeight = Math.floor(r?.children[0].children[0].getBoundingClientRect().height ?? 0) - Math.floor(r?.children[0].children[0].children[0].getBoundingClientRect().height ?? 0);
+                            }
+                        })
+                    ).observe(r?.children[0])
+                }
                 style={{
                     display: SnappingManager.IsResizing === this.View.Document[Id] ? 'none' : undefined,
                     backgroundColor: this.isEditing ? this._props.background : Colors.TRANSPARENT,
diff --git a/src/client/views/global/globalCssVariables.module.scss b/src/client/views/global/globalCssVariables.module.scss
index ad0c5c21d..82f6caa52 100644
--- a/src/client/views/global/globalCssVariables.module.scss
+++ b/src/client/views/global/globalCssVariables.module.scss
@@ -4,7 +4,7 @@ $white: #ffffff;
 $off-white: #fdfdfd;
 $light-gray: #dfdfdf;
 $medium-gray: #9f9f9f;
-$medium-gray-dim: #9f9f9f30;
+$medium-gray-dim: #9f9f9f;
 $dark-gray: #323232;
 $black: #000000;
 
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index dd5fd0d0c..f60f33608 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -273,7 +273,9 @@
 
 .documentView-noAIWidgets {
     transform-origin: top left;
-    position: relative;
+    position: absolute;
+    bottom: 0;
+    pointer-events: none;
 }
 
 .documentView-editorView-history {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 37f888ddd..4cf60d356 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -748,7 +748,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                         style={{
                             width: `${100 / this.uiBtnScaling}%`, //
                             transform: `scale(${this.uiBtnScaling})`,
-                            bottom: Number.isNaN(this.maxWidgetSize) ? undefined : this.maxWidgetSize,
+                            height: Number.isNaN(this.maxWidgetSize) ? undefined : this.TagsBtnHeight * this.uiBtnScaling,
                         }}>
                         {this._props.DocumentView?.() && !this._props.docViewPath().slice(-2)[0].ComponentView?.isUnstyledView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
                     </div>
@@ -1163,6 +1163,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     @observable private _selected = false;
     @observable public static CurrentlyPlaying: DocumentView[] = []; // audio or video media views that are currently playing
     @observable public TagPanelHeight = 0;
+    @observable public TagPanelEditing = false;
 
     @computed get showTags() {
         return this.Document._layout_showTags || this._props.showTags;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f7e6d8e1e..d90be007f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -154,11 +154,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     // eslint-disable-next-line no-return-assign
     @computed get config()                     { return FormattedTextBox.MakeConfig(this._rules = new RichTextRules(this.Document, this), this); } // prettier-ignore
-    @computed get recordingDictation()        { return this.dataDoc?.mediaState === mediaState.Recording; } // prettier-ignore
-    @computed get SidebarShown()               {  return !!(this._showSidebar || this.layoutDoc._layout_showSidebar);  } // prettier-ignore
+    @computed get recordingDictation()         { return this.dataDoc?.mediaState === mediaState.Recording; } // prettier-ignore
+    @computed get SidebarShown()               { return !!(this._showSidebar || this.layoutDoc._layout_showSidebar);  } // prettier-ignore
     @computed get allSidebarDocs()             { return DocListCast(this.dataDoc[this.sidebarKey]); } // prettier-ignore
     @computed get noSidebar()                  { return this.DocumentView?.()._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar; } // prettier-ignore
-    @computed get layout_sidebarWidthPercent() { return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } // prettier-ignore
+    @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, this._showSidebar ? '20%' :'0%'); } // prettier-ignore
     @computed get sidebarColor()               { return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4')); } // prettier-ignore
     @computed get layout_autoHeight()          { return (this._props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this._props.ignoreAutoHeight; } // prettier-ignore
     @computed get textHeight()                 { return NumCast(this.dataDoc[this.fieldKey + '_height']); } // prettier-ignore
@@ -743,15 +743,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     @action
     toggleSidebar = (preview: boolean = false) => {
         const defaultSidebar = 250;
-        const prevWidth = 1 - this.sidebarWidth() / DivWidth(this._ref.current!);
+        const dw = DivWidth(this._ref.current);
+        const prevWidth = 1 - this.sidebarWidth() / dw / this.nativeScaling();
         if (preview) this._showSidebar = true;
         else {
-            this.layoutDoc[this.sidebarKey + '_freeform_scale_max'] = 1;
-            this.layoutDoc._layout_showSidebar =
-                (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(defaultSidebar / (NumCast(this.layoutDoc._width) + defaultSidebar)) * 100}%` : '0%') !== '0%';
+            this.layoutDoc._layout_sidebarWidthPercent =
+                this.sidebarWidthPercent === '0%' //
+                    ? `${(defaultSidebar / (NumCast(this.layoutDoc._width) + defaultSidebar)) * 100}%` //
+                    : '0%';
+            this.layoutDoc._layout_showSidebar = this.sidebarWidthPercent !== '0%';
         }
 
-        this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) + defaultSidebar : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
+        this.layoutDoc._width =
+            !preview && this.SidebarShown //
+                ? NumCast(this.layoutDoc._width) + defaultSidebar
+                : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
     };
     sidebarDown = (e: React.PointerEvent) => {
         const batch = UndoManager.StartBatch('toggle sidebar');
@@ -769,7 +775,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     };
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
         const localDelta = this.DocumentView?.().screenToViewTransform().transformDirection(delta[0], delta[1]) ?? delta;
-        const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100;
+        const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.sidebarWidthPercent.replace('%', ''))) / 100;
         const width = NumCast(this.layoutDoc._width) + localDelta[0];
         this.layoutDoc._layout_sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%';
         this.layoutDoc.width = width;
@@ -1223,7 +1229,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     }
 
     @computed get contentScaling() {
-        return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this._props.NativeDimScaling?.() || 1 : 1;
+        return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this.nativeScaling() : 1;
     }
     componentDidMount() {
         !this._props.dontSelectOnLoad && this._props.setContentViewBox?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
@@ -1891,7 +1897,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         }
     };
     fitContentsToBox = () => BoolCast(this.Document._freeform_fitContentsToBox);
-    sidebarContentScaling = () => (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+    nativeScaling = () => this._props.NativeDimScaling?.() || 1;
     sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.sidebarKey) => {
         if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
         return this.addDocument(doc, sidebarKey);
@@ -1901,12 +1907,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     setSidebarHeight = (height: number) => {
         this.dataDoc[this.sidebarKey + '_height'] = height;
     };
-    sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
+    sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
     sidebarScreenToLocal = () =>
         this._props
             .ScreenToLocalTransform()
-            .translate(-(this._props.PanelWidth() - this.sidebarWidth()) / (this._props.NativeDimScaling?.() || 1), 0)
-            .scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this._props.NativeDimScaling?.() || 1));
+            .translate(-(this._props.PanelWidth() - this.sidebarWidth()) / this.nativeScaling(), 0)
+            .scale(1 / this.nativeScaling());
 
     @computed get audioHandle() {
         return !this.recordingDictation ? null : (
@@ -1927,6 +1933,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             </div>
         );
     }
+    private _sideBtnWidth = 35;
+    /**
+     * How much the content of the view is being scaled based on its nesting and its fit-to-width settings
+     */
+    @computed get viewScaling() { return this.ScreenToLocalBoxXf().Scale ; } // prettier-ignore
+    /**
+     * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size.
+     */
+    @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth, 0.2 * this._props.PanelWidth())*this.viewScaling; } // prettier-ignore
+    /**
+     * How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content
+     */
+    @computed get uiBtnScaling() { return this.maxWidgetSize / this._sideBtnWidth; } // prettier-ignore
+
     @computed get sidebarHandle() {
         TraceMobx();
         const annotated = DocListCast(this.dataDoc[this.sidebarKey]).filter(d => d?.author).length;
@@ -1941,7 +1961,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     backgroundColor,
                     color,
                     opacity: annotated ? 1 : undefined,
-                    transform: `scale(${this._props.DocumentView?.().UIBtnScaling ?? 1})`,
+                    transform: `scale(${this.uiBtnScaling})`,
                 }}>
                 <FontAwesomeIcon icon="comment-alt" />
             </div>
@@ -1986,7 +2006,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                         isAnnotationOverlay={false}
                         select={emptyFunction}
                         isAnyChildContentActive={returnFalse}
-                        NativeDimScaling={this.sidebarContentScaling}
+                        NativeDimScaling={this.nativeScaling}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                         removeDocument={this.sidebarRemDocument}
                         moveDocument={this.sidebarMoveDocument}
@@ -2003,7 +2023,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             );
         };
         return (
-            <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+            <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
                 {renderComponent(StrCast(this.layoutDoc[this.sidebarKey + '_type_collection']))}
             </div>
         );
@@ -2061,7 +2081,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
         // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
         if (this._props.isContentActive()) {
-            const scale = this._props.NativeDimScaling?.() || 1;
+            const scale = this.nativeScaling();
             const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._header_height}px' >
             const height = Number(styleFromLayout.height?.replace('px', ''));
             // prevent default if selected || child is active  but this doc isn't scrollable
@@ -2078,8 +2098,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     render() {
         TraceMobx();
-        const scale = this._props.NativeDimScaling?.() || 1;
-        const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
+        const scale = this.nativeScaling();
+        const rounded = StrCast(this.layoutDoc._layout_borderRounding) === '100%' ? '-rounded' : '';
         setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
 
         const scrSize = (which: number, view = this._props.docViewPath().slice(-which)[0]) => 
@@ -2107,7 +2127,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                               height: `${100 / scale}%`,
                           }),
                     transition: 'inherit',
-                    paddingBottom: this.tagsHeight,
                     // overflowY: this.layoutDoc._layout_autoHeight ? "hidden" : undefined,
                     color: this.fontColor,
                     fontSize: this.fontSize,
@@ -2140,7 +2159,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                             this._scrollRef = r;
                         }}
                         style={{
-                            width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+                            width: this.noSidebar ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`,
                             overflow: this.layoutDoc._createDocOnCR || this.layoutDoc._layout_hideScroll ? 'hidden' : this.layout_autoHeight ? 'visible' : undefined,
                         }}
                         onScroll={this.onScroll}
@@ -2160,7 +2179,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                             }}
                         />
                     </div>
-                    {this.noSidebar || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+                    {this.noSidebar || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
                     {this.noSidebar || this.Document._layout_noSidebar || this.Document._createDocOnCR || this.layoutDoc._chromeHidden || this.Document.quiz ? null : this.sidebarHandle}
                     {this.audioHandle}
                     {this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}
-- 
cgit v1.2.3-70-g09d2


From 4f236271c39380f15409b8fb38b2f6e2bae8df9f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 9 Apr 2025 11:44:56 -0400
Subject: cleaned up color setting for templates so that instances can override
 template default color.

---
 src/client/views/DocComponent.tsx        | 19 +++++++++++++++++++
 src/client/views/StyleProvider.tsx       |  8 +++++---
 src/client/views/global/globalScripts.ts |  7 ++++---
 src/client/views/nodes/DocumentView.tsx  |  8 ++++----
 4 files changed, 32 insertions(+), 10 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index d33f3b713..b8b70d9d2 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -49,6 +49,13 @@ export function DocComponent<P extends DocComponentProps>() {
         get rootDoc() {
             return DocCast(this.Document.rootDocument, this.Document);
         }
+
+        /**
+         * Whether the doc is a sub-componentn of a compound template doc.
+         */
+        get isTemplate() {
+            return this.rootDoc !== this.layoutDoc;
+        }
         /**
          * This is the document being rendered by the React component. In the
          * case of a compound template, this will be the  expanded template Doc
@@ -109,6 +116,12 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
         get rootDoc() {
             return DocCast(this.Document.rootDocument, this.Document);
         }
+        /**
+         * Whether the doc is a sub-componentn of a compound template doc.
+         */
+        get isTemplate() {
+            return this.rootDoc !== this.layoutDoc;
+        }
         /**
          * This is the document being rendered by the React component. In the
          * case of a compound template, this will be the  expanded template Doc
@@ -183,6 +196,12 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
         @computed get rootDoc() {
             return DocCast(this.Document.rootDocument, this.Document);
         }
+        /**
+         * Whether the doc is a sub-componentn of a compound template doc.
+         */
+        get isTemplate() {
+            return this.rootDoc !== this.layoutDoc;
+        }
         /**
          * This is the document being rendered.  It may be a template so it may or may no inherit from the data doc.
          */
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index ae94c206c..a1c6aa1a2 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -251,9 +251,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             const dataKey = doc ? Doc.LayoutFieldKey(doc) : "";
             const usePath = StrCast(doc?.[dataKey + "_usePath"]);
             const alternate = usePath.includes(":hover") ? ( isHovering?.() ? '_' + usePath.replace(":hover","") : "") : usePath ? "_" +usePath:usePath;
-            let docColor: Opt<string> = StrCast(doc?.[fieldKey+alternate], StrCast(doc?.['backgroundColor' +alternate], isCaption ? 'rgba(0,0,0,0.4)' : ''));
-            if (!docColor && doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc) docColor = StrCast(doc._backgroundColor,docColor);
-            if (docColor && doc?._override_backgroundColor) docColor = StrCast(doc._backgroundColor,docColor);
+            let docColor:Opt<string> = StrCast(doc?._backgroundColor, // renderDoc === this.layoutDoc
+                StrCast(doc?.[fieldKey+alternate],       // doc === this.Document
+                    StrCast(doc?.['backgroundColor' +alternate], 
+                        isCaption ? 'rgba(0,0,0,0.4)' : '')));
+            
             // prettier-ignore
             switch (layoutDoc?.type) {
                 case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 04b2873d1..caacd0a11 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -38,6 +38,7 @@ import { VideoBox } from '../nodes/VideoBox';
 import { WebBox } from '../nodes/WebBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
+import { DocData } from '../../../fields/DocSymbols';
 
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function IsNoneSelected() {
@@ -131,10 +132,10 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
                 obj[fieldKey] = color;
                 CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj);
             } else {
-                const dataKey = Doc.LayoutFieldKey(dv.Document);
+                const colorDoc = dv.isTemplate ? dv.layoutDoc : dv.dataDoc; // assigning to a template should not assign to the data doc
+                const dataKey = Doc.LayoutFieldKey(colorDoc);
                 const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', '');
-                dv.layoutDoc[fieldKey + alternate] = undefined;
-                dv.dataDoc[fieldKey + alternate] = color;
+                colorDoc[fieldKey + alternate] = color;
             }
         });
     } else {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4cf60d356..436f35d4c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -103,14 +103,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
     @computed get border()            { return this.style(this.layoutDoc, StyleProp.Border) as string || ""; } // prettier-ignore
     @computed get borderRounding()    { return this.style(this.layoutDoc, StyleProp.BorderRounding) as string; } // prettier-ignore
     @computed get widgetDecorations() { return this.style(this.layoutDoc, StyleProp.Decorations) as JSX.Element; } // prettier-ignore
-    @computed get backgroundBoxColor(){ return this.style(this.Document, StyleProp.BackgroundColor + ':docView') as string; } // prettier-ignore
+    @computed get backgroundBoxColor(){ return this.style(this.layoutDoc, StyleProp.BackgroundColor + ':docView') as string; } // prettier-ignore
     @computed get showTitle()         { return this.style(this.layoutDoc, StyleProp.ShowTitle) as Opt<string>; } // prettier-ignore
     @computed get showCaption()       { return this.style(this.layoutDoc, StyleProp.ShowCaption) as string ?? "";  } // prettier-ignore
     @computed get headerMargin()      { return this.style(this.layoutDoc, StyleProp.HeaderMargin)  as number ?? 0; } // prettier-ignore
     @computed get titleHeight()       { return this.style(this.layoutDoc, StyleProp.TitleHeight) as number ?? 0;  } // prettier-ignore
-    @computed get docContents()       { return this.style(this.Document, StyleProp.DocContents) as JSX.Element; } // 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
+    @computed get docContents()       { return this.style(this.Document,  StyleProp.DocContents) as JSX.Element; } // 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
 
     @computed get onClickHdlr()       { return this._props.onClickScript?.()       ?? ScriptCast(this.layoutDoc.onClick       ?? this.Document.onClick); } // prettier-ignore
     @computed get onDoubleClickHdlr() { return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick ?? this.Document.onDoubleClick); } // prettier-ignore
-- 
cgit v1.2.3-70-g09d2


From 2d382a1dd28aa5aae22669988475e5721435b738 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 9 Apr 2025 14:55:28 -0400
Subject: more color template fixes.

---
 src/client/views/DocComponent.tsx        | 17 +++--------------
 src/client/views/StyleProvider.tsx       | 20 ++++++++++++--------
 src/client/views/global/globalScripts.ts |  4 ++--
 src/client/views/nodes/DocumentView.tsx  |  2 +-
 4 files changed, 18 insertions(+), 25 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index b8b70d9d2..45c80c6f7 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -19,6 +19,7 @@ import { FieldViewProps } from './nodes/FieldView';
  * */
 export interface DocComponentProps {
     Document: Doc;
+    TemplateDataDocument?: Doc;
     LayoutTemplate?: () => Opt<Doc>;
     LayoutTemplateString?: string;
 }
@@ -53,8 +54,8 @@ export function DocComponent<P extends DocComponentProps>() {
         /**
          * Whether the doc is a sub-componentn of a compound template doc.
          */
-        get isTemplate() {
-            return this.rootDoc !== this.layoutDoc;
+        get isTemplateForField() {
+            return this.rootDoc !== this.layoutDoc && this._props.TemplateDataDocument;
         }
         /**
          * This is the document being rendered by the React component. In the
@@ -116,12 +117,6 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
         get rootDoc() {
             return DocCast(this.Document.rootDocument, this.Document);
         }
-        /**
-         * Whether the doc is a sub-componentn of a compound template doc.
-         */
-        get isTemplate() {
-            return this.rootDoc !== this.layoutDoc;
-        }
         /**
          * This is the document being rendered by the React component. In the
          * case of a compound template, this will be the  expanded template Doc
@@ -196,12 +191,6 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
         @computed get rootDoc() {
             return DocCast(this.Document.rootDocument, this.Document);
         }
-        /**
-         * Whether the doc is a sub-componentn of a compound template doc.
-         */
-        get isTemplate() {
-            return this.rootDoc !== this.layoutDoc;
-        }
         /**
          * This is the document being rendered.  It may be a template so it may or may no inherit from the data doc.
          */
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index a1c6aa1a2..a3067907d 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -248,14 +248,18 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 : 0;
         case StyleProp.BackgroundColor: {
             if (SnappingManager.LastPressedBtn === doc?.[Id]) return SnappingManager.userColor; // hack to indicate active menu panel item
-            const dataKey = doc ? Doc.LayoutFieldKey(doc) : "";
-            const usePath = StrCast(doc?.[dataKey + "_usePath"]);
-            const alternate = usePath.includes(":hover") ? ( isHovering?.() ? '_' + usePath.replace(":hover","") : "") : usePath ? "_" +usePath:usePath;
-            let docColor:Opt<string> = StrCast(doc?._backgroundColor, // renderDoc === this.layoutDoc
-                StrCast(doc?.[fieldKey+alternate],       // doc === this.Document
-                    StrCast(doc?.['backgroundColor' +alternate], 
-                        isCaption ? 'rgba(0,0,0,0.4)' : '')));
-            
+            const dataKey = doc ? Doc.LayoutFieldKey(doc) : '';
+            const usePath = StrCast(doc?.[dataKey + '_usePath']);
+            const alternate = usePath.includes(':hover') ? ( isHovering?.() ? '_' + usePath.replace(':hover','') : '') : usePath ? "_" +usePath:usePath;
+            let docColor:Opt<string> = layoutDoc &&
+                StrCast(alternate ? layoutDoc['backgroundColor' + alternate]:undefined,
+                    doc.rootDocument 
+                        ? StrCast(layoutDoc.backgroundColor, StrCast(DocCast(doc.rootDocument).backgroundColor))  // for nested templates: use template's color, then root doc's color
+                        : layoutDoc === doc 
+                            ? StrCast(doc.backgroundColor)
+                            : StrCast(StrCast(Doc.GetT(layoutDoc, 'backgroundColor', 'string', true), StrCast(doc.backgroundColor, StrCast(layoutDoc.backgroundColor)) // otherwise, use expanded template coloor, then root doc's color, then template's inherited color
+                    )));
+        
             // prettier-ignore
             switch (layoutDoc?.type) {
                 case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index caacd0a11..32afb3d3d 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -120,7 +120,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
             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] || defaultFill();
+            return (contentFrameNumber !== undefined ? CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] : selView.backgroundColor()) || defaultFill();
         }
         !selectedViews.length && setDefaultFill(color ?? 'transparent');
         selectedViews.forEach(dv => {
@@ -132,7 +132,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
                 obj[fieldKey] = color;
                 CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj);
             } else {
-                const colorDoc = dv.isTemplate ? dv.layoutDoc : dv.dataDoc; // assigning to a template should not assign to the data doc
+                const colorDoc = dv.isTemplateForField ? dv.layoutDoc : dv.dataDoc; // assigning to a template's compoment field should not assign to the data doc
                 const dataKey = Doc.LayoutFieldKey(colorDoc);
                 const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', '');
                 colorDoc[fieldKey + alternate] = color;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 436f35d4c..b03dd3944 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -103,11 +103,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
     @computed get border()            { return this.style(this.layoutDoc, StyleProp.Border) as string || ""; } // prettier-ignore
     @computed get borderRounding()    { return this.style(this.layoutDoc, StyleProp.BorderRounding) as string; } // prettier-ignore
     @computed get widgetDecorations() { return this.style(this.layoutDoc, StyleProp.Decorations) as JSX.Element; } // prettier-ignore
-    @computed get backgroundBoxColor(){ return this.style(this.layoutDoc, StyleProp.BackgroundColor + ':docView') as string; } // prettier-ignore
     @computed get showTitle()         { return this.style(this.layoutDoc, StyleProp.ShowTitle) as Opt<string>; } // prettier-ignore
     @computed get showCaption()       { return this.style(this.layoutDoc, StyleProp.ShowCaption) as string ?? "";  } // prettier-ignore
     @computed get headerMargin()      { return this.style(this.layoutDoc, StyleProp.HeaderMargin)  as number ?? 0; } // prettier-ignore
     @computed get titleHeight()       { return this.style(this.layoutDoc, StyleProp.TitleHeight) as number ?? 0;  } // prettier-ignore
+    @computed get backgroundBoxColor(){ return this.style(this.Document,  StyleProp.BackgroundColor + ':docView') as string; } // prettier-ignore
     @computed get docContents()       { return this.style(this.Document,  StyleProp.DocContents) as JSX.Element; } // 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
-- 
cgit v1.2.3-70-g09d2


From b0bb28202fa77556768d1da93bf9ea879c6ba2e5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 10 Apr 2025 12:05:55 -0400
Subject: performance fixes for text boxes to not call screenToLocal
 unnecessarily.  simplifications to template data docs

---
 src/client/views/DocumentDecorations.scss          |   2 +-
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/StyleProvider.tsx                 |  24 +++--
 src/client/views/TagsView.scss                     |   3 +
 .../views/collections/CollectionCarouselView.scss  |   2 +
 .../views/collections/CollectionCarouselView.tsx   |  14 ++-
 .../views/collections/CollectionNoteTakingView.tsx |   6 +-
 .../views/collections/CollectionStackingView.tsx   |  13 ++-
 src/client/views/collections/CollectionSubView.tsx |   7 +-
 .../views/collections/CollectionTreeView.tsx       |  14 +--
 .../collectionGrid/CollectionGridView.tsx          |  11 +--
 .../CollectionMulticolumnView.tsx                  | 106 ++++++++++-----------
 .../CollectionMultirowView.tsx                     | 106 ++++++++++-----------
 src/client/views/nodes/DocumentView.scss           |   6 ++
 src/client/views/nodes/DocumentView.tsx            |  87 ++++++++++-------
 src/client/views/nodes/FieldView.tsx               |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  16 ++--
 .../formattedText/ProsemirrorExampleTransfer.ts    |   3 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |   2 +-
 19 files changed, 229 insertions(+), 197 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 28cebe23c..09a13634f 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -40,10 +40,10 @@ $resizeHandler: 8px;
     }
     .documentDecorations-tagsView {
         position: absolute;
-        height: 100%;
         width: 100%;
         pointer-events: all;
         border-radius: 50%;
+        top: 30; // offset by height of documentButtonBar so that items can be clicked without overlap interference
         color: black;
     }
 }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 452dd60ee..0756d3d9e 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -863,7 +863,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                             <div
                                 className="documentDecorations-tagsView"
                                 style={{
-                                    top: 30, // offset by height of documentButtonBar so that items can be clicked without overlap interference
+                                    display: DocumentView.Selected().length > 1 || !seldocview.showTags ? 'none' : undefined,
                                     transform: `translate(${-this._resizeBorderWidth + 10}px, ${2 * this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                 }}>
                                 {DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} background={SnappingManager.userBackgroundColor ?? ''} /> : null}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index a3067907d..e8876f96f 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -327,7 +327,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                         ? undefined // if it's a background & has a cluster color, make the shadow spread really big
                         : fieldKey.includes('_inline')  // if doc is an inline document in a text box
                         ? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0vw 0vw 0.1vw')}`
-                        :doc.rootDocument !== doc.embedContainer && DocCast(doc.embedContainer)?.type === DocumentType.RTF && !isInk() // if doc is embedded in a text document (but not an inline) and this isn't a simple text template (where the layoutDoc's rootDocument is its embed container)
+                        : doc.rootDocument !== doc.embedContainer && DocCast(doc.embedContainer)?.type === DocumentType.RTF && !isInk() // if doc is embedded in a text document (but not an inline) and this isn't a simple text template (where the layoutDoc's rootDocument is its embed container)
                         ? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
                         : StrCast(doc.layout_boxShadow, '');
             }
@@ -342,25 +342,28 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             if (isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations: {
-            const lock = () => doc?.pointerEvents !== 'none' ? null : (
+            const showLock = doc?.pointerEvents === 'none'
+            const lock = () =>  !showLock ? null : (
                         <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
                             <FontAwesomeIcon icon='lock' size="lg" />
                         </div>
                     );
-            const paint = () => !doc?.onPaint ? null : (
+            const showPaint = doc?.onPaint;
+            const paint = () => !showPaint ? null : (
                         <div className={`styleProvider-paint${isSelected?.() ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
                             <FontAwesomeIcon icon='pen' size="lg" />
                         </div>
                     );
+            const showFilterIcon =
+                StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
+                    ? 'green' // #18c718bd' //'hasFilter'
+                    : childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length
+                    ? 'orange' // 'inheritsFilter'
+                    : undefined;
+            const showFilter = showFilterIcon && !hideFilterStatus;
             const filter = () => {
                 const dashView = untracked(() => DocumentView.getDocumentView(Doc.ActiveDashboard));
-                const showFilterIcon =
-                    StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
-                        ? 'green' // #18c718bd' //'hasFilter'
-                        : childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length
-                        ? 'orange' // 'inheritsFilter'
-                        : undefined;
-                return !showFilterIcon || hideFilterStatus ? null : (
+                return !showFilter ? null : (
                     <div className="styleProvider-filter">
                         <Dropdown
                             type={Type.TERT}
@@ -394,6 +397,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             };
         
             return (
+                !showPaint && !showLock && !showFilter ? null:
                 <>
                     {paint()}
                     {lock()}
diff --git a/src/client/views/TagsView.scss b/src/client/views/TagsView.scss
index d58b55ab5..a940502d4 100644
--- a/src/client/views/TagsView.scss
+++ b/src/client/views/TagsView.scss
@@ -16,6 +16,9 @@
                 min-height: unset !important;
             }
         }
+        .tagsView-editing-box {
+            pointer-events: all;
+        }
     }
 }
 
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
index 1080aa703..962b590c8 100644
--- a/src/client/views/collections/CollectionCarouselView.scss
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -9,6 +9,8 @@
         height: 50;
         display: inline-block;
         width: 100%;
+        bottom: 0;
+        position: absolute;
     }
     .collectionCarouselView-image {
         height: calc(100% - 50px);
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index c80ac23eb..2d3f8cb0e 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -16,7 +16,6 @@ import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionCarouselView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
-import { DocData } from '../../../fields/DocSymbols';
 
 @observer
 export class CollectionCarouselView extends CollectionSubView() {
@@ -116,12 +115,14 @@ export class CollectionCarouselView extends CollectionSubView() {
                 ? false
                 : undefined; // prettier-ignore
     onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
+
     renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => {
         return (
             <DocumentView
                 {...this._props}
                 ref={overlayFunc}
                 Document={doc}
+                TemplateDataDocument={doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
                 fitWidth={this._props.childLayoutFitWidth}
@@ -137,7 +138,6 @@ export class CollectionCarouselView extends CollectionSubView() {
                 renderDepth={this._props.renderDepth + 1}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
-                TemplateDataDocument={doc[DocData]}
                 childFilters={this.childDocFilters}
                 focus={this.focus}
                 hideDecorations={BoolCast(this.layoutDoc.layout_hideDecorations)}
@@ -183,7 +183,15 @@ export class CollectionCarouselView extends CollectionSubView() {
     }
 
     @computed get content() {
-        const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined };
+        const captionProps = {
+            ...this._props, //
+            NativeScaling: returnOne,
+            PanelWidth: this.captionWidth,
+            fieldKey: 'caption',
+            setHeight: undefined,
+            setContentView: undefined,
+            noSidebar: true,
+        };
         const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption);
         return !this.curDoc() ? null : (
             <>
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 01695dbaf..173147f64 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -258,11 +258,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const noteTakingDocTransform = () => this.getDocTransform(doc, dref);
         return (
             <DocumentView
-                ref={r => {
-                    dref = r || undefined;
-                }}
+                ref={r => (dref = r || undefined)}
                 Document={doc}
-                TemplateDataDocument={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
+                TemplateDataDocument={doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined}
                 pointerEvents={this.blockPointerEventsWhenDragging}
                 renderDepth={this._props.renderDepth + 1}
                 PanelWidth={width}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d7cf0ffc2..4f60acb18 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -675,12 +675,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     }
 
     return35 = () => 35;
+    @computed get menuBtnDoc() { return DocCast(this.layoutDoc.layout_headerButton); } // prettier-ignore
     @computed get buttonMenu() {
-        const menuDoc = DocCast(this.layoutDoc.layout_headerButton);
-        return !menuDoc ? null : (
-            <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}>
+        return !this.menuBtnDoc ? null : (
+            <div className="buttonMenu-docBtn" style={{ width: NumCast(this.menuBtnDoc._width, 30), height: NumCast(this.menuBtnDoc._height, 30) }}>
                 <DocumentView
-                    Document={menuDoc}
+                    Document={this.menuBtnDoc}
                     isContentActive={this.isContentActive}
                     isDocumentActive={this.isContentActive}
                     addDocument={this._props.addDocument}
@@ -728,13 +728,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             SetValue: this.addGroup,
             contents: '+ ADD A GROUP',
         };
-        const buttonMenu = this.layoutDoc.layout_headerButton;
         const noviceExplainer = this.layoutDoc.layout_explainer;
         return (
             <>
-                {buttonMenu || noviceExplainer ? (
+                {this.menuBtnDoc || noviceExplainer ? (
                     <div className="documentButtonMenu">
-                        {buttonMenu ? this.buttonMenu : null}
+                        {this.menuBtnDoc ? this.buttonMenu : null}
                         {Doc.noviceMode && noviceExplainer ? <div className="documentExplanation">{StrCast(noviceExplainer)}</div> : null}
                     </div>
                 ) : null}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a88707c6f..375c0fe53 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -603,7 +603,12 @@ export function CollectionSubView<X>() {
          */
         @computed get uiBtnScaling() { return this.maxWidgetSize / this._sideBtnWidth; } // prettier-ignore
 
-        screenXPadding = () => (this.uiBtnScaling * this._sideBtnWidth - NumCast(this.layoutDoc.xMargin)) / this._props.ScreenToLocalTransform().Scale;
+        screenXPadding = (docView?: DocumentView) => {
+            if (!docView) return 0;
+            const diff = this._props.PanelWidth() - docView.PanelWidth();
+            const xpad1 = this.uiBtnScaling * (this._sideBtnWidth - NumCast(this.layoutDoc.xMargin)) - diff / 2; // this._sideBtnWidth;
+            return xpad1 / (docView.NativeDimScaling?.() || 1);
+        };
         filteredChildDocs = () => this.childLayoutPairs.map(pair => pair.layout);
         childDocsFunc = () => this.childDocs;
         @action setFilterFunc = (func?: (doc: Doc) => boolean) => { this._filterFunc = func; }; // prettier-ignore
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 418c437d5..962fbdcd7 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -8,7 +8,7 @@ import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, Utils } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
@@ -200,7 +200,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     onContextMenu = (): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
         const layoutItems: ContextMenuProps[] = [];
-        const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick)?.script.originalScript === CollectionTreeView.AddTreeFunc;
+        const menuDoc = ScriptCast(this.menuBtnDoc?.onClick)?.script.originalScript === CollectionTreeView.AddTreeFunc;
         menuDoc && layoutItems.push({ description: 'Create new folder', event: () => CollectionTreeView.addTreeFolder(this.Document), icon: 'paint-brush' });
         if (!Doc.noviceMode) {
             layoutItems.push({
@@ -347,14 +347,14 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     }
 
     return35 = () => 35;
+    @computed get menuBtnDoc() { return DocCast(this.layoutDoc.layout_headerButton); } // prettier-ignore
     @computed get buttonMenu() {
-        const menuDoc = Cast(this.layoutDoc.layout_headerButton, Doc, null);
         // To create a multibutton menu add a CollectionLinearView
-        return !menuDoc ? null : (
-            <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}>
+        return !this.menuBtnDoc ? null : (
+            <div className="buttonMenu-docBtn" style={{ width: NumCast(this.menuBtnDoc._width, 30), height: NumCast(this.menuBtnDoc._height, 30) }}>
                 <DocumentView
-                    Document={menuDoc}
-                    TemplateDataDocument={menuDoc}
+                    Document={this.menuBtnDoc}
+                    TemplateDataDocument={this.menuBtnDoc}
                     isContentActive={this._props.isContentActive}
                     isDocumentActive={returnTrue}
                     addDocument={this._props.addDocument}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 8393c719b..a7603a45b 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
-import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
@@ -16,7 +16,6 @@ import { DocumentView } from '../../nodes/DocumentView';
 import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
 import './CollectionGridView.scss';
 import Grid, { Layout } from './Grid';
-import { DocData } from '../../../../fields/DocSymbols';
 
 @observer
 export class CollectionGridView extends CollectionSubView() {
@@ -183,17 +182,17 @@ export class CollectionGridView extends CollectionSubView() {
     isChildContentActive = () => (this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) ? true : undefined);
     /**
      *
-     * @param layout
+     * @param childLayout
      * @param dxf the x- and y-translations of the decorations box as a transform i.e. this.lookupIndividualTransform
      * @param width
      * @param height
      * @returns the `ContentFittingDocumentView` of the node
      */
-    getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => (
+    getDisplayDoc = (childLayout: Doc, dxf: () => Transform, width: () => number, height: () => number) => (
         <DocumentView
             {...this._props}
-            Document={layout}
-            TemplateDataDocument={DocCast(layout.rootDocument)?.[DocData]}
+            Document={childLayout}
+            TemplateDataDocument={childLayout.isTemplateDoc || childLayout.isTemplateForField ? this._props.TemplateDataDocument : undefined}
             NativeWidth={returnZero}
             NativeHeight={returnZero}
             fitWidth={this._props.childLayoutFitWidth}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index f27398427..42b093cc7 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -6,7 +6,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaChevronRight } from 'react-icons/fa';
 import { Doc, DocListCast } from '../../../../fields/Doc';
-import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { DragManager } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
@@ -18,7 +18,7 @@ import ResizeBar from './MulticolumnResizer';
 import WidthLabel from './MulticolumnWidthLabel';
 import { dropActionType } from '../../../util/DropActionTypes';
 import { SnappingManager } from '../../../util/SnappingManager';
-import { DocData } from '../../../../fields/DocSymbols';
+import { computedFn } from 'mobx-utils';
 
 interface WidthSpecifier {
     magnitude: number;
@@ -198,15 +198,14 @@ export class CollectionMulticolumnView extends CollectionSubView() {
      * documents before the target.
      */
     private lookupIndividualTransform = (layout: Doc) => {
-        if (this.columnUnitLength === undefined) {
-            return Transform.Identity(); // we're still waiting on promises to resolve
-        }
-        let offset = 0;
-        for (const { layout: candidate } of this.childLayoutPairs) {
-            if (candidate === layout) {
-                return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0);
+        if (this.columnUnitLength !== undefined) {
+            let offset = 0;
+            for (const { layout: candidate } of this.childLayoutPairs) {
+                if (candidate === layout) {
+                    return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0);
+                }
+                offset += this.lookupPixels(candidate) + resizerWidth;
             }
-            offset += this.lookupPixels(candidate) + resizerWidth;
         }
         return Transform.Identity();
     };
@@ -262,50 +261,51 @@ export class CollectionMulticolumnView extends CollectionSubView() {
               ? true
               : undefined;
     };
-    getDisplayDoc = (childLayout: Doc) => {
-        const width = () => this.lookupPixels(childLayout);
-        const height = () => this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
-        const dxf = () =>
-            this.lookupIndividualTransform(childLayout)
+    childHeight = () => this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
+    childWidth = computedFn((childDoc: Doc) => () => this.lookupPixels(childDoc));
+    childXf = computedFn(
+        (childDoc: Doc) => () =>
+            this.lookupIndividualTransform(childDoc)
                 .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
-                .scale(this._props.NativeDimScaling?.() || 1);
-        return (
-            <DocumentView
-                Document={childLayout}
-                TemplateDataDocument={DocCast(childLayout.rootDocument)?.[DocData]}
-                styleProvider={this._props.styleProvider}
-                containerViewPath={this.childContainerViewPath}
-                LayoutTemplate={this._props.childLayoutTemplate}
-                LayoutTemplateString={this._props.childLayoutString}
-                renderDepth={this._props.renderDepth + 1}
-                PanelWidth={width}
-                PanelHeight={height}
-                rootSelected={this.rootSelected}
-                dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
-                onClickScript={this.onChildClickHandler}
-                onDoubleClickScript={this.onChildDoubleClickHandler}
-                suppressSetHeight
-                ScreenToLocalTransform={dxf}
-                isContentActive={this.isChildContentActive}
-                isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
-                hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)}
-                hideDecorationTitle={this._props.childHideDecorationTitle}
-                fitContentsToBox={this._props.fitContentsToBox}
-                focus={this._props.focus}
-                childFilters={this.childDocFilters}
-                childFiltersByRanges={this.childDocRangeFilters}
-                searchFilterDocs={this.searchFilterDocs}
-                dontRegisterView={this._props.dontRegisterView}
-                addDocument={this._props.addDocument}
-                moveDocument={this._props.moveDocument}
-                removeDocument={this._props.removeDocument}
-                whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                addDocTab={this._props.addDocTab}
-                pinToPres={this._props.pinToPres}
-                dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'}
-            />
-        );
-    };
+                .scale(this._props.NativeDimScaling?.() || 1)
+    );
+    getDisplayDoc = (childLayout: Doc) => (
+        <DocumentView
+            Document={childLayout}
+            TemplateDataDocument={childLayout.isTemplateDoc || childLayout.isTemplateForField ? this._props.TemplateDataDocument : undefined}
+            styleProvider={this._props.styleProvider}
+            containerViewPath={this.childContainerViewPath}
+            LayoutTemplate={this._props.childLayoutTemplate}
+            LayoutTemplateString={this._props.childLayoutString}
+            renderDepth={this._props.renderDepth + 1}
+            PanelWidth={this.childWidth(childLayout)}
+            PanelHeight={this.childHeight}
+            rootSelected={this.rootSelected}
+            dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
+            onClickScript={this.onChildClickHandler}
+            onDoubleClickScript={this.onChildDoubleClickHandler}
+            suppressSetHeight
+            ScreenToLocalTransform={this.childXf(childLayout)}
+            isContentActive={this.isChildContentActive}
+            isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+            hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)}
+            hideDecorationTitle={this._props.childHideDecorationTitle}
+            fitContentsToBox={this._props.fitContentsToBox}
+            focus={this._props.focus}
+            childFilters={this.childDocFilters}
+            childFiltersByRanges={this.childDocRangeFilters}
+            searchFilterDocs={this.searchFilterDocs}
+            dontRegisterView={this._props.dontRegisterView}
+            addDocument={this._props.addDocument}
+            moveDocument={this._props.moveDocument}
+            removeDocument={this._props.removeDocument}
+            whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+            addDocTab={this._props.addDocTab}
+            pinToPres={this._props.pinToPres}
+            dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'}
+        />
+    );
+
     /**
      * @returns the resolved list of rendered child documents, displayed
      * at their resolved pixel widths, each separated by a resizer.
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index f95928cf6..6bbe55f54 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -2,7 +2,7 @@ import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../../fields/Doc';
-import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { DragManager } from '../../../util/DragManager';
 import { dropActionType } from '../../../util/DropActionTypes';
 import { Transform } from '../../../util/Transform';
@@ -11,7 +11,7 @@ import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'
 import './CollectionMultirowView.scss';
 import HeightLabel from './MultirowHeightLabel';
 import ResizeBar from './MultirowResizer';
-import { DocData } from '../../../../fields/DocSymbols';
+import { computedFn } from 'mobx-utils';
 
 interface HeightSpecifier {
     magnitude: number;
@@ -179,16 +179,14 @@ export class CollectionMultirowView extends CollectionSubView() {
      * documents before the target.
      */
     private lookupIndividualTransform = (layout: Doc) => {
-        if (this.rowUnitLength === undefined) {
-            return Transform.Identity(); // we're still waiting on promises to resolve
-        }
-        let offset = 0;
-        // eslint-disable-next-line no-restricted-syntax
-        for (const { layout: candidate } of this.childLayoutPairs) {
-            if (candidate === layout) {
-                return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
+        if (this.rowUnitLength !== undefined) {
+            let offset = 0;
+            for (const { layout: candidate } of this.childLayoutPairs) {
+                if (candidate === layout) {
+                    return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
+                }
+                offset += this.lookupPixels(candidate) + resizerHeight;
             }
-            offset += this.lookupPixels(candidate) + resizerHeight;
         }
         return Transform.Identity(); // type coersion, this case should never be hit
     };
@@ -244,49 +242,51 @@ export class CollectionMultirowView extends CollectionSubView() {
               ? true
               : undefined;
     };
-    getDisplayDoc = (layout: Doc) => {
-        const height = () => this.lookupPixels(layout);
-        const width = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
-        const dxf = () =>
-            this.lookupIndividualTransform(layout)
+    childHeight = computedFn((childDoc: Doc) => () => this.lookupPixels(childDoc));
+    childWidth = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
+    childXf = computedFn(
+        (childDoc: Doc) => () =>
+            this.lookupIndividualTransform(childDoc)
                 .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
-                .scale(this._props.NativeDimScaling?.() || 1);
-        return (
-            <DocumentView
-                Document={layout}
-                TemplateDataDocument={DocCast(layout.rootDocument)?.[DocData]}
-                styleProvider={this._props.styleProvider}
-                containerViewPath={this.childContainerViewPath}
-                LayoutTemplate={this._props.childLayoutTemplate}
-                LayoutTemplateString={this._props.childLayoutString}
-                renderDepth={this._props.renderDepth + 1}
-                PanelWidth={width}
-                PanelHeight={height}
-                rootSelected={this.rootSelected}
-                dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
-                onClickScript={this.onChildClickHandler}
-                onDoubleClickScript={this.onChildDoubleClickHandler}
-                ScreenToLocalTransform={dxf}
-                isContentActive={this.isChildContentActive}
-                isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
-                hideResizeHandles={!!(layout.layout_fitWidth || this._props.childHideResizeHandles)}
-                hideDecorationTitle={this._props.childHideDecorationTitle}
-                fitContentsToBox={this._props.fitContentsToBox}
-                focus={this._props.focus}
-                childFilters={this.childDocFilters}
-                childFiltersByRanges={this.childDocRangeFilters}
-                searchFilterDocs={this.searchFilterDocs}
-                dontRegisterView={this._props.dontRegisterView}
-                addDocument={this._props.addDocument}
-                moveDocument={this._props.moveDocument}
-                removeDocument={this._props.removeDocument}
-                whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                addDocTab={this._props.addDocTab}
-                pinToPres={this._props.pinToPres}
-                dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'y' | 'x' | 'xy'}
-            />
-        );
-    };
+                .scale(this._props.NativeDimScaling?.() || 1)
+    );
+
+    getDisplayDoc = (childLayout: Doc) => (
+        <DocumentView
+            Document={childLayout}
+            TemplateDataDocument={childLayout.isTemplateDoc || childLayout.isTemplateForField ? this._props.TemplateDataDocument : undefined}
+            styleProvider={this._props.styleProvider}
+            containerViewPath={this.childContainerViewPath}
+            LayoutTemplate={this._props.childLayoutTemplate}
+            LayoutTemplateString={this._props.childLayoutString}
+            renderDepth={this._props.renderDepth + 1}
+            PanelWidth={this.childWidth}
+            PanelHeight={this.childHeight(childLayout)}
+            rootSelected={this.rootSelected}
+            dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
+            onClickScript={this.onChildClickHandler}
+            onDoubleClickScript={this.onChildDoubleClickHandler}
+            ScreenToLocalTransform={this.childXf(childLayout)}
+            isContentActive={this.isChildContentActive}
+            isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+            hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)}
+            hideDecorationTitle={this._props.childHideDecorationTitle}
+            fitContentsToBox={this._props.fitContentsToBox}
+            focus={this._props.focus}
+            childFilters={this.childDocFilters}
+            childFiltersByRanges={this.childDocRangeFilters}
+            searchFilterDocs={this.searchFilterDocs}
+            dontRegisterView={this._props.dontRegisterView}
+            addDocument={this._props.addDocument}
+            moveDocument={this._props.moveDocument}
+            removeDocument={this._props.removeDocument}
+            whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+            addDocTab={this._props.addDocTab}
+            pinToPres={this._props.pinToPres}
+            dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'y' | 'x' | 'xy'}
+        />
+    );
+
     /**
      * @returns the resolved list of rendered child documents, displayed
      * at their resolved pixel widths, each separated by a resizer.
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index f60f33608..5ac66f2cd 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -277,6 +277,12 @@
     bottom: 0;
     pointer-events: none;
 }
+.documentView-widgetDecorations {
+    transform-origin: top right;
+    position: absolute;
+    top: 0;
+    right: 0;
+}
 
 .documentView-editorView-history {
     position: absolute;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b03dd3944..bec5ea5c1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -712,6 +712,54 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
 
     aiContentsWidth = () => (this.aiContentsHeight() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1);
     aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight * this.uiBtnScaling);
+    aiEditor = () => (
+        <>
+            <div
+                className="documentView-editorView-history"
+                ref={r => this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))}
+                style={{
+                    transform: `scale(${this.uiBtnScaling})`,
+                    height: this.aiContentsHeight() / this.uiBtnScaling,
+                    width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling,
+                }}>
+                {this._componentView?.componentAIViewHistory?.() ?? null}
+            </div>
+            <div
+                className="documentView-editorView"
+                style={{
+                    background: SnappingManager.userVariantColor,
+                    width: `${100 / this.uiBtnScaling}%`, //
+                    transform: `scale(${this.uiBtnScaling})`,
+                }}
+                ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
+                <div className="documentView-editorView-resizer" />
+                {this._componentView?.componentAIView?.() ?? null}
+                {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
+            </div>
+        </>
+    );
+    @computed get tagsOverlay() {
+        return (
+            <div
+                className="documentView-noAiWidgets"
+                style={{
+                    width: `${100 / this.uiBtnScaling}%`, //
+                    transform: `scale(${this.uiBtnScaling})`,
+                    height: Number.isNaN(this.maxWidgetSize) ? undefined : this.TagsBtnHeight * this.uiBtnScaling,
+                }}>
+                {this._props.DocumentView?.() && !this._props.docViewPath().slice(-2)[0].ComponentView?.isUnstyledView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
+            </div>
+        );
+    }
+    tagsOverlayFunc = () => (this._props.DocumentView?.().showTags ? this.tagsOverlay : null);
+    @computed get widgetOverlay() {
+        return (
+            <div className="documentView-widgetDecorations" style={{ transform: `scale(${this.uiBtnScaling})` }}>
+                {this.widgetDecorations}
+            </div>
+        );
+    }
+    widgetOverlayFunc = () => (this.widgetDecorations ? this.widgetOverlay : null);
     @computed get viewBoxContents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
@@ -742,43 +790,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                         hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)}
                     />
                 </div>
-                {!this._props.showAIEditor ? (
-                    <div
-                        className="documentView-noAiWidgets"
-                        style={{
-                            width: `${100 / this.uiBtnScaling}%`, //
-                            transform: `scale(${this.uiBtnScaling})`,
-                            height: Number.isNaN(this.maxWidgetSize) ? undefined : this.TagsBtnHeight * this.uiBtnScaling,
-                        }}>
-                        {this._props.DocumentView?.() && !this._props.docViewPath().slice(-2)[0].ComponentView?.isUnstyledView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
-                    </div>
-                ) : (
-                    <>
-                        <div
-                            className="documentView-editorView-history"
-                            ref={r => this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))}
-                            style={{
-                                transform: `scale(${this.uiBtnScaling})`,
-                                height: this.aiContentsHeight() / this.uiBtnScaling,
-                                width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling,
-                            }}>
-                            {this._componentView?.componentAIViewHistory?.() ?? null}
-                        </div>
-                        <div
-                            className="documentView-editorView"
-                            style={{
-                                background: SnappingManager.userVariantColor,
-                                width: `${100 / this.uiBtnScaling}%`, //
-                                transform: `scale(${this.uiBtnScaling})`,
-                            }}
-                            ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
-                            <div className="documentView-editorView-resizer" />
-                            {this._componentView?.componentAIView?.() ?? null}
-                            {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
-                        </div>
-                    </>
-                )}
-                <div style={{ transformOrigin: 'top right', transform: `scale(${this.uiBtnScaling})`, position: 'absolute', top: 0, right: 0 }}>{this.widgetDecorations ?? null}</div>
+                {this._props.showAIEditor ? this.aiEditor() : this.tagsOverlayFunc()}
+                {this.widgetOverlayFunc()}
             </>
         );
     }
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 74059bd12..ad6d93d43 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -50,7 +50,7 @@ export interface FieldViewSharedProps {
     TemplateDataDocument?: Doc;
     renderDepth: number;
     scriptContext?: unknown; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
-    screenXPadding?: () => number; // padding in screen space coordinates (used by text box to reflow around UI buttons in carouselView)
+    screenXPadding?: (view: DocumentView | undefined) => number; // padding in screen space coordinates (used by text box to reflow around UI buttons in carouselView)
     xPadding?: number;
     yPadding?: number;
     dontRegisterView?: boolean;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 6fced76bd..a45b8a488 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -215,7 +215,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     }
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
-        const rootDoc: Doc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document);
+        const rootDoc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : this.rootDoc;
         if (!pinProps && this.EditorView?.state.selection.empty) return rootDoc;
         const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc });
         this.addDocument(anchor);
@@ -308,7 +308,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     leafText = (node: Node) => {
         if (node.type === this.EditorView?.state.schema.nodes.dashField) {
-            const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
+            const refDoc = !node.attrs.docId ? this.rootDoc : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
             const fieldKey = StrCast(node.attrs.fieldKey);
             return (
                 (node.attrs.hideKey ? '' : fieldKey + ':') + //
@@ -316,7 +316,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             );
         }
         if (node.type === this.EditorView?.state.schema.nodes.dashDoc) {
-            const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
+            const refDoc = !node.attrs.docId ? this.rootDoc : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
             return refDoc[ToString]();
         }
         return '';
@@ -1522,8 +1522,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             this._editorView.TextView = this;
         }
 
-        const selectOnLoad =
-            Doc.AreProtosEqual(this._props.TemplateDataDocument ?? DocCast(this.Document.rootDocument, this.Document), DocumentView.SelectOnLoad) && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()));
+        const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.rootDoc, DocumentView.SelectOnLoad) && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()));
         const selLoadChar = FormattedTextBox.SelectOnLoadChar;
         if (selectOnLoad) {
             DocumentView.SetSelectOnLoad(undefined);
@@ -2102,11 +2101,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const rounded = StrCast(this.layoutDoc._layout_borderRounding) === '100%' ? '-rounded' : '';
         setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
 
-        const scrSize = (which: number, view = this._props.docViewPath().slice(-which)[0]) => 
-                [view?._props.PanelWidth() /(view?.screenToLocalScale()??1), view?._props.PanelHeight() / (view?.screenToLocalScale()??1)]; // prettier-ignore
-        const scrMargin = [Math.max(0, (scrSize(2)[0] - scrSize(1)[0]) / 2), Math.max(0, (scrSize(2)[1] - scrSize(1)[1]) / 2)];
-        const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), this._props.xPadding ?? 0, 0, ((this._props.screenXPadding?.() ?? 0) - scrMargin[0]) * this.ScreenToLocalBoxXf().Scale);
-        const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, ((this._props.yPadding ?? 0) - scrMargin[1]) * this.ScreenToLocalBoxXf().Scale);
+        const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), 0, this._props.xPadding ?? 0, this._props.screenXPadding?.(this._props.DocumentView?.()) ?? 0);
+        const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, this._props.yPadding ?? 0); // prettier-ignore
         const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._header_height}px' >
         return this.isLabel ? (
             <LabelBox {...this._props} />
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index eabc6455f..7ae1fc202 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -50,8 +50,7 @@ export function buildKeymap<S extends Schema<string>>(schema: S, tbox?: Formatte
 
     const canEdit = (state: EditorState) => {
         if (!tbox) return true;
-        const permissions = GetEffectiveAcl(tbox._props.TemplateDataDocument ?? tbox.Document[DocData]);
-        switch (permissions) {
+        switch (GetEffectiveAcl(tbox.dataDoc)) {
             case AclAugment:
                 {
                     // previously used hack: (state.selection as any).$cursor.nodeBefore;
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 31cd1603f..7e0375275 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -67,7 +67,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // Since this node is being rendered with a template, this method retrieves
     // the actual slide being rendered from the auto-generated rendering template
     @computed get slideDoc() {
-        return DocCast(this.Document.rootDocument, this.Document);
+        return this.rootDoc;
     }
 
     // this is the document in the workspaces that is targeted by the slide
-- 
cgit v1.2.3-70-g09d2


From eef391dba5ee9f1588274f8719eb4047fe844e22 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 10 Apr 2025 23:08:13 -0400
Subject: changed naming of makeDelegates to not add a number at the end.
 switched dataNote to MetaNote and cleaned up initialization of it.  fixed
 sidebar/stacking view to not grab wheel evebnts when not active.  fixed
 dashfieldview to vcenter labels.  fixed height and scrolling of templae menu.
 simplified formattedtextbox menu

---
 src/client/documents/DocUtils.ts                   |  2 +-
 src/client/documents/Documents.ts                  |  5 +-
 src/client/util/CurrentUserUtils.ts                | 83 +++++++++++-----------
 src/client/views/DocumentButtonBar.tsx             |  2 +-
 src/client/views/SidebarAnnos.tsx                  |  3 +-
 src/client/views/TemplateMenu.scss                 |  4 ++
 .../views/collections/CollectionStackingView.tsx   |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 70 +++++++++---------
 .../views/nodes/formattedText/DashFieldView.scss   |  1 +
 .../views/nodes/formattedText/FormattedTextBox.tsx | 61 ----------------
 .../views/nodes/formattedText/RichTextRules.ts     |  2 +-
 src/fields/Doc.ts                                  |  6 +-
 13 files changed, 96 insertions(+), 147 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index f5eee84aa..0d15a91e9 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -899,7 +899,7 @@ export namespace DocUtils {
         const ndoc = Doc.MakeDelegateWithProto(dragFactory);
         if (ndoc && dragFactory.dragFactory_count !== undefined) {
             dragFactory.dragFactory_count = NumCast(dragFactory.dragFactory_count) + 1;
-            Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory.dragFactory_count).toString();
+            Doc.GetProto(ndoc).title = ndoc.title;
         }
         return ndoc;
     }
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0e3c614fe..d93a432d4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -221,7 +221,6 @@ export class DocumentOptions {
     color?: STRt = new StrInfo('foreground color data doc', false);
     hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection', false);
     backgroundColor?: STRt = new StrInfo('background color for data doc', false);
-    _override_backgroundColor?: BOOLt = new BoolInfo("whether the layout template overrides the data doc's background color");
     opacity?: NUMt = new NumInfo('document opacity', false);
     viewTransitionTime?: NUMt = new NumInfo('transition duration for view parameters', false);
     dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc', false);
@@ -284,7 +283,7 @@ export class DocumentOptions {
     _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);
     _layout_fitWidth?: BOOLt = new BoolInfo('whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)');
-    _layout_fieldKey?: STRt = new StrInfo('the field key containing the current layout definition', false);
+    layout_fieldKey?: STRt = new StrInfo('the field key containing the current layout definition', false);
     _layout_enableAltContentUI?: BOOLt = new BoolInfo('whether to show alternate content button');
     _layout_flashcardType?: STRt = new StrInfo('flashcard style to render in ComparisonBox.  currently just "flashcard".');
     _layout_showTitle?: string; // field name to display in header (:hover is an optional suffix)
@@ -678,7 +677,7 @@ export namespace Docs {
             // whatever options pertain to this specific prototype
             const options: DocumentOptions = {
                 isSystem: true,
-                _layout_fieldKey: 'layout',
+                layout_fieldKey: 'layout',
                 title,
                 type,
                 isBaseProto: true,
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 4c492cae0..1c0f11c91 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -144,14 +144,16 @@ export class CurrentUserUtils {
     static setupNoteTemplates(doc: Doc, field="template_notes") {
         const tempNotes = DocCast(doc[field]);
         const reqdTempOpts:DocumentOptions[] = [
-            { title: "Postit", _override_backgroundColor: true, backgroundColor: "yellow",    icon: "sticky-note", _layout_showTitle: "title", layout_borderRounding: "5px"},
-            { title: "Idea",   _override_backgroundColor: true, backgroundColor: "pink",      icon: "lightbulb"  , _layout_showTitle: "title"},
-            { title: "Topic",  _override_backgroundColor: true, backgroundColor: "lightblue", icon: "book-open"  , _layout_showTitle: "title"}];
+            { title: "Postit", backgroundColor: "yellow",    icon: "sticky-note", _layout_showTitle: "title", layout_borderRounding: "5px"},
+            { title: "Idea",   backgroundColor: "pink",      icon: "lightbulb"  , _layout_showTitle: "title"},
+            { title: "Topic",  backgroundColor: "lightblue", icon: "book-open"  , _layout_showTitle: "title"}];
+
+        
         const reqdNoteList = [...reqdTempOpts.map(opts => {
             const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true};
             const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(fdoc => fdoc.title === opts.title): undefined;
             return DocUtils.AssignOpts(noteTemp, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts));
-        }), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))];
+        }), DocCast(doc.emptyMetaNote), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))];
 
         const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true };
         return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts));
@@ -247,7 +249,6 @@ export class CurrentUserUtils {
         title: string, toolTip: string, icon: string,  ignoreClick?: boolean, dragFactory?: Doc, 
         backgroundColor?: string, openFactoryAsDelegate?:boolean, openFactoryLocation?:string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string},
     }[] {
-        const standardOps = (key:string) => ({ title : "Untitled "+ key, _layout_fitWidth: false, isSystem: true, "dragFactory_count": 0, cloneFieldFilter: new List<string>(["isSystem"]) });
         const json = {
             doc: {
                 type: "doc",
@@ -272,17 +273,17 @@ export class CurrentUserUtils {
             // "    <FormattedTextBox {...props} fieldKey={'header'} dontSelectOnLoad={'true'} ignoreAutoHeight={'true'} pointerEvents='{this._header_pointerEvents||`none`}' fontSize='{this._header_fontSize}px' height='{this._header_height}px' background='{this._header_color}' />" +
             // "    <FormattedTextBox {...props} fieldKey={'text'} position='absolute' top='{this._header_height}px' height='calc(100% - {this._header_height}px)'/>" +
             // "</div>";
-        const headerBtnHgt = 10;
-        const headerTemplate = (opts:DocumentOptions) => 
-            MakeTemplate(Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "Header Template", 
+        const metadataBtnHght = 10;
+        const metaNoteTemplate = (opts:DocumentOptions) => 
+            MakeTemplate(Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "MetaNote", 
                 layout:`<HTMLdiv transformOrigin='top left' width='100%' height='100%'>
-                            <FormattedTextBox {...props} dontScale='true' fieldKey={'text'} height='calc(100% - ${headerBtnHgt}px - {this._header_height||0}px)'  />
+                            <FormattedTextBox {...props} dontScale='true' fieldKey={'text'} height='calc(100% - ${metadataBtnHght}px - {this._header_height||0}px)'  />
                             <FormattedTextBox {...props} dontScale='true' fieldKey={'header'} dontSelectOnLoad='true' ignoreAutoHeight='true' fontSize='{this._header_fontSize||9}px' height='{(this._header_height||0)}px' backgroundColor='{this._header_color || "lightGray"}' />
-                            <HTMLdiv fontSize='${headerBtnHgt - 1}px' height='${headerBtnHgt}px' backgroundColor='yellow'
-                                   onClick={‘(this._header_height=(this._header_height===0?50:0)) + (this._layout_autoHeightMargins=this._header_height ? this._header_height+${headerBtnHgt}:0)’} > Metadata
+                            <HTMLdiv fontSize='${metadataBtnHght - 1}px' height='${metadataBtnHght}px' backgroundColor='yellow'
+                                   onClick={‘(this._header_height=(this._header_height===0?50:0)) + (this._layout_autoHeightMargins=this._header_height ? this._header_height+${metadataBtnHght}:0)’} > Metadata
                                    </HTMLdiv>
                         </HTMLdiv>`
-                    }, "header"));
+                    }, "metaNote"));
         const slideView = (opts:DocumentOptions) => 
             MakeTemplate(Docs.Create.MultirowDocument(
                     [
@@ -405,9 +406,9 @@ pie title Minerals in my tap water
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("", opts),     opts: { _width: 300, _height: 300, }},
             // AARAV ADD //
-            {key: "DailyJournal",creator:opts => Docs.Create.DailyJournalDocument("", opts),opts:{ _width: 300, _height: 300}},
+            {key: "DailyJournal",creator:opts => Docs.Create.DailyJournalDocument("", opts),opts: { _width: 300, _height: 300}},
             {key: "Chat",       creator: Docs.Create.ChatDocument,                          opts: { _width: 500, _height: 500, _layout_fitWidth: true, }},
-            {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}},
+            {key: "MetaNote",   creator: metaNoteTemplate,                                  opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _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, _layout_dontCenter:'xy', 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, }},
@@ -420,40 +421,42 @@ pie title Minerals in my tap water
             {key: "Plotly",     creator: plotlyView,                                        opts: { _width: 300, _height: 300, }},
                                                                                         ];
 
+        const standardOps = (key:string) => ({ title : "Untitled "+ key, _layout_fitWidth: false, isSystem: true, "dragFactory_count": 0, cloneFieldFilter: new List<string>(["isSystem"]) });
         emptyThings.forEach(
             thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs));
-        
+        DocCast(Doc.UserDoc().emptyMetaNote).title = "MetaNote"; // hack: metanotes are used a template, so 'untitled metaNote' is an awkward name 
+
         return [
-            { toolTip: "Tap or drag to create a note",            title: "Note", icon: "sticky-note",   dragFactory: doc.emptyNote as Doc,       clickFactory: DocCast(doc.emptyNote)},
-            { toolTip: "Tap or drag to create a flashcard",       title: "Flashcard", icon: "id-card",  dragFactory: doc.emptyFlashcard as Doc,  clickFactory: DocCast(doc.emptyFlashcard)},
-            { toolTip: "Tap or drag to create an equation",       title: "Math", icon: "calculator",    dragFactory: doc.emptyEquation as Doc,   clickFactory: DocCast(doc.emptyEquation)},
-            { toolTip: "Tap or drag to create a mermaid node",    title: "Mermaids", icon: "rocket",    dragFactory: doc.emptyMermaids as Doc,   clickFactory: DocCast(doc.emptyMermaids)}, 
-            { toolTip: "Tap or drag to create a plotly node",     title: "Plotly", icon: "rocket",      dragFactory: doc.emptyPlotly as Doc,     clickFactory: DocCast(doc.emptyMermaids)}, 
-            { toolTip: "Tap or drag to create a note board",      title: "Notes", icon: "book",         dragFactory: doc.emptyNoteboard as Doc,  clickFactory: DocCast(doc.emptyNoteboard)},
-            { toolTip: "Tap or drag to create an image",          title: "Image", icon: "image",        dragFactory: doc.emptyImage as Doc,      clickFactory: DocCast(doc.emptyImage)},
-            { toolTip: "Tap or drag to create a collection",      title: "Col", icon: "folder",         dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)},
-            { toolTip: "Tap or drag to create a webpage",         title: "Web", icon: "globe-asia",     dragFactory: doc.emptyWebpage as Doc,    clickFactory: DocCast(doc.emptyWebpage)},
-            { toolTip: "Tap or drag to create a comparison box",  title: "Compare", icon: "columns",    dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)},
-            { toolTip: "Tap or drag to create a diagram",         title: "Diagram", icon: "tree",       dragFactory: doc.emptyDiagram as Doc,    clickFactory: DocCast(doc.emptyDiagram)},
-            { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone",   dragFactory: doc.emptyAudio as Doc,      clickFactory: DocCast(doc.emptyAudio),      openFactoryLocation: OpenWhere.overlay},
-            { toolTip: "Tap or drag to create a map",             title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc,        clickFactory: DocCast(doc.emptyMap)},
-            { toolTip: "Tap or drag to create a chat assistant",  title: "Assistant Chat", icon: "book",dragFactory: doc.emptyChat as Doc,       clickFactory: DocCast(doc.emptyChat)},
-            { toolTip: "Tap or drag to create a screen grabber",  title: "Grab", icon: "photo-video",   dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
-            { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc,     clickFactory: DocCast(doc.emptyWebCam),     openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
-            { toolTip: "Tap or drag to create a button",          title: "Button", icon: "circle",      dragFactory: doc.emptyButton as Doc,     clickFactory: DocCast(doc.emptyButton)},
-            { 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 journal entry",   title: "Journal", icon: "book",       dragFactory: doc.emptyDailyJournal as Doc,clickFactory:DocCast(doc.emptyDataJournal), },
-            { toolTip: "Tap or drag to create a bullet slide",    title: "PPT Slide", icon: "person-chalkboard",   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 unknown as Doc, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
+            { toolTip: "Tap or drag to create a note",            title: "Note",        icon: "sticky-note",     dragFactory: doc.emptyNote as Doc,       clickFactory: DocCast(doc.emptyNote)},
+            { toolTip: "Tap or drag to create a flashcard",       title: "Flashcard",   icon: "id-card",         dragFactory: doc.emptyFlashcard as Doc,  clickFactory: DocCast(doc.emptyFlashcard)},
+            { toolTip: "Tap or drag to create an equation",       title: "Math",        icon: "calculator",      dragFactory: doc.emptyEquation as Doc,   clickFactory: DocCast(doc.emptyEquation)},
+            { toolTip: "Tap or drag to create a mermaid node",    title: "Mermaids",    icon: "rocket",          dragFactory: doc.emptyMermaids as Doc,   clickFactory: DocCast(doc.emptyMermaids)}, 
+            { toolTip: "Tap or drag to create a plotly node",     title: "Plotly",      icon: "rocket",          dragFactory: doc.emptyPlotly as Doc,     clickFactory: DocCast(doc.emptyMermaids)}, 
+            { toolTip: "Tap or drag to create a note board",      title: "Notes",       icon: "book",            dragFactory: doc.emptyNoteboard as Doc,  clickFactory: DocCast(doc.emptyNoteboard)},
+            { toolTip: "Tap or drag to create an image",          title: "Image",       icon: "image",           dragFactory: doc.emptyImage as Doc,      clickFactory: DocCast(doc.emptyImage)},
+            { toolTip: "Tap or drag to create a collection",      title: "Col",         icon: "folder",          dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)},
+            { toolTip: "Tap or drag to create a webpage",         title: "Web",         icon: "globe-asia",      dragFactory: doc.emptyWebpage as Doc,    clickFactory: DocCast(doc.emptyWebpage)},
+            { toolTip: "Tap or drag to create a comparison box",  title: "Compare",     icon: "columns",         dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)},
+            { toolTip: "Tap or drag to create a diagram",         title: "Diagram",     icon: "tree",            dragFactory: doc.emptyDiagram as Doc,    clickFactory: DocCast(doc.emptyDiagram)},
+            { toolTip: "Tap or drag to create an audio recorder", title: "Audio",       icon: "microphone",      dragFactory: doc.emptyAudio as Doc,      clickFactory: DocCast(doc.emptyAudio),      openFactoryLocation: OpenWhere.overlay},
+            { toolTip: "Tap or drag to create a map",             title: "Map",         icon: "map-marker-alt",  dragFactory: doc.emptyMap as Doc,        clickFactory: DocCast(doc.emptyMap)},
+            { toolTip: "Tap or drag to create a chat assistant",  title: "Chat Assist", icon: "book",            dragFactory: doc.emptyChat as Doc,       clickFactory: DocCast(doc.emptyChat)},
+            { toolTip: "Tap or drag to create a screen grabber",  title: "Grab",        icon: "photo-video",     dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+            { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam",      icon: "photo-video",     dragFactory: doc.emptyWebCam as Doc,     clickFactory: DocCast(doc.emptyWebCam),     openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+            { toolTip: "Tap or drag to create a button",          title: "Button",      icon: "circle",          dragFactory: doc.emptyButton as Doc,     clickFactory: DocCast(doc.emptyButton)},
+            { 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 journal entry",   title: "Journal",     icon: "book",            dragFactory:doc.emptyDailyJournal as Doc,clickFactory: DocCast(doc.emptyDataJournal), },
+            { toolTip: "Tap or drag to create a bullet slide",    title: "PPT Slide",   icon:"person-chalkboard",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: "MetaNote",    icon: "window-maximize", dragFactory: doc.emptyMetaNote as Doc,   clickFactory: DocCast(doc.emptyMetaNote),  openFactoryAsDelegate: true,            funcs: { hidden: "IsNoviceMode()"} },
+            { toolTip: "Toggle a Calculator REPL",                title: "replviewer",  icon: "calculator",                                               clickFactory: '<ScriptingRepl />' as unknown as Doc, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
           ].map(tuple => (
               { openFactoryLocation: OpenWhere.addRight, 
                 scripts: { onClick:     'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)', 
                            onDragStart: '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }'}, 
                 funcs: tuple.funcs,
-                ...tuple, }))
+                ...tuple, }));
     }
 
     /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 9a57a5e6c..6f2f051cd 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -512,7 +512,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 <div className="documentButtonBar-button">{this.pinButton}</div>
                 <div className="documentButtonBar-button">{this.recordButton}</div>
                 <div className="documentButtonBar-button">{this.calendarButton}</div>
-                <div className="documentButtonBar-button">{this.aiEditorButton}</div>
+                {this.view0?.HasAIEditor ? <div className="documentButtonBar-button">{this.aiEditorButton}</div> : null}
                 <div className="documentButtonBar-button">{this.keywordButton}</div>
                 {!Doc.UserDoc().documentLinksButton_fullMenu ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
                 <div className="documentButtonBar-button">{this.menuButton}</div>
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index b7e6318b1..573c28ccf 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -1,7 +1,7 @@
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { ClientUtils, returnAll, returnFalse, returnOne, returnZero } from '../../ClientUtils';
+import { ClientUtils, returnFalse, returnOne, returnZero } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
 import { Doc, DocListCast, Field, FieldResult, FieldType, StrListCast } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
@@ -253,7 +253,6 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
                         renderDepth={this._props.renderDepth + 1}
                         type_collection={CollectionViewType.Stacking}
                         fieldKey={this.sidebarKey}
-                        pointerEvents={returnAll}
                     />
                 </div>
             </div>
diff --git a/src/client/views/TemplateMenu.scss b/src/client/views/TemplateMenu.scss
index 8879fc20d..049945517 100644
--- a/src/client/views/TemplateMenu.scss
+++ b/src/client/views/TemplateMenu.scss
@@ -40,12 +40,16 @@
     height: 100%;
     width: 100%;
     max-height: 250px;
+    overflow: auto;
 
     .templateToggle,
     .chromeToggle {
         text-align: left;
         color: black;
     }
+    .collectionTreeView {
+        height: fit-content !important;
+    }
 
     input {
         margin-right: 10px;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 4f60acb18..112510265 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -720,7 +720,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         return this._props.isContentActive() === false ? 'none' : undefined;
     }
 
-    onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
+    onPassiveWheel = (e: WheelEvent) => this._props.isContentActive() && e.stopPropagation();
     render() {
         TraceMobx();
         const editableViewProps = {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 962fbdcd7..3497a62cb 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -467,7 +467,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
 
         const scale = this._props.NativeDimScaling?.() || 1;
         return (
-            <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
+            <div className="collectionTreeView" style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
                 {!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? (
                     <CollectionFreeFormView
                         {...this._props}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bec5ea5c1..cbd92c159 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -523,7 +523,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                 appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => DocumentView.SetLightboxDoc(this.Document), icon: 'external-link-alt' });
             }
             appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'map-pin' });
-            appearanceItems.push({ description: 'AI view', event: () => this._docView?.toggleAIEditor(), icon: 'map-pin' });
+            this._componentView?.componentAIView?.() && appearanceItems.push({ description: 'AI view', event: () => this._docView?.toggleAIEditor(), icon: 'map-pin' });
 
             !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' });
@@ -660,7 +660,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
         this._props
             .ScreenToLocalTransform()
             .translate(-NumCast(this.Document.borderWidth), -this.headerMargin - NumCast(this.Document.borderWidth))
-            .scale(this._props.showAIEditor ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1);
+            .scale(this.viewingAiEditor() ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1);
     onClickFunc = () => (this.disableClickScriptFunc ? undefined : this.onClickHdlr);
     setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height + 2 * NumCast(this.Document.borderWidth))); } // prettier-ignore
     setContentView = action((view: ViewBoxInterface<FieldViewProps>) => { this._componentView = view; }); // prettier-ignore
@@ -712,32 +712,34 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
 
     aiContentsWidth = () => (this.aiContentsHeight() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1);
     aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight * this.uiBtnScaling);
-    aiEditor = () => (
-        <>
-            <div
-                className="documentView-editorView-history"
-                ref={r => this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))}
-                style={{
-                    transform: `scale(${this.uiBtnScaling})`,
-                    height: this.aiContentsHeight() / this.uiBtnScaling,
-                    width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling,
-                }}>
-                {this._componentView?.componentAIViewHistory?.() ?? null}
-            </div>
-            <div
-                className="documentView-editorView"
-                style={{
-                    background: SnappingManager.userVariantColor,
-                    width: `${100 / this.uiBtnScaling}%`, //
-                    transform: `scale(${this.uiBtnScaling})`,
-                }}
-                ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
-                <div className="documentView-editorView-resizer" />
-                {this._componentView?.componentAIView?.() ?? null}
-                {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
-            </div>
-        </>
-    );
+    @computed get aiEditor() {
+        return (
+            <>
+                <div
+                    className="documentView-editorView-history"
+                    ref={r => this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))}
+                    style={{
+                        transform: `scale(${this.uiBtnScaling})`,
+                        height: this.aiContentsHeight() / this.uiBtnScaling,
+                        width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling,
+                    }}>
+                    {this._componentView?.componentAIViewHistory?.() ?? null}
+                </div>
+                <div
+                    className="documentView-editorView"
+                    style={{
+                        background: SnappingManager.userVariantColor,
+                        width: `${100 / this.uiBtnScaling}%`, //
+                        transform: `scale(${this.uiBtnScaling})`,
+                    }}
+                    ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
+                    <div className="documentView-editorView-resizer" />
+                    {this._componentView?.componentAIView?.() ?? null}
+                    {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
+                </div>
+            </>
+        );
+    }
     @computed get tagsOverlay() {
         return (
             <div
@@ -760,6 +762,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
         );
     }
     widgetOverlayFunc = () => (this.widgetDecorations ? this.widgetOverlay : null);
+    viewingAiEditor = () => (this._props.showAIEditor && this._componentView?.componentAIView?.() !== undefined ? this.aiEditor : null);
     @computed get viewBoxContents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
@@ -770,8 +773,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                     className="documentView-contentsView"
                     style={{
                         pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
-                        width: this._props.showAIEditor ? this.aiContentsWidth() : undefined,
-                        height: this._props.showAIEditor ? this.aiContentsHeight() : this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
+                        width: this.viewingAiEditor() ? this.aiContentsWidth() : undefined,
+                        height: this.viewingAiEditor() ? this.aiContentsHeight() : this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
                     }}>
                     <DocumentContentsView
                         {...this._props}
@@ -779,8 +782,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                         pointerEvents={this.contentPointerEvents}
                         setContentViewBox={this.setContentView}
                         childFilters={this.childFilters}
-                        PanelWidth={this._props.showAIEditor ? this.aiContentsWidth : this._props.PanelWidth}
-                        PanelHeight={this._props.showAIEditor ? this.aiContentsHeight : this.panelHeight}
+                        PanelWidth={this.viewingAiEditor() ? this.aiContentsWidth : this._props.PanelWidth}
+                        PanelHeight={this.viewingAiEditor() ? this.aiContentsHeight : this.panelHeight}
                         setHeight={this.setHeight}
                         isContentActive={this.isContentActive}
                         ScreenToLocalTransform={this.screenToLocalContent}
@@ -790,7 +793,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                         hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)}
                     />
                 </div>
-                {this._props.showAIEditor ? this.aiEditor() : this.tagsOverlayFunc()}
+                {this.viewingAiEditor() ?? this.tagsOverlayFunc()}
                 {this.widgetOverlayFunc()}
             </>
         );
@@ -1256,6 +1259,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     public get allLinks()       { return this._docViewInternal?._allLinks ?? []; } // prettier-ignore
     public get TagBtnHeight()   { return this._docViewInternal?.TagsBtnHeight; } // prettier-ignore
     public get UIBtnScaling()   { return this._docViewInternal?.uiBtnScaling; } // prettier-ignore
+    public get HasAIEditor()    { return !!this._docViewInternal?._componentView?.componentAIView?.(); } // prettier-ignore
 
     get LayoutFieldKey() {
         return Doc.LayoutFieldKey(this.Document, this._props.LayoutTemplateString);
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 6db8abc7f..3734ad9cc 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -26,6 +26,7 @@
         display: inline-block;
         font-weight: normal;
         background: rgba(0, 0, 0, 0.1);
+        align-content: center;
         cursor: default;
     }
     .dashFieldView-fieldSpan {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bef999e6d..fbe4fd294 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,5 +1,4 @@
 /* eslint-disable no-use-before-define */
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { Property } from 'csstype';
@@ -21,7 +20,6 @@ import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, U
 import { Id, ToString } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
-import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { ComputedField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types';
@@ -35,7 +33,6 @@ import { DocUtils } from '../../../documents/DocUtils';
 import { DictationManager } from '../../../util/DictationManager';
 import { DragManager } from '../../../util/DragManager';
 import { dropActionType } from '../../../util/DropActionTypes';
-import { MakeTemplate } from '../../../util/DropConverter';
 import { LinkManager } from '../../../util/LinkManager';
 import { RTFMarkup } from '../../../util/RTFMarkup';
 import { SnappingManager } from '../../../util/SnappingManager';
@@ -855,42 +852,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             return;
         }
 
-        const changeItems: ContextMenuProps[] = [];
-        changeItems.push({
-            description: 'plain',
-            event: undoable(() => {
-                Doc.setNativeView(this.Document);
-                this.layoutDoc.layout_autoHeightMargins = undefined;
-            }, 'set plain view'),
-            icon: 'eye',
-        });
-        changeItems.push({
-            description: 'metadata',
-            event: undoable(() => {
-                this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout;
-                this.Document.layout_fieldKey = 'layout_meta';
-                setTimeout(() => {
-                    this.layoutDoc._header_height = this.layoutDoc._layout_autoHeightMargins = 50;
-                }, 50);
-            }, 'set metadata view'),
-            icon: 'eye',
-        });
-        const noteTypesDoc = Cast(Doc.UserDoc().template_notes, Doc, null);
-        DocListCast(noteTypesDoc?.data).forEach(note => {
-            const icon: IconProp = StrCast(note.icon) as IconProp;
-            changeItems.push({
-                description: StrCast(note.title),
-                event: undoable(
-                    () => {
-                        this.layoutDoc.layout_autoHeightMargins = undefined;
-                        Doc.setNativeView(this.Document);
-                        DocUtils.makeCustomViewClicked(this.Document, Docs.Create.TreeDocument, StrCast(note.title), note);
-                    },
-                    `set ${StrCast(note.title)} view}`
-                ),
-                icon: icon,
-            });
-        });
         const highlighting: ContextMenuProps[] = [];
         const noviceHighlighting = ['Audio Tags', 'My Text', 'Text from Others', 'Bold Text'];
         const expertHighlighting = [...noviceHighlighting, 'Important Items', 'Ignore Items', 'Disagree Items', 'By Recent Minute', 'By Recent Hour'];
@@ -952,20 +913,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 icon: 'expand-arrows-alt',
             });
 
-        appearanceItems.push({ description: 'Change Style...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' });
-
-        !Doc.noviceMode &&
-            appearanceItems.push({
-                description: 'Make Default Layout',
-                event: () => {
-                    if (!this.layoutDoc.isTemplateDoc) {
-                        MakeTemplate(this.Document);
-                    }
-                    Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.Document);
-                    Doc.AddDocToList(Cast(Doc.UserDoc().template_notes, Doc, null), 'data', this.Document);
-                },
-                icon: 'eye',
-            });
         !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
 
         const options = cm.findByDescription('Options...');
@@ -988,14 +935,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 },
                 icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars',
             });
-        !Doc.noviceMode &&
-            optionItems.push({
-                description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`,
-                event: () => {
-                    this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight;
-                },
-                icon: this.Document._layout_autoHeight ? 'lock' : 'unlock',
-            });
         optionItems.push({
             description: this.Document.savedAsSticker ? 'Sticker Saved!' : 'Save to Stickers',
             event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')),
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 77c00537b..04315e886 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -113,7 +113,7 @@ export class RichTextRules {
                 const inlineFieldKey = '$inline' + numInlines; // which field on the text document this annotation will write to
                 const inlineLayoutKey = '$layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation
                 const textDocInline = Docs.Create.TextDocument('', {
-                    _layout_fieldKey: inlineLayoutKey,
+                    layout_fieldKey: inlineLayoutKey,
                     _width: 75,
                     _height: 35,
                     annotationOn: this.Document[DocData],
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e48337651..2db8d3b85 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -239,7 +239,7 @@ export class Doc extends RefField {
     public static get MyPublishedDocs()       { return DocListCast(Doc.ActiveDashboard?.myPublishedDocs).concat(DocListCast(DocCast(Doc.UserDoc().myPublishedDocs)?.data)); } // prettier-ignore
     public static get MyDashboards()          { return DocCast(Doc.UserDoc().myDashboards); } // prettier-ignore
     public static get MyTemplates()           { return DocCast(Doc.UserDoc().myTemplates); } // prettier-ignore
-    public static get MyStickers()               { return DocCast(Doc.UserDoc().myStickers); } // prettier-ignore
+    public static get MyStickers()            { return DocCast(Doc.UserDoc().myStickers); } // prettier-ignore
     public static get MyLightboxDrawings()    { return DocCast(Doc.UserDoc().myLightboxDrawings); } // prettier-ignore
     public static get MyImports()             { return DocCast(Doc.UserDoc().myImports); } // prettier-ignore
     public static get MyFilesystem()          { return DocCast(Doc.UserDoc().myFilesystem); } // prettier-ignore
@@ -1062,14 +1062,14 @@ export namespace Doc {
         return ndoc;
     }
 
-    let _applyCount: number = 0;
     export function ApplyTemplate(templateDoc: Doc) {
         if (templateDoc) {
             const proto = new Doc();
+            const applyCount = NumCast(templateDoc.dragFactory_count);
             proto.author = ClientUtils.CurrentUserEmail();
             const target = Doc.MakeDelegate(proto);
             const targetKey = StrCast(templateDoc.layout_fieldKey, 'layout');
-            const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')');
+            const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + applyCount + ')');
             target.layout_fieldKey = targetKey; //this and line above
             applied && (Doc.GetProto(applied).type = templateDoc.type);
             return applied;
-- 
cgit v1.2.3-70-g09d2


From 1525fe600142d955fa24e939322f45cbca9d1cba Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 11 Apr 2025 12:53:15 -0400
Subject: fixed ViewGuid generation to never start with a number (enables text
 boxes to have css specific to a single Doc).  cleaned up '%' style rules for
 text boxes.  cleaned up custom style sheets in text boxes to only be created
 when needed and to improve highlighting bold text with context.

---
 src/ClientUtils.ts                                 |   5 +-
 src/client/util/CaptureManager.tsx                 |   2 +-
 src/client/util/SettingsManager.tsx                |   2 +-
 .../views/collections/CollectionDockingView.tsx    |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/WebBox.tsx                  |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 119 ++++++++++-----------
 .../views/nodes/formattedText/RichTextRules.ts     |  33 ++----
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 9 files changed, 75 insertions(+), 94 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index e1f490c1a..03ff13924 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -476,7 +476,10 @@ export function smoothScrollHorizontal(duration: number, element: HTMLElement |
 export function addStyleSheet() {
     const style = document.createElement('style');
     const sheets = document.head.appendChild(style);
-    return sheets.sheet;
+    return sheets;
+}
+export function removeStyleSheet(sheet?: HTMLStyleElement) {
+    sheet && document.head.removeChild(sheet);
 }
 export function addStyleSheetRule(sheet: CSSStyleSheet | null, selector: string, css: string | { [key: string]: string }, selectorPrefix = '.') {
     const propText =
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 47f31612f..80af78898 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -13,7 +13,7 @@ import './CaptureManager.scss';
 export class CaptureManager extends React.Component<object> {
     // eslint-disable-next-line no-use-before-define
     public static Instance: CaptureManager;
-    static _settingsStyle = addStyleSheet();
+    static _settingsStyle = addStyleSheet().sheet;
     @observable _document: Opt<Doc> = undefined;
     @observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not.
 
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 89e3686b2..88f1f3260 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -30,7 +30,7 @@ export enum ColorScheme {
 export class SettingsManager extends React.Component<object> {
     // eslint-disable-next-line no-use-before-define
     public static Instance: SettingsManager;
-    static _settingsStyle = addStyleSheet();
+    static _settingsStyle = addStyleSheet().sheet;
     @observable private _passwordResultText = '';
     @observable private _playgroundMode = false;
 
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 12a131deb..094e61db9 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -60,7 +60,7 @@ export class CollectionDockingView extends CollectionSubView() {
     public get HasFullScreen() {
         return this._goldenLayout._maximisedItem !== null;
     }
-    static _highlightStyleSheet = addStyleSheet();
+    static _highlightStyleSheet = addStyleSheet().sheet;
 
     constructor(props: SubCollectionViewProps) {
         super(props);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cbd92c159..d88d8c44d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1161,7 +1161,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     public static UniquifyId(inLightbox: boolean | undefined, id: string) {
         return (inLightbox ? 'lightbox-' : '') + id;
     }
-    public ViewGuid = DocumentView.UniquifyId(DocumentView.LightboxContains(this), Utils.GenerateGuid()); // a unique id associated with the main <div>. used by LinkBox's Xanchor to find the arrowhead locations.
+    public ViewGuid = DocumentView.UniquifyId(DocumentView.LightboxContains(this), 'D' + Utils.GenerateGuid()); // a unique id associated with the main <div>. used by LinkBox's Xanchor to find the arrowhead locations.
     public DocUniqueId = DocumentView.UniquifyId(DocumentView.LightboxContains(this), this.Document[Id]);
 
     constructor(props: DocumentViewProps) {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 4b3f96bcf..bad4f5b13 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -54,7 +54,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
     public static openSidebarWidth = 250;
     public static sidebarResizerWidth = 5;
-    static webStyleSheet = addStyleSheet();
+    static webStyleSheet = addStyleSheet().sheet;
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fbe4fd294..cfb6dfa12 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transacti
 import { EditorView, NodeViewConstructor } from 'prosemirror-view';
 import * as React from 'react';
 import { BsMarkdownFill } from 'react-icons/bs';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, removeStyleSheet, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
 import { DateField } from '../../../../fields/DateField';
 import { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
@@ -101,12 +101,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     public static LiveTextUndo: UndoManager.Batch | undefined; // undo batch when typing a new text note into a collection
 
     private static _nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor };
-    private static _globalHighlightsCache: string = '';
-    private static _globalHighlights = new ObservableSet<string>(['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']);
-    private static _highlightStyleSheet = addStyleSheet();
-    private static _bulletStyleSheet = addStyleSheet();
-    private static _userStyleSheet = addStyleSheet();
+    private _curHighlights = new ObservableSet<string>(['Audio Tags']);
+    private static _highlightStyleSheet = addStyleSheet().sheet;
+    private static _bulletStyleSheet = addStyleSheet().sheet;
+    private _userStyleSheetElement: HTMLStyleElement | undefined;
 
+    private _enteringStyle = false;
     private _oldWheel: HTMLDivElement | null = null;
     private _selectionHTML: string | undefined;
     private _sidebarRef = React.createRef<SidebarAnnos>();
@@ -696,45 +696,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     }
 
     updateHighlights = (highlights: string[]) => {
-        if (Array.from(highlights).join('') === FormattedTextBox._globalHighlightsCache) return;
-        setTimeout(() => {
-            FormattedTextBox._globalHighlightsCache = Array.from(highlights).join('');
-        });
-        clearStyleSheetRules(FormattedTextBox._userStyleSheet);
-        if (!highlights.includes('Audio Tags')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, '');
-        }
-        if (highlights.includes('Text from Others')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' });
-        }
-        if (highlights.includes('My Text')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace(/\./g, '').replace(/@/g, ''), { background: 'moccasin' });
-        }
-        if (highlights.includes('Todo Items')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-todo', { outline: 'black solid 1px' });
-        }
-        if (highlights.includes('Important Items')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-important', { 'font-size': 'larger' });
-        }
-        if (highlights.includes('Bold Text')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner .ProseMirror strong > span', { 'font-size': 'large' }, '');
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner .ProseMirror :not(strong > span)', { 'font-size': '0px' }, '');
-        }
-        if (highlights.includes('Disagree Items')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-disagree', { 'text-decoration': 'line-through' });
-        }
-        if (highlights.includes('Ignore Items')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-ignore', { 'font-size': '1' });
-        }
+        const userStyleSheet = () => {
+            if (!this._userStyleSheetElement) {
+                this._userStyleSheetElement = addStyleSheet();
+            }
+            return this._userStyleSheetElement.sheet;
+        };
+        const viewId = this.DocumentView?.().ViewGuid ?? 1;
+        const userId = ClientUtils.CurrentUserEmail().replace(/\./g, '').replace('@', ''); // must match marks_rts -> user_mark's uid
+        highlights.filter(f => f !== 'Audio Tags').length && clearStyleSheetRules(userStyleSheet());
+        if (!highlights.includes('Audio Tags'))      addStyleSheetRule(userStyleSheet(), `#${viewId} .audiotag`,     { display:           'none' }, ''); // prettier-ignore
+        if (highlights.includes('Text from Others')) addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-remote`,    { background:        'yellow' }, ''); // prettier-ignore
+        if (highlights.includes('My Text'))          addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-${userId}`, { background:        'moccasin' }, ''); // prettier-ignore
+        if (highlights.includes('Todo Items'))       addStyleSheetRule(userStyleSheet(), `#${viewId} .UT-todo`,      { outline:           'black solid 1px' }, ''); // prettier-ignore
+        if (highlights.includes('Important Items'))  addStyleSheetRule(userStyleSheet(), `#${viewId} .UT-important`, { 'font-size':       'larger' }, ''); // prettier-ignore
+        if (highlights.includes('Disagree Items'))   addStyleSheetRule(userStyleSheet(), `#${viewId} .UT-disagree`,  { 'text-decoration': 'line-through' }, ''); // prettier-ignore
+        if (highlights.includes('Ignore Items'))     addStyleSheetRule(userStyleSheet(), `#${viewId} .UT-ignore`,    { 'font-size':       '1' }, ''); // prettier-ignore
+        if (highlights.includes('Bold Text')) {      addStyleSheetRule(userStyleSheet(), `#${viewId} .formattedTextBox-inner .ProseMirror p:not(:has(strong))`,         { 'font-size': '0px' }, '');
+                                                     addStyleSheetRule(userStyleSheet(), `#${viewId} .formattedTextBox-inner .ProseMirror p:not(:has(strong)) ::after`, { content: '...', 'font-size': '5px' }, '')} // prettier-ignore
         if (highlights.includes('By Recent Minute')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' });
+            addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-${userId}`, { opacity: '0.1' }, '');
             const min = Math.round(Date.now() / 1000 / 60);
-            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
+            numberRange(10).map(i => addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-min-` + (min - i), { opacity: ((10 - i - 1) / 10).toString() }, ''));
         }
         if (highlights.includes('By Recent Hour')) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + ClientUtils.CurrentUserEmail().replace('.', '').replace('@', ''), { opacity: '0.1' });
+            addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-${userId}`, { opacity: '0.1' }, '');
             const hr = Math.round(Date.now() / 1000 / 60 / 60);
-            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
+            numberRange(10).map(i => addStyleSheetRule(userStyleSheet(), `#${viewId} .UM-hr-` + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }, ''));
         }
         this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx.  so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView)
     };
@@ -857,16 +845,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const expertHighlighting = [...noviceHighlighting, 'Important Items', 'Ignore Items', 'Disagree Items', 'By Recent Minute', 'By Recent Hour'];
         (Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
             highlighting.push({
-                description: (!FormattedTextBox._globalHighlights.has(option) ? 'Highlight ' : 'Unhighlight ') + option,
+                description: (!this._curHighlights.has(option) ? 'Highlight ' : 'Unhighlight ') + option,
                 event: action(() => {
                     e.stopPropagation();
-                    if (!FormattedTextBox._globalHighlights.has(option)) {
-                        FormattedTextBox._globalHighlights.add(option);
+                    if (!this._curHighlights.has(option)) {
+                        this._curHighlights.add(option);
                     } else {
-                        FormattedTextBox._globalHighlights.delete(option);
+                        this._curHighlights.delete(option);
                     }
                 }),
-                icon: !FormattedTextBox._globalHighlights.has(option) ? 'highlighter' : 'remove-format',
+                icon: !this._curHighlights.has(option) ? 'highlighter' : 'remove-format',
             })
         );
         const appearance = cm.findByDescription('Appearance...');
@@ -1191,15 +1179,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss], xMargin: this.Document.xMargin, yMargin: this.Document.yMargin }),
             autoHeight => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
         );
-        this._disposers.highlights = reaction(
-            () => Array.from(FormattedTextBox._globalHighlights).slice(),
-            highlights => this.updateHighlights(highlights),
-            { fireImmediately: true }
-        );
-        this._disposers.width = reaction(
-            () => this._props.PanelWidth(),
-            () => this.tryUpdateScrollHeight()
-        );
+        this._disposers.highlights = reaction(() => Array.from(this._curHighlights).slice(), this.updateHighlights, { fireImmediately: true });
+        this._disposers.width = reaction(this._props.PanelWidth, this.tryUpdateScrollHeight);
         this._disposers.scrollHeight = reaction(
             () => ({ scrollHeight: this.scrollHeight, layoutAutoHeight: this.layout_autoHeight, width: NumCast(this.layoutDoc._width) }),
             ({ width, scrollHeight, layoutAutoHeight }) => width && layoutAutoHeight && this.resetNativeHeight(scrollHeight),
@@ -1211,7 +1192,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             ({ border, sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => {
                 const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
                 if (
-                    (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && //
+                    (!Array.from(this._curHighlights).includes('Bold Text') || this._props.isSelected()) && //
                     layoutAutoHeight &&
                     newHeight &&
                     (newHeight !== this.layoutDoc.height || border < NumCast(this.layoutDoc.height)) &&
@@ -1220,7 +1201,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     this._props.setHeight?.(newHeight);
                 }
             },
-            { fireImmediately: !Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') }
+            { fireImmediately: !Array.from(this._curHighlights).includes('Bold Text') }
         );
         this._disposers.links = reaction(
             () => Doc.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
@@ -1275,7 +1256,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     });
                 }
                 this.prepareForTyping();
-                if (FormattedTextBox._globalHighlights.has('Bold Text')) {
+                if (this._curHighlights.has('Bold Text')) {
                     this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
                 }
                 if (((RichTextMenu.Instance?.view === this.EditorView && this.EditorView) || this.isLabel) && !selected) {
@@ -1520,6 +1501,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         if (this.recordingDictation) {
             this.recordingDictation = !this.recordingDictation;
         }
+        removeStyleSheet(this._userStyleSheetElement);
         Object.values(this._disposers).forEach(disposer => disposer?.());
         this.endUndoTypingBatch();
         FormattedTextBox.LiveTextUndo?.end();
@@ -1766,13 +1748,30 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         }
         const { state } = _editorView;
         if (!state.selection.empty && e.key === '%') {
-            this._rules!.EnteringStyle = true;
+            this._enteringStyle = true;
             StopEvent(e);
             return;
         }
+        if (this._enteringStyle && 'tix!'.includes(e.key)) {
+            const tag = e.key === 't' ? 'todo' : e.key === 'i' ? 'ignore' : e.key === 'x' ? 'disagree' : e.key === '!' ? 'important' : '??';
+            const node = state.selection.$from.nodeAfter;
+            const start = state.selection.from;
+            const end = state.selection.to;
+
+            if (node) {
+                StopEvent(e);
+                _editorView.dispatch(
+                    state.tr
+                        .removeMark(start, end, schema.marks.user_mark)
+                        .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }))
+                        .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail(), tag, modified: Math.round(Date.now() / 1000 / 60) }))
+                );
+                return;
+            }
+        }
 
-        if (state.selection.empty || !this._rules!.EnteringStyle) {
-            this._rules!.EnteringStyle = false;
+        if (state.selection.empty || !this._enteringStyle) {
+            this._enteringStyle = false;
         }
         for (let i = state.selection.from; i <= state.selection.to; i++) {
             const node = state.doc.resolve(i);
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 04315e886..14ea911b1 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -1,7 +1,6 @@
 import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules';
 import { NodeType } from 'prosemirror-model';
 import { NodeSelection, TextSelection } from 'prosemirror-state';
-import { ClientUtils } from '../../../../ClientUtils';
 import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc';
 import { DocData } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
@@ -21,7 +20,6 @@ import { schema } from './schema_rts';
 export class RichTextRules {
     public Document: Doc;
     public TextBox: FormattedTextBox;
-    public EnteringStyle: boolean = false;
     constructor(doc: Doc, textBox: FormattedTextBox) {
         this.Document = doc;
         this.TextBox = textBox;
@@ -143,9 +141,8 @@ export class RichTextRules {
                 return replaced;
             }),
 
-            // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
-            new InputRule(/(%d|d)$/, (state, match, start, end) => {
-                if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
+            // set the First-line indent node type for the selection's paragraph
+            new InputRule(/%d$/, (state, match, start, end) => {
                 const pos = state.doc.resolve(start);
                 for (let depth = pos.depth; depth >= 0; depth--) {
                     const node = pos.node(depth);
@@ -158,9 +155,8 @@ export class RichTextRules {
                 return null;
             }),
 
-            // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
-            new InputRule(/(%h|h)$/, (state, match, start, end) => {
-                if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
+            // set the Hanging indent node type for the current selection's paragraph
+            new InputRule(/%h$/, (state, match, start, end) => {
                 const pos = state.doc.resolve(start);
                 for (let depth = pos.depth; depth >= 0; depth--) {
                     const node = pos.node(depth);
@@ -173,9 +169,8 @@ export class RichTextRules {
                 return null;
             }),
 
-            // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
-            new InputRule(/(%q|q)$/, (state, match, start, end) => {
-                if (!match[0].startsWith('%') && !this.EnteringStyle) return null;
+            // set the Quoted indent node type for the current selection's paragraph
+            new InputRule(/%q$/, (state, match, start, end) => {
                 const pos = state.doc.resolve(start);
                 if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
                     const { node } = state.selection;
@@ -410,22 +405,6 @@ export class RichTextRules {
             // # heading
             textblockTypeInputRule(/^(#{1,6})\s$/, schema.nodes.heading, match => ({ level: match[1].length })),
 
-            // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
-            new InputRule(/[ti!x]$/, (state, match, start, end) => {
-                if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
-
-                const tag = match[0] === 't' ? 'todo' : match[0] === 'i' ? 'ignore' : match[0] === 'x' ? 'disagree' : match[0] === '!' ? 'important' : '??';
-                const node = state.doc.resolve(start).nodeAfter;
-
-                if (node?.marks.findIndex(m => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
-                return node
-                    ? state.tr
-                          .removeMark(start, end, schema.marks.user_mark)
-                          .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }))
-                          .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail(), tag: tag, modified: Math.round(Date.now() / 1000 / 60) }))
-                    : state.tr;
-            }),
-
             new InputRule(/%\(/, (state, match, start, end) => {
                 const node = state.doc.resolve(start).nodeAfter;
                 const sm = state.storedMarks?.slice() || [];
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 9aa0e8e8b..21d987587 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -55,7 +55,7 @@ interface IViewerProps extends FieldViewProps {
  */
 @observer
 export class PDFViewer extends ObservableReactComponent<IViewerProps> {
-    static _annotationStyle = addStyleSheet();
+    static _annotationStyle = addStyleSheet().sheet;
 
     constructor(props: IViewerProps) {
         super(props);
-- 
cgit v1.2.3-70-g09d2


From d818ef151ca65008e5c6bb5e92b709decb3026d8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 14 Apr 2025 18:35:49 -0400
Subject: fixed how templates are expanded to avoid template sub-component
 conflicts by changing how field keys are named.   fixed various Cast
 functions to be more typesafe by including undefined as part of return type. 
 overhaul of Doc.MakeClone, MakeCopy, FindRefernces - makeClone is no longer
 async.  fixed inlined docs in text docs.

---
 src/client/documents/DocUtils.ts                   |  41 +-
 src/client/documents/DocumentTypes.ts              |   2 +-
 src/client/documents/Documents.ts                  |   8 +-
 src/client/util/CurrentUserUtils.ts                |  37 +-
 src/client/util/DictationManager.ts                |   2 +-
 src/client/util/DocumentManager.ts                 |   2 +-
 src/client/util/DragManager.ts                     |  40 +-
 src/client/util/DropConverter.ts                   |   6 +-
 src/client/util/Import & Export/ImageUtils.ts      |   2 +-
 src/client/util/LinkFollower.ts                    |  32 +-
 src/client/util/LinkManager.ts                     |  43 +-
 src/client/util/SearchUtil.ts                      |   6 +-
 src/client/util/SettingsManager.tsx                |   2 +-
 src/client/views/DocComponent.tsx                  |   8 +-
 src/client/views/DocumentButtonBar.tsx             |   8 +-
 src/client/views/FilterPanel.tsx                   |   4 +-
 src/client/views/GlobalKeyHandler.ts               |   6 +-
 src/client/views/InkStrokeProperties.ts            |   2 +-
 src/client/views/InkTranscription.tsx              |   6 +-
 src/client/views/LightboxView.tsx                  |   4 +-
 src/client/views/Main.tsx                          |   4 +-
 src/client/views/MainView.tsx                      |  42 +-
 src/client/views/PinFuncs.ts                       |   8 +-
 src/client/views/PropertiesButtons.tsx             |   2 +-
 src/client/views/PropertiesView.tsx                |   8 +-
 src/client/views/StyleProvider.tsx                 |  12 +-
 src/client/views/StyleProviderQuiz.tsx             |   2 +-
 src/client/views/TagsView.tsx                      |   6 +-
 src/client/views/animationtimeline/Timeline.tsx    |   2 +-
 .../views/collections/CollectionCarouselView.tsx   |   3 +-
 .../views/collections/CollectionDockingView.tsx    |  16 +-
 .../views/collections/CollectionNoteTakingView.tsx |  11 +-
 .../views/collections/CollectionStackingView.tsx   |  12 +-
 .../CollectionStackingViewFieldColumn.tsx          |   4 +-
 src/client/views/collections/CollectionSubView.tsx |  37 +-
 src/client/views/collections/TabDocView.tsx        |  14 +-
 src/client/views/collections/TreeView.tsx          |  20 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  31 +-
 .../collectionFreeForm/FaceCollectionBox.tsx       |   2 +-
 .../collectionFreeForm/ImageLabelBox.tsx           |   4 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   6 +-
 .../collectionLinear/CollectionLinearView.tsx      |   4 +-
 .../collectionSchema/CollectionSchemaView.tsx      |   4 +-
 .../collectionSchema/SchemaTableCell.tsx           |   2 +-
 src/client/views/global/globalScripts.ts           |  37 +-
 src/client/views/newlightbox/NewLightboxView.tsx   |   2 +-
 src/client/views/nodes/AudioBox.tsx                |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   4 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   2 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   4 +-
 src/client/views/nodes/DocumentView.tsx            | 100 ++--
 src/client/views/nodes/FieldView.tsx               |   2 +-
 src/client/views/nodes/IconTagBox.tsx              |   2 +-
 src/client/views/nodes/ImageBox.tsx                |  24 +-
 src/client/views/nodes/KeyValueBox.tsx             |   8 +-
 src/client/views/nodes/KeyValuePair.tsx            |   4 +-
 src/client/views/nodes/PDFBox.tsx                  |   2 +-
 .../views/nodes/RecordingBox/RecordingBox.tsx      |   2 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   2 +-
 src/client/views/nodes/ScriptingBox.tsx            |  13 +-
 src/client/views/nodes/WebBox.tsx                  |  18 +-
 .../nodes/chatbot/chatboxcomponents/ChatBox.tsx    |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  55 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |   6 +-
 .../views/nodes/formattedText/RichTextRules.ts     |  19 +-
 src/client/views/nodes/trails/PresBox.tsx          | 187 +++---
 src/client/views/nodes/trails/PresElementBox.scss  | 308 ----------
 src/client/views/nodes/trails/PresElementBox.tsx   | 628 ---------------------
 src/client/views/nodes/trails/PresSlideBox.scss    | 308 ++++++++++
 src/client/views/nodes/trails/PresSlideBox.tsx     | 628 +++++++++++++++++++++
 src/client/views/nodes/trails/index.ts             |   2 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |   2 +-
 src/client/views/search/FaceRecognitionHandler.tsx |   4 +-
 src/client/views/search/SearchBox.tsx              |   2 +-
 src/client/views/smartdraw/StickerPalette.tsx      |  13 +-
 src/debug/Repl.tsx                                 |   6 +-
 src/fields/Doc.ts                                  | 486 ++++++++--------
 src/fields/List.ts                                 |   6 +-
 src/fields/Proxy.ts                                |  64 +--
 src/fields/Schema.ts                               |  21 +-
 src/fields/ScriptField.ts                          |  12 +-
 src/fields/Types.ts                                |  91 +--
 src/fields/util.ts                                 |  10 +-
 83 files changed, 1799 insertions(+), 1806 deletions(-)
 delete mode 100644 src/client/views/nodes/trails/PresElementBox.scss
 delete mode 100644 src/client/views/nodes/trails/PresElementBox.tsx
 create mode 100644 src/client/views/nodes/trails/PresSlideBox.scss
 create mode 100644 src/client/views/nodes/trails/PresSlideBox.tsx

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index 5a8230847..7845c80aa 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -55,13 +55,13 @@ export namespace DocUtils {
             const matchLink = (val: string, anchor: Doc) => {
                 const linkedToExp = (val ?? '').split('=');
                 if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val;
-                return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
+                return DocCast(anchor[linkedToExp[0]]) && Field.toScriptString(DocCast(anchor[linkedToExp[0]])!) === linkedToExp[1];
             };
             // prettier-ignore
             return  (value === Doc.FilterNone && !allLinks.length) || 
                     (value === Doc.FilterAny && !!allLinks.length) ||
-                    (allLinks.some(link => matchLink(value as string, DocCast(link.link_anchor_1)) || 
-                                           matchLink(value as string, DocCast(link.link_anchor_2)) ));
+                    (allLinks.some(link => (DocCast(link.link_anchor_1) && matchLink(value as string, DocCast(link.link_anchor_1)!)) || 
+                                           (DocCast(link.link_anchor_2) && matchLink(value as string, DocCast(link.link_anchor_2)!)) ));
         }
         if (typeof value === 'string') {
             value = value.replace(`,${ClientUtils.noRecursionHack}`, '');
@@ -266,7 +266,7 @@ export namespace DocUtils {
             Object.keys(funcs)
                 .filter(key => !key.endsWith('-setter'))
                 .forEach(key => {
-                    const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+                    const cfield = ComputedField.DisableCompute(() => FieldValue(doc[key]));
                     const func = funcs[key];
                     if (ScriptCast(cfield)?.script.originalScript !== func) {
                         const setFunc = Cast(funcs[key + '-setter'], 'string', null);
@@ -375,12 +375,13 @@ export namespace DocUtils {
 
         const documentList: ContextMenuProps[] = foo
             .filter(btnDoc => !btnDoc.hidden)
-            .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
+            .map(btnDoc => DocCast(btnDoc?.dragFactory))
             .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title)
+            .map(doc => doc!)
             .map(dragDoc => ({
                 description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
                 event: undoable(() => {
-                    const newDoc = (Doc.isTemplateDoc(dragDoc) ? DocUtils.delegateDragFactory : DocUtils.copyDragFactory)(dragDoc);
+                    const newDoc = (dragDoc.isTemplateDoc ? DocUtils.delegateDragFactory : DocUtils.copyDragFactory)(dragDoc);
                     if (newDoc) {
                         newDoc._author = ClientUtils.CurrentUserEmail();
                         newDoc.x = x;
@@ -437,6 +438,7 @@ export namespace DocUtils {
             .filter(btnDoc => !btnDoc.hidden)
             .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
             .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title)
+            .map(doc => doc!)
             .map(dragDoc => ({
                 description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
                 event: undoable(() => {
@@ -494,11 +496,11 @@ export namespace DocUtils {
             .map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc)
             .filter(d => d.isTemplateDoc);
         // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document.  but type is not reliable and there could be other types of template searches so this should be generalized
-        // first try to find a template that matches the specific document type (<typeName>_<templateName>).  otherwise, fallback to a general match on <templateName>
+        // first try to find a template that matches the specific document type (<typeName><TemplateName>).  otherwise, fallback to a general match on <templateName>
         !docLayoutTemplate &&
             allTemplates.forEach(tempDoc => {
                 const templateType = StrCast(doc[templateName + '_fieldKey'] || doc.type);
-                StrCast(tempDoc.title) === templateName + '_' + templateType && (docLayoutTemplate = tempDoc);
+                StrCast(tempDoc.title) === templateName + (templateType[0].toUpperCase() + templateType.slice(1)) && (docLayoutTemplate = tempDoc);
             });
         !docLayoutTemplate &&
             allTemplates.forEach(tempDoc => {
@@ -522,18 +524,13 @@ export namespace DocUtils {
                 Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined);
             }
         } else {
-            let fieldTemplate: Opt<Doc>;
-            if (doc.data instanceof RichTextField || typeof doc.data === 'string') {
-                fieldTemplate = Docs.Create.TextDocument('', options);
-            } else if (doc.data instanceof PdfField) {
-                fieldTemplate = Docs.Create.PdfDocument('http://www.msn.com', options);
-            } else if (doc.data instanceof VideoField) {
-                fieldTemplate = Docs.Create.VideoDocument('http://www.cs.brown.edu', options);
-            } else if (doc.data instanceof AudioField) {
-                fieldTemplate = Docs.Create.AudioDocument('http://www.cs.brown.edu', options);
-            } else if (doc.data instanceof ImageField) {
-                fieldTemplate = Docs.Create.ImageDocument('http://www.cs.brown.edu', options);
-            }
+            const fieldTemplate = (() => {
+                if (doc.data instanceof RichTextField || typeof doc.data === 'string') return Docs.Create.TextDocument('', options);
+                if (doc.data instanceof PdfField) return Docs.Create.PdfDocument('http://www.msn.com', options);
+                if (doc.data instanceof VideoField) return Docs.Create.VideoDocument('http://www.cs.brown.edu', options);
+                if (doc.data instanceof AudioField) return Docs.Create.AudioDocument('http://www.cs.brown.edu', options);
+                if (doc.data instanceof ImageField) return Docs.Create.ImageDocument('http://www.cs.brown.edu', options);
+            })();
             const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + '(' + doc.title + ')', isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
             fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
             docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
@@ -904,8 +901,8 @@ export namespace DocUtils {
         return ndoc;
     }
 
-    export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
-        const { clone, map, linkMap } = await Doc.MakeClone(doc);
+    export function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
+        const { clone, map, linkMap } = Doc.MakeClone(doc);
         const proms = new Set<string>();
         function replacer(key: string, value: { url: string; [key: string]: unknown }) {
             if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index dd0985182..5c6559836 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -36,7 +36,7 @@ export enum DocumentType {
     // special purpose wrappers that either take no data or are compositions of lower level types
     LINK = 'link',
     PRES = 'presentation',
-    PRESELEMENT = 'preselement',
+    PRESSLIDE = 'presslide',
     COMPARISON = 'comparison',
     PUSHPIN = 'pushpin',
     MAPROUTE = 'maproute',
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d93a432d4..bf9cc5bd4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -217,7 +217,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);
-    rootDocument?: DOCt = new DocInfo('document that supplies the information needed for a rendering template (eg, pres slide for PresElement)');
+    rootDocument?: DOCt = new DocInfo('document that stores the data for compound template documents.');
     color?: STRt = new StrInfo('foreground color data doc', false);
     hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection', false);
     backgroundColor?: STRt = new StrInfo('background color for data doc', false);
@@ -1139,8 +1139,8 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) });
         }
 
-        export function PresElementBoxDocument() {
-            return Prototypes.get(DocumentType.PRESELEMENT);
+        export function PresSlideDocument() {
+            return Prototypes.get(DocumentType.PRESSLIDE);
         }
 
         export function DataVizDocument(url: string, options?: DocumentOptions, overwriteDoc?: Doc) {
@@ -1148,7 +1148,7 @@ export namespace Docs {
         }
 
         export function AnnoPaletteDocument(options?: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.ANNOPALETTE), new List([Doc.MyStickers]), { ...(options || {}) });
+            return InstanceFromProto(Prototypes.get(DocumentType.ANNOPALETTE), new List([...(Doc.MyStickers ? [Doc.MyStickers] : [])]), { ...(options || {}) });
         }
 
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 35dc5f1c7..1887c1716 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -148,12 +148,12 @@ export class CurrentUserUtils {
             { title: "Idea",   backgroundColor: "pink",      icon: "lightbulb"  , _layout_showTitle: "title"},
             { title: "Topic",  backgroundColor: "lightblue", icon: "book-open"  , _layout_showTitle: "title"}];
 
-        
+        const metanote = DocCast(doc.emptyMetaNote);
         const reqdNoteList = [...reqdTempOpts.map(opts => {
             const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true};
             const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(fdoc => fdoc.title === opts.title): undefined;
             return DocUtils.AssignOpts(noteTemp, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts));
-        }), DocCast(doc.emptyMetaNote), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))];
+        }), ...(metanote ? [metanote]:[]), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))];
 
         const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true };
         return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts));
@@ -211,7 +211,7 @@ export class CurrentUserUtils {
         const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts  });
         
         const makeIconTemplate = (name: DocumentType | string | undefined, templateField: string, opts:DocumentOptions) => {
-            const title = "icon" + (name ? "_" + name : "");
+            const title = "icon" + (name ? name[0].toUpperCase()+name.slice(1) : "");
             const curIcon = DocCast(templateIconsDoc[title]);
             const creator =  (() => { switch (opts.iconTemplate) {
                                         case DocumentType.IMG : return imageBox;
@@ -424,7 +424,10 @@ pie title Minerals in my tap water
         const standardOps = (key:string) => ({ title : "Untitled "+ key, _layout_fitWidth: false, isSystem: true, "dragFactory_count": 0, cloneFieldFilter: new List<string>(["isSystem"]) });
         emptyThings.forEach(
             thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs));
-        DocCast(Doc.UserDoc().emptyMetaNote).title = "MetaNote"; // hack: metanotes are used a template, so 'untitled metaNote' is an awkward name 
+        const metanote = DocCast(Doc.UserDoc().emptyMetaNote);
+        if (metanote) {
+            metanote.title = "MetaNote"; // hack: metanotes are used a template, so 'untitled metaNote' is an awkward name 
+        }
 
         return [
             { toolTip: "Tap or drag to create a note",            title: "Note",        icon: "sticky-note",     dragFactory: doc.emptyNote as Doc,       clickFactory: DocCast(doc.emptyNote)},
@@ -490,7 +493,7 @@ pie title Minerals in my tap water
             { title: "Tools",           toolTip: "Tools",           target: this.setupToolsBtnPanel(doc, "myTools"),           icon: "wrench",      },
             { title: "Imports",         toolTip: "Imports ⌘I",      target: this.setupImportSidebar(doc, "myImports"),         icon: "upload",      },
             { title: "Closed",          toolTip: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), icon: "archive",     hidden: true }, // this doc is hidden from the Sidebar, but it's still being used in MyFilesystem which ignores the hidden field
-            { title: "Shared",          toolTip: "Shared Docs",     target: Doc.MySharedDocs,                                  icon: "users",       funcs: {badgeValue: badgeValue}},
+            { title: "Shared",          toolTip: "Shared Docs",     target: Doc.MySharedDocs??Doc.UserDoc(),                   icon: "users",       funcs: {badgeValue: badgeValue}},
             { title: "Trails",          toolTip: "Trails ⌘R",       target: Doc.UserDoc(),                                     icon: "pres-trail",  funcs: {target: getActiveDashTrails}},
             { title: "Image Grouper",   toolTip: "Image Grouper",   target: this.setupImageGrouper(doc, "myImageGrouper"),     icon: "folder-open", hidden: false },
             { title: "Faces",           toolTip: "Unique Faces",    target: this.setupFaceCollection(doc, "myFaceCollection"), icon: "face-smile",  hidden: false },
@@ -551,7 +554,6 @@ pie title Minerals in my tap water
         const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, allTools?.length ? allTools[0]:undefined);
         const userTools = allTools && allTools?.length > 1 ? allTools[1]:undefined;
         const userBtns = CurrentUserUtils.setupUserDocumentCreatorButtons(doc, userTools);
-        // doc.myUserBtns = new PrefetchProxy(userBtns);
         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_dontCenter: "y",
@@ -975,7 +977,10 @@ pie title Minerals in my tap water
         };
         
         DocUtils.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts);
-        if (!Doc.GetProto(DocCast(doc.mySharedDocs)).data_dashboards) Doc.GetProto(DocCast(doc.mySharedDocs)).data_dashboards = new List<Doc>();
+        const sharedDocs = DocCast(doc.mySharedDocs);
+        if (sharedDocs) {
+            if (!Doc.GetProto(sharedDocs).data_dashboards) Doc.GetProto(sharedDocs).data_dashboards = new List<Doc>();
+        }
     }
 
     /// Import option on the left side button panel 
@@ -1000,9 +1005,9 @@ pie title Minerals in my tap water
     static updateUserDocument(docIn: Doc, sharingDocumentId: string, linkDatabaseId: string) {
         const doc = docIn;
         DocUtils.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {});
-        reaction(() => DateCast(DocCast(doc.globalGroupDatabase).data_modificationDate),
+        reaction(() => DateCast(DocCast(doc.globalGroupDatabase)?.data_modificationDate),
             async () => {
-                const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
+                const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase)?.data);
                 const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(ClientUtils.CurrentUserEmail())) || [];
                 SetCachedGroups(["Guest", ...(mygroups?.map(g => StrCast(g.title))??[])]);
             }, { fireImmediately: true });
@@ -1058,11 +1063,13 @@ pie title Minerals in my tap water
         
         SelectionManager.DeselectAll(); // this forces SelectionManager implementation to copy over to DocumentView's API.  This also triggers the LinkManager to be created
         
-        Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards);
-        Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs);
-        Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed);
+        if (Doc.MyFilesystem) {
+            Doc.MyDashboards && Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards);
+            Doc.MyDashboards && Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards);
+            Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed);
+        }
 
-        Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org");
+        DocCast(Doc.UserDoc().emptyWebpage) && (Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)!).data = new WebField("https://www.wikipedia.org"));
 
         DocServer.CacheNeedsUpdate() && setTimeout(UPDATE_SERVER_CACHE, 2500);
         setInterval(UPDATE_SERVER_CACHE, 120000);
@@ -1142,7 +1149,7 @@ pie title Minerals in my tap water
             const file = input.files?.[0];
             if (file?.type === 'application/zip' || file?.type === 'application/x-zip-compressed') {
                 const doc = await Doc.importDocument(file);
-                const list = Cast(Doc.MyImports.data, listSpec(Doc), null);
+                const list = Cast(Doc.MyImports?.data, listSpec(Doc), null);
                 doc instanceof Doc && list?.splice(0, 0, doc);
             } else if (input.files && input.files.length !== 0) {
                 const disposer = OverlayView.ShowSpinner();
@@ -1150,7 +1157,7 @@ pie title Minerals in my tap water
                 if (results.length !== input.files?.length) {
                     alert("Error uploading files - possibly due to unsupported file types");
                 }
-                const list = Cast(Doc.MyImports.data, listSpec(Doc), null);
+                const list = Cast(Doc.MyImports?.data, listSpec(Doc), null);
                 list?.splice(0, 0, ...results);
                 disposer();
             } else {
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 2eef3da0e..dcef4a4fe 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -364,7 +364,7 @@ export namespace DictationManager {
                                     case 'nested collection':return Docs.Create.FreeformDocument([], {});
                                 } // prettier-ignore
                             })();
-                            created && Doc.AddDocToList(target.dataDoc, Doc.LayoutFieldKey(target.Document), created);
+                            created && Doc.AddDocToList(target.dataDoc, Doc.LayoutDataKey(target.Document), created);
                         }
                     }
                 },
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 5ce005811..ad57c2a62 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -183,7 +183,7 @@ export class DocumentManager {
 
     static _howl: Howl;
     static playAudioAnno(doc: Doc) {
-        const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement();
+        const anno = Cast(doc[Doc.LayoutDataKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement();
         if (anno) {
             this._howl?.stop();
             if (anno instanceof AudioField) {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index e2e4c0fe4..a66e6998b 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -234,29 +234,27 @@ export namespace DragManager {
             dropDoc instanceof Doc && CreateLinkToActiveAudio(() => dropDoc);
             return dropDoc;
         };
-        const finishDrag = async (e: DragCompleteEvent) => {
+        const finishDrag = (e: DragCompleteEvent) => {
             const { docDragData } = e;
             setTimeout(() => dragData.dragEnding?.());
             onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
             if (docDragData && !docDragData.droppedDocuments.length) {
                 docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
-                docDragData.droppedDocuments = (
-                    await Promise.all(
-                        dragData.draggedDocuments.map(async d =>
-                            !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
-                                ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result as Doc)
-                                : docDragData.dropAction === dropActionType.embed
-                                  ? Doc.BestEmbedding(d)
-                                  : docDragData.dropAction === dropActionType.add
-                                    ? d
-                                    : docDragData.dropAction === dropActionType.proto
-                                      ? d[DocData]
-                                      : docDragData.dropAction === dropActionType.copy
-                                        ? (await Doc.MakeClone(d)).clone
-                                        : d
-                        )
+                docDragData.droppedDocuments = dragData.draggedDocuments
+                    .map(d =>
+                        !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
+                            ? addAudioTag(ScriptCast(d.onDragStart)!.script.run({ this: d }).result as Doc)
+                            : docDragData.dropAction === dropActionType.embed
+                              ? Doc.BestEmbedding(d)
+                              : docDragData.dropAction === dropActionType.add
+                                ? d
+                                : docDragData.dropAction === dropActionType.proto
+                                  ? d[DocData]
+                                  : docDragData.dropAction === dropActionType.copy
+                                    ? Doc.MakeClone(d).clone
+                                    : d
                     )
-                ).filter(d => d);
+                    .filter(d => d);
                 ![dropActionType.same, dropActionType.proto].includes(StrCast(docDragData.dropAction) as dropActionType) &&
                     docDragData.droppedDocuments
                         // .filter(drop => !drop.dragOnlyWithinContainer || ['embed', 'copy'].includes(docDragData.dropAction as any))
@@ -364,7 +362,7 @@ export namespace DragManager {
         };
     }
 
-    async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
+    function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
         const dropArgs = {
             cancelable: true, // allows preventDefault() to be called to cancel the drop
             bubbles: true,
@@ -380,7 +378,7 @@ export namespace DragManager {
         };
         target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
         UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes)
-        await finishDrag?.(complete);
+        finishDrag?.(complete);
         UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
         options?.dragComplete?.(complete);
         endDrag?.();
@@ -566,11 +564,11 @@ export namespace DragManager {
                 const targClassName = e.target instanceof HTMLElement && typeof e.target.className === 'string' ? e.target.className : '';
                 if (['lm_tab', 'lm_title_wrap', 'lm_tabs', 'lm_header'].includes(targClassName) && docDragData.draggedDocuments.length === 1) {
                     if (!startWindowDragTimer) {
-                        startWindowDragTimer = setTimeout(async () => {
+                        startWindowDragTimer = setTimeout(() => {
                             startWindowDragTimer = undefined;
                             docDragData.dropAction = docDragData.userDropAction || dropActionType.same;
                             AbortDrag();
-                            await finishDrag?.(new DragCompleteEvent(true, docDragData));
+                            finishDrag?.(new DragCompleteEvent(true, docDragData));
                             DragManager.StartWindowDrag?.(e, docDragData.droppedDocuments, aborted => {
                                 if (!aborted && (docDragData?.dropAction === dropActionType.move || docDragData?.dropAction === dropActionType.same)) {
                                     docDragData.removeDocument?.(docDragData?.draggedDocuments[0]);
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 7d3f63448..b6b111930 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -3,7 +3,7 @@ import { DocData, DocLayout } from '../../fields/DocSymbols';
 import { ObjectField } from '../../fields/ObjectField';
 import { RichTextField } from '../../fields/RichTextField';
 import { ComputedField, ScriptField } from '../../fields/ScriptField';
-import { StrCast } from '../../fields/Types';
+import { DocCast, StrCast } from '../../fields/Types';
 import { ImageField } from '../../fields/URLField';
 import { Docs, DocumentOptions } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
@@ -23,7 +23,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
  */
 function makeTemplate(doc: Doc, first: boolean = true): boolean {
     const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
-    if (layoutDoc.layout instanceof Doc) {
+    if (DocCast(layoutDoc.layout)) {
         return true; // its already a template
     }
     const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)?.[0];
@@ -68,7 +68,7 @@ export function MakeTemplate(doc: Doc) {
  * Makes a draggable button or image that will create a template doc Instance
  */
 export function makeUserTemplateButtonOrImage(doc: Doc, image?: string) {
-    const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
+    const layoutDoc = doc;
     if (layoutDoc.type !== DocumentType.FONTICON) {
         !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc);
     }
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index 9c32ca25a..eae3460b3 100644
--- a/src/client/util/Import & Export/ImageUtils.ts	
+++ b/src/client/util/Import & Export/ImageUtils.ts	
@@ -15,7 +15,7 @@ export namespace ImageUtils {
     export const AssignImgInfo = (document: Doc, data?: Upload.InspectionResults) => {
         if (data) {
             data.nativeWidth && (document._height = (NumCast(document._width) * data.nativeHeight) / data.nativeWidth);
-            const field = '$' + Doc.LayoutFieldKey(document);
+            const field = '$' + Doc.LayoutDataKey(document);
             document[`${field}_nativeWidth`] = data.nativeWidth;
             document[`${field}_nativeHeight`] = data.nativeHeight;
             document[`${field}_path`] = data.source;
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 0e67dcfaa..6081c3fae 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -43,13 +43,13 @@ export class LinkFollower {
     };
 
     public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) {
-        const getView = (doc: Doc) => DocumentView.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc));
-        const isAnchor = (source: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(anchor.annotationOn as Doc, source);
+        const getView = (doc: Doc) => DocumentView.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc)!);
+        const isAnchor = (source?: Doc, anchor?: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(DocCast(anchor?.annotationOn), source);
         const linkDocs = link ? [link] : Doc.Links(sourceDoc);
-        const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_1 as Doc)); // link docs where 'sourceDoc' is link_anchor_1
-        const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_2 as Doc)); // link docs where 'sourceDoc' is link_anchor_2
-        const fwdLinkWithoutTargetView = fwdLinks.find(l => !getView(DocCast(l.link_anchor_2)));
-        const backLinkWithoutTargetView = backLinks.find(l => !getView(DocCast(l.link_anchor_1)));
+        const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, DocCast(l.link_anchor_1))); // link docs where 'sourceDoc' is link_anchor_1
+        const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, DocCast(l.link_anchor_2))); // link docs where 'sourceDoc' is link_anchor_2
+        const fwdLinkWithoutTargetView = fwdLinks.find(l => !DocCast(l.link_anchor_2) || !getView(DocCast(l.link_anchor_2)!));
+        const backLinkWithoutTargetView = backLinks.find(l => !DocCast(l.link_anchor_1) || !getView(DocCast(l.link_anchor_1)!));
         const linkWithoutTargetDoc = traverseBacklink === undefined ? (fwdLinkWithoutTargetView ?? backLinkWithoutTargetView) : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
         const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? fwdLinks.concat(backLinks) : traverseBacklink ? backLinks : fwdLinks;
         const followLinks = sourceDoc.followLinkToggle || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1);
@@ -60,17 +60,17 @@ export class LinkFollower {
             return false;
         }
         followLinks.forEach(async linkDoc => {
-            const target = (
+            const target = DocCast(
                 sourceDoc === linkDoc.link_anchor_1
                     ? linkDoc.link_anchor_2
                     : sourceDoc === linkDoc.link_anchor_2
                       ? linkDoc.link_anchor_1
-                      : Doc.AreProtosEqual(sourceDoc, linkDoc.link_anchor_1 as Doc) || Doc.AreProtosEqual((linkDoc.link_anchor_1 as Doc).annotationOn as Doc, sourceDoc)
+                      : Doc.AreProtosEqual(sourceDoc, DocCast(linkDoc.link_anchor_1)) || Doc.AreProtosEqual(DocCast(DocCast(linkDoc.link_anchor_1)?.annotationOn), sourceDoc)
                         ? linkDoc.link_anchor_2
                         : linkDoc.link_anchor_1
-            ) as Doc;
-            const srcAnchor: Doc = Doc.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
+            );
             if (target) {
+                const srcAnchor = Doc.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
                 const doFollow = (canToggle?: boolean) => {
                     const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
                     const options: FocusViewOptions = {
@@ -102,14 +102,16 @@ export class LinkFollower {
                 if (srcAnchor.followLinkLocation === OpenWhere.inParent) {
                     const sourceDocParent = DocCast(sourceDoc.embedContainer);
                     if (target.embedContainer instanceof Doc && target.embedContainer !== sourceDocParent) {
-                        Doc.RemoveDocFromList(target.embedContainer, Doc.LayoutFieldKey(target.embedContainer), target);
+                        Doc.RemoveDocFromList(target.embedContainer, Doc.LayoutDataKey(target.embedContainer), target);
                         movedTarget = true;
                     }
-                    if (!DocListCast(sourceDocParent[Doc.LayoutFieldKey(sourceDocParent)]).includes(target)) {
-                        Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target);
-                        movedTarget = true;
+                    if (sourceDocParent) {
+                        if (!DocListCast(sourceDocParent[Doc.LayoutDataKey(sourceDocParent)]).includes(target)) {
+                            Doc.AddDocToList(sourceDocParent, Doc.LayoutDataKey(sourceDocParent), target);
+                            movedTarget = true;
+                        }
+                        Doc.SetContainer(target, sourceDocParent);
                     }
-                    Doc.SetContainer(target, sourceDocParent);
                 }
                 const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
                 if (srcAnchor.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 3f98ab3c4..344e2e4c0 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -136,7 +136,7 @@ export class LinkManager {
             true
         );
         FieldLoader.ServerLoadStatus.message = 'links';
-        this.addLinkDB(Doc.LinkDBDoc());
+        Doc.LinkDBDoc() && this.addLinkDB(Doc.LinkDBDoc()!);
     }
 
     public createlink_relationshipLists = () => {
@@ -148,16 +148,21 @@ export class LinkManager {
 
     public addLink(linkDoc: Doc, checkExists = false) {
         Doc.AddDocToList(Doc.UserDoc(), 'links', linkDoc);
-        if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) {
-            Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc);
-            // eslint-disable-next-line no-use-before-define
-            setTimeout(UPDATE_SERVER_CACHE, 100);
+        if (Doc.LinkDBDoc()) {
+            if (!checkExists || !DocListCast(Doc.LinkDBDoc()!.data).includes(linkDoc)) {
+                Doc.AddDocToList(Doc.LinkDBDoc()!, 'data', linkDoc);
+                // eslint-disable-next-line no-use-before-define
+                setTimeout(UPDATE_SERVER_CACHE, 100);
+            }
         }
     }
     public deleteLink(linkDoc: Doc) {
-        const ret = Doc.RemoveDocFromList(Doc.LinkDBDoc(), 'data', linkDoc);
-        linkDoc.$link_anchor_1 = linkDoc.$link_anchor_2 = undefined;
-        return ret;
+        if (Doc.LinkDBDoc()) {
+            const ret = Doc.RemoveDocFromList(Doc.LinkDBDoc()!, 'data', linkDoc);
+            linkDoc.$link_anchor_1 = linkDoc.$link_anchor_2 = undefined;
+            return ret;
+        }
+        return false;
     }
     public deleteAllLinksOnAnchor(anchor: Doc) {
         LinkManager.Instance.relatedLinker(anchor).forEach(LinkManager.Instance.deleteLink);
@@ -178,8 +183,8 @@ export class LinkManager {
         }
 
         const dirLinks = Array.from(anchor[DocData][DirectLinks]).filter(l => Doc.GetProto(anchor) === anchor[DocData] || ['1', '2'].includes(LinkManager.anchorIndex(l, anchor) as '0' | '1' | '2'));
-        const anchorRoot = DocCast(anchor.rootDocument, anchor); // template Doc fields store annotations on the topmost root of a template (not on themselves since the template layout items are only for layout)
-        const annos = DocListCast(anchorRoot[Doc.LayoutFieldKey(anchor) + '_annotations']);
+        const anchorRoot = DocCast(anchor.rootDocument, anchor)!; // template Doc fields store annotations on the topmost root of a template (not on themselves since the template layout items are only for layout)
+        const annos = DocListCast(anchorRoot[Doc.LayoutDataKey(anchor) + '_annotations']);
         return Array.from(
             annos.reduce((set, anno) => {
                 if (!processed.includes(anno)) {
@@ -242,11 +247,13 @@ export function UPDATE_SERVER_CACHE() {
         .filter(doc => doc instanceof Doc)
         .map(doc => doc as Doc);
     const references = new Set<Doc>(prototypes);
+    DocCast(Doc.UserDoc().myLinkDatabase) && references.add(DocCast(Doc.UserDoc().myLinkDatabase)!); // prevent crawling through link database here -- see below
     Doc.FindReferences(Doc.UserDoc(), references, undefined);
-    DocListCast(DocCast(Doc.UserDoc().myLinkDatabase).data).forEach(link => {
-        if (!references.has(DocCast(link.link_anchor_1)) && !references.has(DocCast(link.link_anchor_2))) {
-            Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase), 'data', link);
-            Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
+
+    DocListCast(DocCast(Doc.UserDoc().myLinkDatabase)?.data).forEach(link => {
+        if (DocCast(link.link_anchor_1) && !references.has(DocCast(link.link_anchor_1)!) && DocCast(link.link_anchor_2) && !references.has(DocCast(link.link_anchor_2)!)) {
+            DocCast(Doc.UserDoc().myLinkDatabase) && Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase)!, 'data', link);
+            Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
         }
     });
     LinkManager.Instance.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
@@ -257,10 +264,10 @@ export function UPDATE_SERVER_CACHE() {
     cacheDocumentIds = newCacheUpdate;
 
     // print out cached docs
-    //Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = ');
-    // const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc));
-    // const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
-    //Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
+    Doc.MyDockedBtns?.linearView_IsOpen && console.log('Set cached docs = ');
+    const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc));
+    const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
+    Doc.MyDockedBtns?.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
 
     rp.post(ClientUtils.prepend('/setCacheDocumentIds'), {
         body: {
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 733eae5f4..e4adcaa7e 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -9,7 +9,7 @@ export namespace SearchUtil {
     export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
 
     export function SearchCollection(collectionDoc: Opt<Doc>, queryIn: string, matchKeyNames: boolean, onlyKeys?: string[]) {
-        const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
+        const blockedTypes = [DocumentType.PRESSLIDE, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
         const blockedKeys = matchKeyNames
             ? []
             : Object.entries(DocOptions)
@@ -21,7 +21,7 @@ export namespace SearchUtil {
 
         const results = new ObservableMap<Doc, string[]>();
         if (collectionDoc) {
-            const docs = DocListCast(collectionDoc[Doc.LayoutFieldKey(collectionDoc)]);
+            const docs = DocListCast(collectionDoc[Doc.LayoutDataKey(collectionDoc)]);
             // eslint-disable-next-line @typescript-eslint/ban-types
             const docIDs: String[] = [];
             SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => {
@@ -77,7 +77,7 @@ export namespace SearchUtil {
             // eslint-disable-next-line no-loop-func
             docs.filter(d => d && !visited.includes(d)).forEach(d => {
                 visited.push(d);
-                const fieldKey = Doc.LayoutFieldKey(d);
+                const fieldKey = Doc.LayoutDataKey(d);
                 const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView');
                 const data = d[annos ? fieldKey + '_annotations' : fieldKey];
                 data && newarray.push(...DocListCast(data));
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 88f1f3260..9e79fd870 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -578,7 +578,7 @@ export class SettingsManager extends React.Component<object> {
                         <div className="settings-username" style={{ color: SettingsManager.userBackgroundColor }}>
                             {ClientUtils.CurrentUserEmail()}
                         </div>
-                        <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.userVariantColor} onClick={() => window.location.assign(ClientUtils.prepend('/logout'))} />
+                        <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.userVariantColor} background={SettingsManager.userColor} onClick={() => window.location.assign(ClientUtils.prepend('/logout'))} />
                     </div>
                 </div>
 
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 45c80c6f7..1ecd82dbc 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -48,7 +48,7 @@ export function DocComponent<P extends DocComponentProps>() {
          * This Doc inherits from the dataDoc, and may or may not inherit (or be) the layoutDoc.
          */
         get rootDoc() {
-            return DocCast(this.Document.rootDocument, this.Document);
+            return DocCast(this.Document.rootDocument, this.Document)!;
         }
 
         /**
@@ -64,7 +64,7 @@ export function DocComponent<P extends DocComponentProps>() {
          * This may or may not inherit from the data doc.
          */
         @computed get layoutDoc() {
-            return this._props.LayoutTemplateString ? this.Document : Doc.Layout(this.Document, this._props.LayoutTemplate?.());
+            return this._props.LayoutTemplateString ? this.Document : Doc.LayoutDoc(this.Document, this._props.LayoutTemplate?.());
         }
 
         /**
@@ -115,7 +115,7 @@ export function ViewBoxBaseComponent<P extends FieldViewProps>() {
          *       consider: Document, layoutDoc, dataDoc
          */
         get rootDoc() {
-            return DocCast(this.Document.rootDocument, this.Document);
+            return DocCast(this.Document.rootDocument, this.Document)!;
         }
         /**
          * This is the document being rendered by the React component. In the
@@ -189,7 +189,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
          *       other Doc options (Document, layoutDoc, dataDoc)
          */
         @computed get rootDoc() {
-            return DocCast(this.Document.rootDocument, this.Document);
+            return DocCast(this.Document.rootDocument, this.Document)!;
         }
         /**
          * This is the document being rendered.  It may be a template so it may or may no inherit from the data doc.
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 6f2f051cd..8a850467a 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -303,7 +303,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                             DocumentView.Selected().forEach(dv => {
                                 dv.layoutDoc._layout_showTags = !showing;
                                 if (e.shiftKey)
-                                    DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => {
+                                    DocListCast(dv.Document[Doc.LayoutDataKey(dv.Document) + '_annotations']).forEach(doc => {
                                         if (doc.face) doc.hidden = showing;
                                     });
                             });
@@ -479,13 +479,13 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         const doc = rootView?.Document;
         if (doc) {
             const anchor = rootView.ComponentView?.getAnchor?.(true) ?? doc;
-            const trail = DocCast(anchor.presentationTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true);
+            const trail = DocCast(anchor.presentationTrail) ?? (DocCast(Doc.UserDoc().emptyTrail) ? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail)!, true) : undefined);
             if (trail !== anchor.presentationTrail) {
-                DocUtils.MakeLink(anchor, trail, { link_relationship: 'link trail' });
+                trail && DocUtils.MakeLink(anchor, trail, { link_relationship: 'link trail' });
                 anchor.presentationTrail = trail;
             }
             Doc.ActivePresentation = trail;
-            this._props.views().lastElement()?._props.addDocTab(trail, OpenWhere.replaceRight);
+            trail && this._props.views().lastElement()?._props.addDocTab(trail, OpenWhere.replaceRight);
         }
         e.stopPropagation();
     };
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index c3b3f9d0c..848a77004 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -182,7 +182,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         return targetView?.ComponentView?.annotationKey || (targetView?.ComponentView?.fieldKey ?? 'data');
     }
     @computed get targetDocChildren() {
-        return [...DocListCast(this.Document?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_sidebar'])];
+        return [...DocListCast(this.Document?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.Document[Doc.LayoutDataKey(this.Document) + '_sidebar'])];
     }
 
     @computed get rangeFilters() {
@@ -236,7 +236,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                     if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++;
                     facetVal !== undefined && valueSet.add(Field.toString(facetVal as FieldType));
                     (facetVal === true || facetVal === false) && valueSet.add(Field.toString(!facetVal));
-                    const fieldKey = Doc.LayoutFieldKey(t);
+                    const fieldKey = Doc.LayoutDataKey(t);
                     const annos = !Field.toString(Doc.LayoutField(t) as FieldType).includes('CollectionView');
                     DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
                     annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 94c023330..948beec74 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -239,7 +239,7 @@ export class KeyManager {
                 break;
             case 'i':
                 {
-                    const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports);
+                    const importBtn = DocListCast(Doc.MyLeftSidebarMenu?.data).find(d => d.target === Doc.MyImports);
                     if (importBtn) {
                         MainView.Instance.selectLeftSidebarButton(importBtn);
                     }
@@ -247,7 +247,7 @@ export class KeyManager {
                 break;
             case 's':
                 {
-                    const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyTrails);
+                    const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu?.data).find(d => d.target === Doc.MyTrails);
                     if (trailsBtn) {
                         MainView.Instance.selectLeftSidebarButton(trailsBtn);
                     }
@@ -257,7 +257,7 @@ export class KeyManager {
                 if (DocumentView.Selected().length === 1 && DocumentView.Selected()[0].ComponentView?.search) {
                     DocumentView.Selected()[0].ComponentView?.search?.('', false, false);
                 } else {
-                    const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher);
+                    const searchBtn = DocListCast(Doc.MyLeftSidebarMenu?.data).find(d => d.target === Doc.MySearcher);
                     if (searchBtn) {
                         MainView.Instance.selectLeftSidebarButton(searchBtn);
                     }
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 6854476e2..425f5b6bb 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -341,7 +341,7 @@ export class InkStrokeProperties {
      */
     snapControl = (inkView: DocumentView, controlIndex: number) => {
         const inkDoc = inkView.Document;
-        const ink = Cast(inkDoc[Doc.LayoutFieldKey(inkDoc)], InkField)?.inkData;
+        const ink = Cast(inkDoc[Doc.LayoutDataKey(inkDoc)], InkField)?.inkData;
 
         if (ink) {
             const screenDragPt = inkView.ComponentView?.ptToScreen?.(ink[controlIndex]);
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 1293aa9d0..2e6b477e9 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -118,9 +118,9 @@ export class InkTranscription extends React.Component {
 
         const times: number[] = [];
         validInks
-            .filter(i => Cast(i[Doc.LayoutFieldKey(i)], InkField))
+            .filter(i => Cast(i[Doc.LayoutDataKey(i)], InkField))
             .forEach(i => {
-                const d = Cast(i[Doc.LayoutFieldKey(i)], InkField, null);
+                const d = Cast(i[Doc.LayoutDataKey(i)], InkField, null);
                 const inkStroke = DocumentView.getDocumentView(i)?.ComponentView as InkingStroke;
                 strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y })));
                 times.push(DateCast(i.author_date).getDate().getTime());
@@ -382,7 +382,7 @@ export class InkTranscription extends React.Component {
 
     render() {
         return (
-            <div className="ink-transcription" style={{pointerEvents: 'none'}}>
+            <div className="ink-transcription" style={{ pointerEvents: 'none' }}>
                 <div className="math-editor" ref={this.setMathRef}></div>
                 <div className="text-editor" ref={this.setTextRef}></div>
             </div>
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index de2c7cd09..0eb21b943 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -136,7 +136,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
         return this.SetLightboxDoc(
             doc,
             undefined,
-            [...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...this._future].sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)),
+            [...DocListCast(doc[Doc.LayoutDataKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...this._future].sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)),
             layoutTemplate
         );
     };
@@ -187,7 +187,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
             saved: this._savedState,
         });
         if (this._docTarget) {
-            const fieldKey = Doc.LayoutFieldKey(this._docTarget);
+            const fieldKey = Doc.LayoutDataKey(this._docTarget);
             const contents = [...DocListCast(this._docTarget[fieldKey]), ...DocListCast(this._docTarget[fieldKey + '_annotations'])];
             const links = Doc.Links(this._docTarget)
                 .map(link => Doc.getOppositeAnchor(link, this._docTarget!)!)
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 22725a2b9..e4bbb1c0f 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -61,7 +61,7 @@ import { FootnoteView } from './nodes/formattedText/FootnoteView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { SummaryView } from './nodes/formattedText/SummaryView';
 import { ImportElementBox } from './nodes/importBox/ImportElementBox';
-import { PresBox, PresElementBox } from './nodes/trails';
+import { PresBox, PresSlideBox } from './nodes/trails';
 import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
 import { SearchBox } from './search/SearchBox';
 import { StickerPalette } from './smartdraw/StickerPalette';
@@ -135,7 +135,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
             AudioBox,
             RecordingBox,
             PresBox,
-            PresElementBox,
+            PresSlideBox,
             SearchBox,
             ImageLabelBox,
             FaceCollectionBox,
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index be6e2fecb..ad6bb09c7 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -93,7 +93,7 @@ export class MainView extends ObservableReactComponent<object> {
     @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
     @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons
     @observable private _panelContent: string = 'none';
-    @observable private _sidebarContent: Doc = Doc.MyLeftSidebarPanel;
+    @observable private _sidebarContent?: Doc = Doc.MyLeftSidebarPanel;
     @observable private _leftMenuFlyoutWidth: number = 0;
     @computed get _hideUI() {
         return SnappingManager.HideUI || (this.mainDoc && this.mainDoc._type_collection !== CollectionViewType.Docking);
@@ -173,7 +173,7 @@ export class MainView extends ObservableReactComponent<object> {
             views => views.length > 1 && document.activeElement instanceof HTMLElement && document.activeElement?.blur()
         );
         reaction(
-            () => Doc.MyDockedBtns.linearView_IsOpen,
+            () => Doc.MyDockedBtns?.linearView_IsOpen,
             open => SnappingManager.SetPrintToConsole(!!open)
         );
         const scriptTag = document.createElement('script');
@@ -199,7 +199,7 @@ export class MainView extends ObservableReactComponent<object> {
                 ele.outerHTML = '';
             }, 1000);
         }
-        this._sidebarContent.proto = undefined;
+        this._sidebarContent && (this._sidebarContent.proto = undefined);
         if (!MainView.Live) {
             DocServer.setLivePlaygroundFields([
                 'dataTransition',
@@ -636,8 +636,10 @@ export class MainView extends ObservableReactComponent<object> {
     openPresentation = (pres: Doc) => {
         if (pres.type === DocumentType.PRES) {
             CollectionDockingView.AddSplit(pres, OpenWhereMod.right, undefined, PresBox.PanelName);
-            Doc.MyTrails && (Doc.ActivePresentation = pres);
-            Doc.AddDocToList(Doc.MyTrails, 'data', pres);
+            if (Doc.MyTrails) {
+                Doc.ActivePresentation = pres;
+                Doc.AddDocToList(Doc.MyTrails, 'data', pres);
+            }
             this.closeFlyout();
         }
     };
@@ -645,16 +647,16 @@ export class MainView extends ObservableReactComponent<object> {
     @action
     createNewFolder = async () => {
         const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true });
-        Doc.AddDocToList(Doc.MyFilesystem, 'data', folder);
+        Doc.MyFilesystem && Doc.AddDocToList(Doc.MyFilesystem, 'data', folder);
     };
 
     waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined);
     headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1);
     mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1);
-    addHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
-    removeHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
+    addHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => !!this.headerBarDoc && Doc.AddDocToList(this.headerBarDoc, 'data', doc), true);
+    removeHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => !!this.headerBarDoc && Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true);
     @computed get headerBarDocView() {
-        return (
+        return !this.headerBarDoc ? null : (
             <div className="mainView-headerBar" style={{ height: this.headerBarDocHeight() }}>
                 <DocumentView
                     key="headerBarDoc"
@@ -792,16 +794,16 @@ export class MainView extends ObservableReactComponent<object> {
             <div key="flyout" className="mainView-libraryFlyout-out">
                 {this.docButtons}
             </div>
-        ) : (
+        ) : !this._sidebarContent ? null : (
             <div key="libFlyout" className="mainView-libraryFlyout" style={{ minWidth: this._leftMenuFlyoutWidth, width: this._leftMenuFlyoutWidth }}>
                 <div className="mainView-contentArea">
                     <DocumentView
-                        Document={DocCast(this._sidebarContent.proto, this._sidebarContent)}
+                        Document={DocCast(this._sidebarContent?.proto, this._sidebarContent)!}
                         addDocument={undefined}
                         addDocTab={DocumentViewInternal.addDocTabFunc}
                         pinToPres={DocumentView.PinDoc}
                         containerViewPath={returnEmptyDocViewList}
-                        styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider}
+                        styleProvider={this._sidebarContent?.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider}
                         removeDocument={returnFalse}
                         ScreenToLocalTransform={this.mainContainerXf}
                         PanelWidth={this.leftMenuFlyoutWidth}
@@ -821,7 +823,7 @@ export class MainView extends ObservableReactComponent<object> {
     }
 
     @computed get leftMenuPanel() {
-        return (
+        return !Doc.MyLeftSidebarMenu ? null : (
             <div key="menu" className="mainView-leftMenuPanel" style={{ background: SnappingManager.userBackgroundColor, display: DocumentView.LightboxDoc() ? 'none' : undefined }}>
                 <DocumentView
                     Document={Doc.MyLeftSidebarMenu}
@@ -861,7 +863,7 @@ export class MainView extends ObservableReactComponent<object> {
                     break;
                 default:
                     this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250;
-                    this._sidebarContent.proto = DocCast(button.target);
+                    this._sidebarContent && (this._sidebarContent.proto = DocCast(button.target));
                     SnappingManager.SetLastPressedBtn(button[Id]);
             }
         }
@@ -875,8 +877,10 @@ export class MainView extends ObservableReactComponent<object> {
      */
     addHotKey = (hotKey: string) => {
         const filterIcons = DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter);
-        const menuDoc = CurrentUserUtils.setupContextMenuBtn(CurrentUserUtils.filterBtnDesc(ToTagName(hotKey), 'question'), filterIcons);
-        Doc.AddToFilterHotKeys(menuDoc);
+        if (filterIcons) {
+            const menuDoc = CurrentUserUtils.setupContextMenuBtn(CurrentUserUtils.filterBtnDesc(ToTagName(hotKey), 'question'), filterIcons);
+            Doc.AddToFilterHotKeys(menuDoc);
+        }
     };
 
     @computed get mainInnerContent() {
@@ -941,13 +945,13 @@ export class MainView extends ObservableReactComponent<object> {
     closeFlyout = action(() => {
         SnappingManager.SetLastPressedBtn('');
         this._panelContent = 'none';
-        this._sidebarContent.proto = undefined;
+        this._sidebarContent && (this._sidebarContent.proto = undefined);
         this._leftMenuFlyoutWidth = 0;
     });
 
-    remButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true);
+    remButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && !!Doc.MyDockedBtns && Doc.RemoveDocFromList(Doc.MyDockedBtns!, 'data', doc), true);
     moveButtonDoc = (docs: Doc | Doc[], targetCol: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(docs) && addDocument(docs);
-    addButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true);
+    addButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && !!Doc.MyDockedBtns && Doc.AddDocToList(Doc.MyDockedBtns!, 'data', doc), true);
 
     buttonBarXf = () => {
         if (!this._docBtnRef.current) return Transform.Identity();
diff --git a/src/client/views/PinFuncs.ts b/src/client/views/PinFuncs.ts
index d756830da..805dd9a49 100644
--- a/src/client/views/PinFuncs.ts
+++ b/src/client/views/PinFuncs.ts
@@ -70,13 +70,13 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) {
             pinProps.pinData.dataview ||
             pinProps.pinData.poslayoutview ||
             pinProps?.activeFrame !== undefined;
-        const fkey = Doc.LayoutFieldKey(targetDoc);
+        const fkey = Doc.LayoutDataKey(targetDoc);
         if (pinProps.pinData.dataview) {
             pinDoc.config_usePath = targetDoc[fkey + '_usePath'];
             pinDoc.config_data = Field.Copy(targetDoc[fkey]);
         }
         if (pinProps.pinData.dataannos) {
-            const fieldKey = '$' + Doc.LayoutFieldKey(targetDoc) + +'_annotations';
+            const fieldKey = '$' + Doc.LayoutDataKey(targetDoc) + +'_annotations';
             pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[fieldKey]).filter(doc => !doc.layout_unrendered));
         }
         if (pinProps.pinData.inkable) {
@@ -87,7 +87,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) {
         }
         if (pinProps.pinData.scrollable) pinDoc.config_scrollTop = targetDoc._layout_scrollTop;
         if (pinProps.pinData.clippable) {
-            const fieldKey = Doc.LayoutFieldKey(targetDoc);
+            const fieldKey = Doc.LayoutDataKey(targetDoc);
             pinDoc.config_clipWidth = targetDoc[fieldKey + '_clipWidth'];
         }
         if (pinProps.pinData.datarange) {
@@ -127,7 +127,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) {
         }
         if (pinProps.pinData.temporal) {
             pinDoc.config_clipStart = targetDoc._layout_currentTimecode;
-            const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
+            const duration = NumCast(pinDoc[`${Doc.LayoutDataKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
             pinDoc.config_clipEnd = NumCast(pinDoc.config_clipStart) + NumCast(targetDoc.clipEnd, duration);
         }
     }
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 6778a6691..66936c64c 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -447,7 +447,7 @@ export class PropertiesButtons extends React.Component {
     };
 
     render() {
-        const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)];
+        const layoutField = this.selectedDoc?.[Doc.LayoutDataKey(this.selectedDoc)];
         const isText = DocumentView.Selected().lastElement()?.ComponentView instanceof FormattedTextBox;
         const isInk = this.selectedDoc?.layout_isSvg;
         const isImage = layoutField instanceof ImageField;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 7fcb15afe..e7186c0e3 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -525,7 +525,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
             let permission;
             if (this.layoutDocAcls) {
                 if (target[DocAcl][userKey]) permission = HierarchyMapping.get(target[DocAcl][userKey])?.name;
-                else if (target.embedContainer) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer))[userKey]);
+                else if (DocCast(target.embedContainer)) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer)!)[userKey]);
                 else permission = StrCast(Doc.GetProto(target)?.[userKey]);
             } else permission = StrCast(target[userKey]);
             individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user
@@ -546,7 +546,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                     if (this.layoutDocAcls) {
                         if (target[DocAcl][groupKey]) {
                             permission = HierarchyMapping.get(target[DocAcl][groupKey])?.name;
-                        } else if (target.embedContainer) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer))[groupKey]);
+                        } else if (DocCast(target.embedContainer)) permission = StrCast(Doc.GetProto(DocCast(target.embedContainer)!)[groupKey]);
                         else permission = StrCast(Doc.GetProto(target)?.[groupKey]);
                     } else permission = StrCast(target[groupKey]);
                     groupTableEntries.unshift(this.sharingItem(StrCast(group.title), showAdmin, permission!, false));
@@ -1921,7 +1921,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
             const selectedItem: boolean = PresBox.Instance.selectedArray.size > 0;
             const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as DocumentType)
                 ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as DocumentType)
-                : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
+                : PresBox.Instance.activeItem
+                  ? PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type
+                  : undefined;
             return (
                 <div className="propertiesView" style={{ width: this._props.width }}>
                     <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index e8876f96f..9110e198f 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -129,7 +129,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
     // prettier-ignore
     switch (property.split(':')[0]) {
         case StyleProp.TreeViewIcon: {
-            const img = ImageCast(doc?.icon ?? doc?.[doc ? Doc.LayoutFieldKey(doc) : ""]);
+            const img = ImageCast(doc?.icon ?? doc?.[doc ? Doc.LayoutDataKey(doc) : ""]);
             if (img) {
                 const ext = extname(img.url.href);
                 const url = doc?.icon ? img.url.href : img.url.href.replace(ext, '_s' + ext);
@@ -164,7 +164,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             return  undefined;
         case StyleProp.DocContents:  return undefined;
         case StyleProp.WidgetColor:  return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
-        case StyleProp.Opacity:      return componentView?.isUnstyledView?.() ? 1 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
+        case StyleProp.Opacity:      return componentView?.isUnstyledView?.() ? 1 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null) ?? null);
         case StyleProp.FontColor:    return StrCast(doc?.[fieldKey + 'fontColor'],  isCaption ? lightOrDark(backgroundCol()) : StrCast(Doc.UserDoc().fontColor, color()));
         case StyleProp.FontSize:     return StrCast(doc?.[fieldKey + 'fontSize'],   StrCast(Doc.UserDoc().fontSize));
         case StyleProp.FontFamily:   return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
@@ -248,13 +248,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 : 0;
         case StyleProp.BackgroundColor: {
             if (SnappingManager.LastPressedBtn === doc?.[Id]) return SnappingManager.userColor; // hack to indicate active menu panel item
-            const dataKey = doc ? Doc.LayoutFieldKey(doc) : '';
+            const dataKey = doc ? Doc.LayoutDataKey(doc) : '';
             const usePath = StrCast(doc?.[dataKey + '_usePath']);
             const alternate = usePath.includes(':hover') ? ( isHovering?.() ? '_' + usePath.replace(':hover','') : '') : usePath ? "_" +usePath:usePath;
             let docColor:Opt<string> = layoutDoc &&
                 StrCast(alternate ? layoutDoc['backgroundColor' + alternate]:undefined,
-                    doc.rootDocument 
-                        ? StrCast(layoutDoc.backgroundColor, StrCast(DocCast(doc.rootDocument).backgroundColor))  // for nested templates: use template's color, then root doc's color
+                    DocCast(doc.rootDocument)
+                        ? StrCast(layoutDoc.backgroundColor, StrCast(DocCast(doc.rootDocument)!.backgroundColor))  // for nested templates: use template's color, then root doc's color
                         : layoutDoc === doc 
                             ? StrCast(doc.backgroundColor)
                             : StrCast(StrCast(Doc.GetT(layoutDoc, 'backgroundColor', 'string', true), StrCast(doc.backgroundColor, StrCast(layoutDoc.backgroundColor)) // otherwise, use expanded template coloor, then root doc's color, then template's inherited color
@@ -262,7 +262,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         
             // prettier-ignore
             switch (layoutDoc?.type) {
-                case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
+                case DocumentType.PRESSLIDE: docColor = docColor || ""; break;
                 case DocumentType.PRES:        docColor = docColor || 'transparent'; break;
                 case DocumentType.FONTICON:    docColor = boxBackground ? undefined : docColor || SnappingManager.userBackgroundColor; break;
                 case DocumentType.RTF:         docColor = docColor || StrCast(Doc.UserDoc().textBackgroundColor, Colors.LIGHT_GRAY); break;
diff --git a/src/client/views/StyleProviderQuiz.tsx b/src/client/views/StyleProviderQuiz.tsx
index b4a1f53c5..fe76bce28 100644
--- a/src/client/views/StyleProviderQuiz.tsx
+++ b/src/client/views/StyleProviderQuiz.tsx
@@ -67,7 +67,7 @@ export namespace styleProviderQuiz {
             newCol.zIndex = 1000;
             newCol.forceActive = true;
             newCol.quiz = text;
-            newCol['$' + Doc.LayoutFieldKey(newCol) + '_transform'] = 'none';
+            newCol['$' + Doc.LayoutDataKey(newCol) + '_transform'] = 'none';
             Doc.AddDocToList(img.Document, 'quizBoxes', newCol);
             img.addDocument(newCol);
             //   img._loading = false;
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 79c4d8af1..40d4f108d 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -21,6 +21,7 @@ import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
 import { IconTagBox } from './nodes/IconTagBox';
 import { Id } from '../../fields/FieldSymbols';
 import { StyleProp } from './StyleProp';
+import { Docs } from '../documents/Documents';
 
 /**
  * The TagsView is a metadata input/display panel shown at the bottom of a DocumentView in a freeform collection.
@@ -171,6 +172,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
         const newEmbeddings = TagItem.allDocsWithTag(this._props.tag).map(doc => Doc.MakeEmbedding(doc));
 
         // Create a new collection and set up configurations.
+        const emptyCol = DocCast(Doc.UserDoc().emptyCollection);
         const newCollection = ((doc: Doc) => {
             doc.$data = new List<Doc>(newEmbeddings);
             doc.$title = this._props.tag;
@@ -182,7 +184,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
             doc.layout_fitWidth = true;
             doc._layout_showTags = true;
             return doc;
-        })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
+        })(emptyCol ? Doc.MakeCopy(emptyCol, true) : Docs.Create.FreeformDocument([], {}));
         newEmbeddings.forEach(embed => Doc.SetContainer(embed, newCollection));
 
         // Add the collection to the tag document's list of associated smart collections.
@@ -342,7 +344,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
         const tagsList = new Set<string>(StrListCast(this.View.dataDoc.tags));
         const chatTagsList = new Set<string>(StrListCast(this.View.dataDoc.tags_chat));
         const facesList = new Set<string>(
-            DocListCast(this.View.dataDoc[Doc.LayoutFieldKey(this.View.Document) + '_annotations'])
+            DocListCast(this.View.dataDoc[Doc.LayoutDataKey(this.View.Document) + '_annotations'])
                 .concat(this.View.Document)
                 .filter(d => d.face)
                 .map(doc => StrCast(DocCast(doc.face)?.title))
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index cd2c7df1b..814e9a7a0 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -92,7 +92,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps & { Doc: D
     private get children(): Doc[] {
         const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this._props.Doc.type) as unknown as DocumentType);
         if (annotatedDoc) {
-            return DocListCast(this._props.Doc[Doc.LayoutFieldKey(this._props.Doc) + '_annotations']);
+            return DocListCast(this._props.Doc[Doc.LayoutDataKey(this._props.Doc) + '_annotations']);
         }
         return DocListCast(this._props.Doc[this._props.fieldKey]);
     }
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 2d3f8cb0e..975dc52fe 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -77,7 +77,8 @@ export class CollectionCarouselView extends CollectionSubView() {
     focus = (anchor: Doc, options: FocusViewOptions): Opt<number> => {
         const docs = DocListCast(this.Document[this.fieldKey]);
         if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) {
-            const newIndex = anchor.config_carousel_index ?? docs.getIndex(DocCast(anchor.annotationOn, anchor));
+            const annoOn = DocCast(anchor.annotationOn, anchor);
+            const newIndex = NumCast(anchor.config_carousel_index, (annoOn && docs.getIndex(annoOn)) ?? 0);
             options.didMove = newIndex !== this.layoutDoc._carousel_index;
             options.didMove && (this.layoutDoc._carousel_index = newIndex);
         }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 094e61db9..ea6259a32 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -473,11 +473,11 @@ export class CollectionDockingView extends CollectionSubView() {
         }
         return undefined;
     }
-    public static async TakeSnapshot(doc: Doc | undefined, clone = false) {
+    public static TakeSnapshot(doc: Doc | undefined, clone = false) {
         if (!doc) return undefined;
         let json = StrCast(doc.dockingConfig);
         if (clone) {
-            const cloned = await Doc.MakeClone(doc);
+            const cloned = Doc.MakeClone(doc);
             Array.from(cloned.map.entries()).forEach(entry => {
                 json = json.replace(entry[0], entry[1][Id]);
             });
@@ -524,11 +524,11 @@ export class CollectionDockingView extends CollectionSubView() {
         this._flush = this._flush ?? UndoManager.StartBatch('tab movement');
         const dashDoc = tab.DashDoc;
         if (dashDoc && ![DocumentType.PRES].includes(dashDoc.type) && !tab.contentItem.config.props.keyValue) {
-            Doc.AddDocToList(Doc.MyHeaderBar, 'data', dashDoc, undefined, undefined, true);
+            Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', dashDoc, undefined, undefined, true);
             // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed
             if (dashDoc.embedContainer === this.Document) dashDoc.embedContainer = undefined;
             if (!dashDoc.embedContainer) {
-                Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true);
+                Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true);
                 Doc.RemoveEmbedding(dashDoc, dashDoc);
             }
         }
@@ -562,7 +562,7 @@ export class CollectionDockingView extends CollectionSubView() {
                     _layout_fitWidth: true,
                     title: `Untitled Tab ${NumCast(dashboard.$myPaneCount)}`,
                 });
-                Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
+                Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
                 inheritParentAcls(this.Document, docToAdd, false);
                 CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
             }
@@ -579,7 +579,7 @@ export class CollectionDockingView extends CollectionSubView() {
                     _freeform_backgroundGrid: true,
                     title: `Untitled Tab ${NumCast(dashboard.$myPaneCount)}`,
                 });
-                Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
+                Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
                 inheritParentAcls(this.dataDoc, docToAdd, false);
                 CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
             }
@@ -668,7 +668,5 @@ ScriptingGlobals.add(
 );
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(async function snapshotDashboard() {
-    const batch = UndoManager.StartBatch('snapshot');
-    await CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard);
-    batch.end();
+    undoable(() => CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard), 'snapshot dashboard');
 }, 'creates a snapshot copy of a dashboard');
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 173147f64..7f639a11e 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -321,11 +321,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth);
     };
 
-    // how to get the height of a document. Nothing special here.
     getDocHeight(d?: Doc) {
         if (!d || d.hidden) return 0;
-        const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
-        const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument;
+        const childLayoutDoc = Doc.LayoutDoc(d, this._props.childLayoutTemplate?.());
+        const childDataDoc = d.isTemplateDoc || d.isTemplateForField ? this._props.TemplateDataDocument : undefined;
         const maxHeight = (lim => (lim === 0 ? this._props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
         const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this._props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0);
         const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this._props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0);
@@ -566,7 +565,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
 
     @undoBatch
     remColumn = (value: SchemaHeaderField) => {
-        const colHdrData = Array.from(Cast(this.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
+        const colHdrData = Array.from(Cast(this.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null) ?? []);
         if (value) {
             const index = colHdrData.indexOf(value);
             index !== -1 && colHdrData.splice(index, 1);
@@ -585,7 +584,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
             }
             return undefined;
         });
-        const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
+        const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null) ?? []);
         const newColWidth = 1 / (this.numGroupColumns + 1);
         columnHeaders.push(new SchemaHeaderField(value, undefined, undefined, newColWidth));
         value && this.resizeColumns(columnHeaders);
@@ -642,7 +641,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         leftHeader.setWidth(leftHeader.width + movementX / this.availableWidth);
         rightHeader.setWidth(rightHeader.width - movementX / this.availableWidth);
         const headers = Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null);
-        headers.splice(headers.indexOf(leftHeader), 1, leftHeader[Copy]());
+        headers?.splice(headers.indexOf(leftHeader), 1, leftHeader[Copy]());
     };
 
     // renderedSections returns a list of all of the JSX elements used (columns and dividers). If the view
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 112510265..9155227dd 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -315,8 +315,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             e.stopPropagation?.();
             const layoutFieldKey = StrCast(textBox.fieldKey);
             const newDoc = Doc.MakeCopy(textBox.Document, true);
-            const dataField = textBox.Document[Doc.LayoutFieldKey(newDoc)];
-            newDoc['$' + Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
+            const dataField = textBox.Document[Doc.LayoutDataKey(newDoc)];
+            newDoc['$' + Doc.LayoutDataKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
             if (layoutFieldKey !== 'layout' && textBox.Document[layoutFieldKey] instanceof Doc) {
                 newDoc[layoutFieldKey] = textBox.Document[layoutFieldKey];
             }
@@ -340,7 +340,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     isChildButtonContentActive = () => (this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined);
     @observable docRefs = new ObservableMap<Doc, DocumentView>();
-    childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null));
+    childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null) ?? null);
     // this is what renders the document that you see on the screen
     // called in Children: this actually adds a document to our children list
     getDisplayDoc(doc: Doc, trans: () => string, count: number) {
@@ -408,7 +408,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     });
     getDocWidth = computedFn((d?: Doc) => () => {
         if (!d) return 0;
-        const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
+        const childLayoutDoc = Doc.LayoutDoc(d, this._props.childLayoutTemplate?.());
         const maxWidth = this.columnWidth / this.numGroupColumns;
         if (!this.layoutDoc._columnsFill && !this.childFitWidth(childLayoutDoc)) {
             return Math.min(NumCast(d._width), maxWidth);
@@ -418,8 +418,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     getDocTransition = computedFn((d?: Doc) => () => StrCast(d?.dataTransition));
     getDocHeight = computedFn((d?: Doc) => () => {
         if (!d || d.hidden) return 0;
-        const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
-        const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument;
+        const childLayoutDoc = Doc.LayoutDoc(d, this._props.childLayoutTemplate?.());
+        const childDataDoc = d.isTemplateDoc || d.isTemplateForField ? this._props.TemplateDataDocument : undefined;
         const maxHeight = (lim => (lim === 0 ? this._props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
         const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._width) : 0);
         const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._height) : 0);
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 66839ba7f..994669734 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -274,7 +274,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                             const container = DocCast(this._props.Doc.rootDocument)?.[DocData] ? Doc.GetProto(this._props.Doc) : this._props.Doc;
                             if (container.isTemplateDoc) {
                                 Doc.MakeMetadataFieldTemplate(created, container);
-                                return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
+                                return Doc.AddDocToList(container, Doc.LayoutDataKey(container), created);
                             }
                             return this._props.addDocument?.(created) || false;
                         }
@@ -316,7 +316,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                 ref={this._headerRef}
                 style={{
                     marginTop: this._props.yMargin,
-                    width: this._props.columnWidth
+                    width: this._props.columnWidth,
                 }}>
                 {/* the default bucket (no key value) has a tooltip that describes what it is.
                     Further, it does not have a color and cannot be deleted. */}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 375c0fe53..bc7d6f897 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -3,7 +3,7 @@ import * as React from 'react';
 import * as rp from 'request-promise';
 import { ClientUtils, DashColor, returnFalse } from '../../../ClientUtils';
 import CursorField from '../../../fields/CursorField';
-import { Doc, DocListCast, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, expandedFieldName, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc';
 import { AclPrivate, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
@@ -132,16 +132,23 @@ export function CollectionSubView<X>() {
 
         hasChildDocs = () => this.childLayoutPairs.map(pair => pair.layout);
         @computed get childLayoutPairs(): { layout: Doc; data: Doc }[] {
-            const { TemplateDataDocument } = this._props;
-            const validPairs = this.childDocs
-                .map(doc => Doc.GetLayoutDataDocPair(this.Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
-                .filter(
-                    pair =>
-                        // filter out any documents that have a proto that we don't have permissions to
-                        !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate))
-                )
-                .filter(pair => !this._filterFunc?.(pair.layout!));
-            return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
+            const lastEle = this.DocumentView?.();
+            if (!lastEle?.IsInvalid(this.Document)) {
+                const rootTemplate = lastEle && Doc.LayoutDoc(lastEle.rootDoc).isTemplateDoc && Doc.LayoutDoc(lastEle.rootDoc);
+                const templateFieldKey = rootTemplate &&
+                    [expandedFieldName(rootTemplate),
+                        ...this._props.docViewPath()
+                            .filter(dv => dv.Document.isTemplateForField)
+                            .map(dv => dv.Document.title),
+                    ].join('_'); // prettier-ignore
+                return this.childDocs
+                    .map(doc => Doc.GetLayoutDataDocPair(this.Document, !this._props.isAnnotationOverlay ? this._props.TemplateDataDocument : undefined, doc, templateFieldKey || ""))
+                    .filter(pair => // filter out any documents that have a proto that we don't have permissions to
+                        !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate)))
+                    .filter(pair => !this._filterFunc?.(pair.layout!))
+                    .map(({ data, layout }) => ({ data: data!, layout: layout! })); // prettier-ignore
+            }
+            return [];
         }
         /**
          * This is the raw, stored list of children on a collection.  If you modify this list, the database will be updated
@@ -161,7 +168,7 @@ export function CollectionSubView<X>() {
         };
 
         collectionFilters = () => this._focusFilters ?? StrListCast(this.Document._childFilters);
-        collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.Document._childFiltersByRanges, listSpec('string'), []);
+        collectionRangeDocFilters = () => this._focusRangeFilters ?? StrListCast(this.Document._childFiltersByRanges);
         // child filters apply to the descendants of the documents in this collection
         childDocFilters = () => [...(this._props.childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
         // unrecursive filters apply to the documents in the collection, but no their children.  See Utils.noRecursionHack
@@ -178,7 +185,7 @@ export function CollectionSubView<X>() {
                 rawdocs = [this.dataField];
             } else if (Cast(this.dataField, listSpec(Doc), null)) {
                 // otherwise, if the collection data is a list, then use it.
-                rawdocs = Cast(this.dataField, listSpec(Doc), null);
+                rawdocs = DocListCast(this.dataField);
             } else if (this.dataField) {
                 // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc.
                 // For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection.
@@ -206,7 +213,7 @@ export function CollectionSubView<X>() {
                 let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0;
                 if (notFiltered) {
                     notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0;
-                    const fieldKey = Doc.LayoutFieldKey(d);
+                    const fieldKey = Doc.LayoutDataKey(d);
                     const isAnnotatableDoc = d[fieldKey] instanceof List && !(d[fieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
                     const docChildDocs = d[isAnnotatableDoc ? fieldKey + '_annotations' : fieldKey];
                     const sidebarDocs = isAnnotatableDoc && d[fieldKey + '_sidebar'];
@@ -219,7 +226,7 @@ export function CollectionSubView<X>() {
                                 newarray = [];
                                 // eslint-disable-next-line no-loop-func
                                 subDocs.forEach(t => {
-                                    const docFieldKey = Doc.LayoutFieldKey(t);
+                                    const docFieldKey = Doc.LayoutDataKey(t);
                                     const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
                                     notFiltered =
                                         notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length));
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 568a08792..4348bc7dc 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -15,7 +15,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { FieldId } from '../../../fields/RefField';
 import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, NumCast, StrCast, toList } from '../../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast, toList } from '../../../fields/Types';
 import { DocServer } from '../../DocServer';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
@@ -145,7 +145,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
                     select={emptyFunction}
                     isSelected={returnFalse}
                     dontRegisterView
-                    fieldKey={Doc.LayoutFieldKey(this._props.doc)}
+                    fieldKey={Doc.LayoutDataKey(this._props.doc)}
                     addDocument={returnFalse}
                     moveDocument={returnFalse}
                     removeDocument={returnFalse}
@@ -173,7 +173,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
         );
     };
     render() {
-        return this._props.doc.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.doc)) || this._props.doc?._type_collection !== CollectionViewType.Freeform ? null : (
+        return this._props.doc.layout !== CollectionView.LayoutString(Doc.LayoutDataKey(this._props.doc)) || this._props.doc?._type_collection !== CollectionViewType.Freeform ? null : (
             <div className="miniMap-hidden">
                 <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
             </div>
@@ -207,10 +207,10 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         const docs = toList(docIn);
 
         const batch = UndoManager.StartBatch('Pin doc to pres trail');
-        const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);
+        const curPres = Doc.ActivePresentation ?? (DocCast(Doc.UserDoc().emptyTrail) ? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail)!, true) : Docs.Create.PresDocument({}));
 
         if (!Doc.ActivePresentation) {
-            Doc.AddDocToList(Doc.MyTrails, 'data', curPres);
+            Doc.MyTrails && Doc.AddDocToList(Doc.MyTrails, 'data', curPres);
             Doc.ActivePresentation = curPres;
         }
 
@@ -236,7 +236,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
             pinDoc.treeView_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
             pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
             pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
-            const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null);
+            const duration = NumCast(doc[`${Doc.LayoutDataKey(pinDoc)}_duration`], null);
 
             if (pinProps.pinViewport) PinDocView(pinDoc, pinProps, anchorDoc ?? doc);
             if (!pinProps?.audioRange && duration !== undefined) {
@@ -566,7 +566,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         return false;
     };
 
-    getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
+    getCurrentFrame = () => NumCast(DocCast(PresBox.Instance.activeItem?.presentation_targetDoc)?._currentFrame);
     focusFunc = () => {
         if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
             this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 4dc937864..4beb75074 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -159,7 +159,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         return this.Document[DocLayout];
     }
     @computed get fieldKey() {
-        return StrCast(this.Document._treeView_FieldKey, Doc.LayoutFieldKey(this.Document));
+        return StrCast(this.Document._treeView_FieldKey, Doc.LayoutDataKey(this.Document));
     }
     @computed get childDocs() {
         return this.childDocList(this.fieldKey);
@@ -186,7 +186,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     moving: boolean = false;
     @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
         if (this.Document !== target && addDoc !== returnFalse) {
-            const canAdd1 = (this._props.parentTreeView as TreeView).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField);
+            const canAdd1 = (this._props.parentTreeView as TreeView).dropping || !(ComputedField.DisableCompute(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField);
 
             // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse
             if (canAdd1 && this._props.removeDoc?.(doc) === true) {
@@ -431,9 +431,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
 
     localAdd = (docs: Doc | Doc[]): boolean => {
         const innerAdd = (doc: Doc): boolean => {
-            const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
+            const dataIsComputed = ComputedField.DisableCompute(() => 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));
+            dataIsComputed && DocCast(this.Document.embedContainer) && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)!);
             return added;
         };
         return toList(docs).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
@@ -511,9 +511,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 const moveDoc = (docs: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(docs, target, addDoc);
                 const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => {
                     const innerAdd = (iDoc: Doc) => {
-                        const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
+                        const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
                         const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, iDoc, addBefore, before, false, true);
-                        dataIsComputed && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer));
+                        dataIsComputed && DocCast(this.Document.embedContainer) && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer)!);
                         return added;
                     };
                     return toList(docs).reduce((flg, iDoc) => flg && innerAdd(iDoc), true as boolean);
@@ -633,7 +633,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                         d.zIndex = i;
                     });
                 }
-                const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
+                const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
                 const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false);
                 !dataIsComputed && added && Doc.SetContainer(doc, this.Document);
 
@@ -883,8 +883,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         ];
     };
     childContextMenuItems = () => {
-        const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []);
-        const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []);
+        const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), [])!;
+        const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), [])!;
         const icons = StrListCast(this.Document.childContextMenuIcons);
         return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
     };
@@ -1296,7 +1296,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
 
                 const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => {
                     if (parent instanceof TreeView && parent._props.treeView.fileSysMode && !newParent.isFolder) return;
-                    const fieldKey = Doc.LayoutFieldKey(newParent);
+                    const fieldKey = Doc.LayoutDataKey(newParent);
                     if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) {
                         remove(child);
                         DocumentView.SetSelectOnLoad(child);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 626538976..842293358 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -275,7 +275,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     elementFunc = () => this._layoutElements;
     viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined);
-    panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null)));
+    panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : (Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null) ?? null) ?? ''));
     fitContentOnce = () => {
         const { cx, cy, scale } = this.contentBounds(); // prettier-ignore
         this.layoutDoc._freeform_panX = cx;
@@ -388,7 +388,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (options.easeFunc) this.setPresEaseFunc(options.easeFunc);
         if (this._lightboxDoc) return undefined;
         if (options.pointFocus) return this.focusOnPoint(options);
-        const anchorInCollection = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor);
+        const anchorInCollection = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutDataKey(this.Document)]).includes(anchor);
         const anchorInChildViews = this.childLayoutPairs.map(pair => pair.layout).includes(anchor);
         if (!anchorInCollection && !anchorInChildViews) {
             return undefined;
@@ -1308,7 +1308,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         SnappingManager.TriggerUserPanned();
         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 docHeight = NumCast(this.Document[Doc.LayoutDataKey(this.Document) + '_nativeHeight'], this.nativeHeight);
         const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling + 1e-4;
         switch (
             !e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?//
@@ -1489,12 +1489,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
      */
     createTextDocCopy = undoable((textBox: FormattedTextBox, below: boolean) => {
         const textDoc = DocCast(textBox.Document);
-        const newDoc = Doc.MakeCopy(textDoc, true);
-        newDoc['$' + Doc.LayoutFieldKey(newDoc)] = undefined; // the copy should not copy the text contents of it source, just the render style
-        newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
-        newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
-        DocumentView.SetSelectOnLoad(newDoc);
-        return this.addDocument?.(newDoc);
+        if (textDoc) {
+            const newDoc = Doc.MakeCopy(textDoc, true);
+            newDoc['$' + Doc.LayoutDataKey(newDoc)] = undefined; // the copy should not copy the text contents of it source, just the render style
+            newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10);
+            newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0);
+            DocumentView.SetSelectOnLoad(newDoc);
+            return this.addDocument?.(newDoc);
+        }
+        return false;
     }, 'copied text note');
 
     onKey = (e: KeyboardEvent, textBox: FormattedTextBox) => {
@@ -1741,7 +1744,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         if (addAsAnnotation) {
             if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
-                Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
+                Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), [])?.push(anchor);
             } else {
                 this.dataDoc[this._props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
             }
@@ -1985,7 +1988,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             optionItems.push({
                 description: 'Use Background Color as Default',
                 event: () => {
-                    Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor);
+                    DocCast(Doc.UserDoc().emptyCollection) && (DocCast(Doc.UserDoc().emptyCollection)!.backgroundColor = StrCast(this.layoutDoc.backgroundColor));
                 },
                 icon: 'palette',
             });
@@ -2085,7 +2088,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30);
     lightboxScreenToLocal = () => this.ScreenToLocalBoxXf().translate(-15, -15);
     onPassiveWheel = (e: WheelEvent) => {
-        const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
+        const docHeight = NumCast(this.Document[Doc.LayoutDataKey(this.Document) + '_nativeHeight'], this.nativeHeight);
         const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling;
         this._props.isSelected() && !scrollable && e.preventDefault();
     };
@@ -2108,7 +2111,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             </div>
         );
     }
-    transitionFunc = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms ${this._presEaseFunc}` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null)));
+    transitionFunc = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms ${this._presEaseFunc}` : (Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null) ?? null) ?? ''));
     get pannableContents() {
         this.incrementalRender(); // needs to happen synchronously or freshly typed text documents will flash and miss their first characters
         return (
@@ -2385,7 +2388,7 @@ ScriptingGlobals.add(function datavizFromSchema() {
         const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key !== 'text');
         if (!keys) return;
 
-        const children = DocListCast(view.Document[Doc.LayoutFieldKey(view.Document)]);
+        const children = DocListCast(view.Document[Doc.LayoutDataKey(view.Document)]);
         const csvRows = [];
         csvRows.push(keys.join(','));
         for (let i = 0; i < children.length; i++) {
diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
index c7aa410c6..72485aa86 100644
--- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
@@ -187,7 +187,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
                         })}>
                         {FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => {
-                            const [name, type] = ImageCastToNameType(doc[Doc.LayoutFieldKey(doc)]) ?? ['-missing-', '.png'];
+                            const [name, type] = ImageCastToNameType(doc[Doc.LayoutDataKey(doc)]) ?? ['-missing-', '.png'];
                             return (
                                 <div
                                     className="image-wrapper"
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
index 7c8ccb92d..ff9fb14e7 100644
--- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
@@ -165,7 +165,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
         const imageInfos = this._selectedImages.map(async doc => {
             if (!doc.$tags_chat) {
-                const url = ImageCastWithSuffix(doc[Doc.LayoutFieldKey(doc)], '_o') ?? '';
+                const url = ImageCastWithSuffix(doc[Doc.LayoutDataKey(doc)], '_o') ?? '';
                 return imageUrlToBase64(url).then(hrefBase64 =>
                 !hrefBase64 ? undefined : 
                     gptImageLabel(hrefBase64,'Give three labels to describe this image.').then(labels => 
@@ -310,7 +310,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 {this._displayImageInformation ? (
                     <div className="image-information-list">
                         {this._selectedImages.map(doc => {
-                            const [name, type] = ImageCastToNameType(doc[Doc.LayoutFieldKey(doc)]);
+                            const [name, type] = ImageCastToNameType(doc[Doc.LayoutDataKey(doc)]);
                             return (
                                 <div className="image-information" style={{ borderColor: SettingsManager.userColor }} key={Utils.GenerateGuid()}>
                                     <img
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 5fbf72f39..3cc7c0f2d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -8,7 +8,7 @@ import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSy
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkData, InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
-import { Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { GetEffectiveAcl } from '../../../../fields/util';
 import { DocUtils } from '../../../documents/DocUtils';
@@ -376,7 +376,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
                   doc.$title = makeGroup ? 'grouping' : 'nested freeform';
                   doc._freeform_panX = doc._freeform_panY = 0;
                   return doc;
-              })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
+              })(DocCast(Doc.UserDoc().emptyCollection) ? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyCollection)!, true) : Docs.Create.FreeformDocument([], {}));
         newCollection.isSystem = undefined;
         newCollection._width = bounds.width || 1; // if width/height are unset/0, then groups won't autoexpand to contain their children
         newCollection._height = bounds.height || 1;
@@ -437,7 +437,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
      */
     @undoBatch
     classifyImages = async () => {
-        const groupButton = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImageGrouper);
+        const groupButton = DocListCast(Doc.MyLeftSidebarMenu?.data).find(d => d.target === Doc.MyImageGrouper);
         if (groupButton) {
             this._selectedDocs = this.marqueeSelect(false, DocumentType.IMG);
             ImageLabelBoxData.Instance.setData(this._selectedDocs);
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 80116dd2f..8d3947653 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -102,7 +102,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
     getLinkUI = () =>
         !DocumentLinksButton.StartLink ? null : (
-            <span className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}>
+            <span key="-link-ui-" className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}>
                 <span className="bottomPopup-text">
                     Creating link from:{' '}
                     <b>
@@ -126,7 +126,7 @@ export class CollectionLinearView extends CollectionSubView() {
         );
     getCurrentlyPlayingUI = () =>
         !DocumentView.CurrentlyPlaying?.length ? null : (
-            <span className="bottomPopup-background">
+            <span key="-currently-playing-" className="bottomPopup-background">
                 <span className="bottomPopup-text">
                     Currently playing:
                     {DocumentView.CurrentlyPlaying.map((clip, i) => (
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 5803acca0..82ca96839 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1193,7 +1193,7 @@ export class CollectionSchemaView extends CollectionSubView() {
             let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0;
             if (notFiltered) {
                 notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0;
-                const fieldKey = Doc.LayoutFieldKey(d);
+                const fieldKey = Doc.LayoutDataKey(d);
                 const isAnnotatableDoc = d[fieldKey] instanceof List && !(d[fieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
                 const docChildDocs = d[isAnnotatableDoc ? fieldKey + '_annotations' : fieldKey];
                 const sidebarDocs = isAnnotatableDoc && d[fieldKey + '_sidebar'];
@@ -1206,7 +1206,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                             newarray = [];
                             // eslint-disable-next-line no-loop-func
                             subDocs.forEach(t => {
-                                const docFieldKey = Doc.LayoutFieldKey(t);
+                                const docFieldKey = Doc.LayoutDataKey(t);
                                 const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
                                 notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length));
                                 DocListCast(t[isSubDocAnnotatable ? docFieldKey + '_annotations' : docFieldKey]).forEach(newdoc => newarray.push(newdoc));
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 173984dc7..8e1edc1ee 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -326,7 +326,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro
         const field = Cast(this._props.Doc[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
         const alts = DocListCast(this._props.Doc[this._props.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
         const altpaths = alts
-            .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
+            .map(doc => Cast(doc[Doc.LayoutDataKey(doc)], ImageField, null)?.url)
             .filter(url => url)
             .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
         const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 32afb3d3d..f4beb1004 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -76,7 +76,7 @@ ScriptingGlobals.add(function setBorderColor(color?: string, checkResult?: boole
             const selView = selectedViews.lastElement();
             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] || defaultBorder();
+            return (contentFrameNumber !== undefined && CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey]) || defaultBorder();
         }
         setDefaultBorder(color ?? 'transparent');
         selectedViews.forEach(dv => {
@@ -87,7 +87,7 @@ ScriptingGlobals.add(function setBorderColor(color?: string, checkResult?: boole
                 obj[fieldKey] = color;
                 CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj);
             } else {
-                const dataKey = Doc.LayoutFieldKey(dv.Document);
+                const dataKey = Doc.LayoutDataKey(dv.Document);
                 const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', '');
                 dv.layoutDoc[fieldKey + alternate] = undefined;
                 dv.dataDoc[fieldKey + alternate] = color;
@@ -133,7 +133,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
                 CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj);
             } else {
                 const colorDoc = dv.isTemplateForField ? dv.layoutDoc : dv.dataDoc; // assigning to a template's compoment field should not assign to the data doc
-                const dataKey = Doc.LayoutFieldKey(colorDoc);
+                const dataKey = Doc.LayoutDataKey(colorDoc);
                 const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', '');
                 colorDoc[fieldKey + alternate] = color;
             }
@@ -158,7 +158,7 @@ ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) {
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
     if (checkResult) {
-        return DocumentView.Selected().length ? StrCast(DocumentView.SelectedDocs().lastElement().layout_headingColor) : Doc.SharingDoc().headingColor;
+        return DocumentView.Selected().length ? StrCast(DocumentView.SelectedDocs().lastElement().layout_headingColor) : Doc.SharingDoc()?.headingColor;
     }
     if (DocumentView.Selected().length) {
         DocumentView.SelectedDocs().forEach(doc => {
@@ -166,8 +166,11 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
             doc.layout_showTitle = color === 'transparent' ? undefined : StrCast(doc.layout_showTitle, 'title');
         });
     } else {
-        Doc.SharingDoc().headingColor = undefined;
-        Doc.GetProto(Doc.SharingDoc()).headingColor = color === 'transparent' ? undefined : color;
+        const sharing = Doc.SharingDoc();
+        if (sharing) {
+            sharing.headingColor = undefined;
+            Doc.GetProto(sharing).headingColor = color === 'transparent' ? undefined : color;
+        }
         Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'title');
     }
     return undefined;
@@ -234,30 +237,30 @@ ScriptingGlobals.add(function showFreeform(
             setDoc: (doc: Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; },
         }],
         ['time', {
-            checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "time",
-            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "time" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Time}, // prettier-ignore
+            checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutDataKey(doc)+"_sort"]) === "time",
+            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutDataKey(doc)+"_sort"] === "time" ? doc[Doc.LayoutDataKey(doc)+"_sort"] = '' : doc[Doc.LayoutDataKey(doc)+"_sort"] = docSortings.Time}, // prettier-ignore
         }],
         ['docType', {
-            checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "type",
-            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "type" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Type}, // prettier-ignore
+            checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutDataKey(doc)+"_sort"]) === "type",
+            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutDataKey(doc)+"_sort"] === "type" ? doc[Doc.LayoutDataKey(doc)+"_sort"] = '' : doc[Doc.LayoutDataKey(doc)+"_sort"] = docSortings.Type}, // prettier-ignore
         }],
         ['color', {
-            checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "color",
-            setDoc: (doc: Doc, dv: DocumentView) => { doc?.[Doc.LayoutFieldKey(doc)+"_sort"] === "color" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Color}, // prettier-ignore
+            checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutDataKey(doc)+"_sort"]) === "color",
+            setDoc: (doc: Doc, dv: DocumentView) => { doc?.[Doc.LayoutDataKey(doc)+"_sort"] === "color" ? doc[Doc.LayoutDataKey(doc)+"_sort"] = '' : doc[Doc.LayoutDataKey(doc)+"_sort"] = docSortings.Color}, // prettier-ignore
         }],
         ['tag', {
-            checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "tag",
-            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "tag" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Tag}, // prettier-ignore
+            checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutDataKey(doc)+"_sort"]) === "tag",
+            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutDataKey(doc)+"_sort"] === "tag" ? doc[Doc.LayoutDataKey(doc)+"_sort"] = '' : doc[Doc.LayoutDataKey(doc)+"_sort"] = docSortings.Tag}, // prettier-ignore
         }],
         ['reverse', {
-            checkResult: (doc: Doc) => BoolCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort_reverse"]),
-            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_reverse"] = !doc[Doc.LayoutFieldKey(doc)+"_sort_reverse"]; },
+            checkResult: (doc: Doc) => BoolCast(doc?.[Doc.LayoutDataKey(doc)+"_sort_reverse"]),
+            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutDataKey(doc)+"_sort_reverse"] = !doc[Doc.LayoutDataKey(doc)+"_sort_reverse"]; },
         }],
         ['toggle-chat', {
             checkResult: (doc: Doc) => SnappingManager.ChatVisible,
             setDoc: (doc: Doc, dv: DocumentView) => {
                 if (SnappingManager.ChatVisible){
-                    doc[Doc.LayoutFieldKey(doc)+"_sort"] = '';
+                    doc[Doc.LayoutDataKey(doc)+"_sort"] = '';
                     SnappingManager.SetChatVisible(false);
                 } else {
                     SnappingManager.SetChatVisible(true);
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index b060fc0b6..87cef01d2 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -77,7 +77,7 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
             NewLightboxView.SetNewLightboxDoc(
                 doc,
                 undefined,
-                [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(NewLightboxView._future ?? [])].sort(
+                [...DocListCast(doc[Doc.LayoutDataKey(doc)]), ...DocListCast(doc[Doc.LayoutDataKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(NewLightboxView._future ?? [])].sort(
                     (a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)
                 ),
                 layoutTemplate
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index e0d59cc9d..d4c512342 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -396,7 +396,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             returnFalse,
             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);
+                const textField = Doc.LayoutDataKey(newDoc);
                 const newDocData = newDoc[DocData];
                 newDocData[`${textField}_recordingSource`] = this.dataDoc;
                 newDocData[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 53a3d3631..940c4cb99 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -71,7 +71,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         { key: 'freeform_panY' },
     ]; // fields that are configured to be animatable using animation frames
     public static animStringFields = ['backgroundColor', 'borderColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
-    public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
+    public static animDataFields = (doc: Doc) => (Doc.LayoutDataKey(doc) ? [Doc.LayoutDataKey(doc)] : []); // fields that are configured to be animatable using animation frames
     public static from(dv?: DocumentView): CollectionFreeFormDocumentView | undefined {
         return dv?._props.reactParent instanceof CollectionFreeFormDocumentView ? dv._props.reactParent : undefined;
     }
@@ -187,7 +187,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
     public static gotoKeyFrame(doc: Doc, newFrame: number) {
         if (doc) {
-            const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]);
+            const childDocs = DocListCast(doc[Doc.LayoutDataKey(doc)]);
             const currentFrame = Cast(doc._currentFrame, 'number', null);
             if (currentFrame === undefined) {
                 doc._currentFrame = 0;
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 117eb05f8..c80e8ebc1 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -352,7 +352,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
                 if (!getFrom?.schema_columnKeys) return undefined;
                 const keys = StrListCast(getFrom?.schema_columnKeys).filter(key => key !== 'text');
-                const children = DocListCast(getFrom?.[Doc.LayoutFieldKey(getFrom)]);
+                const children = DocListCast(getFrom?.[Doc.LayoutDataKey(getFrom)]);
                 const current: { [key: string]: string }[] = [];
                 children
                     .filter(child => child)
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 6f004bed3..504c1491e 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -188,8 +188,8 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte
             this._props.LayoutTemplate?.() ||
             (this._props.LayoutTemplateString && this._props.Document) ||
             (this._props.layoutFieldKey && StrCast(this._props.Document[this._props.layoutFieldKey]) && this._props.Document) ||
-            Doc.Layout(this._props.Document, DocCast(this._props.Document[this._props.layoutFieldKey]));
-        return Doc.expandTemplateLayout(template, this._props.Document);
+            Doc.LayoutDoc(this._props.Document, DocCast(this._props.Document[this._props.layoutFieldKey]));
+        return Doc.expandTemplateLayout(template, this._props.Document, this._props.layoutFieldKey);
     }
 
     CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d88d8c44d..521934152 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -169,9 +169,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
     }
 
     componentDidMount() {
-        runInAction(() => {
-            this._mounted = true;
-        });
+        runInAction(() => (this._mounted = true));
         this.setupHandlers();
         this._disposers.contentActive = reaction(
             () =>
@@ -183,16 +181,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                     : Doc.ActiveTool !== InkTool.None || SnappingManager.CanEmbed || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive()
                       ? true
                       : undefined,
-            active => {
-                this._isContentActive = active;
-            },
+            active => (this._isContentActive = active),
             { fireImmediately: true }
         );
         this._disposers.pointerevents = reaction(
             () => this.style(this.Document, StyleProp.PointerEvents) as Property.PointerEvents | undefined,
-            pointerevents => {
-                this._pointerEvents = pointerevents;
-            },
+            pointerevents => (this._pointerEvents = pointerevents),
             { fireImmediately: true }
         );
     }
@@ -305,7 +299,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                 };
                 const clickFunc = this.onClickFunc?.()?.script ? () => (this.onClickFunc?.()?.script.run(scriptProps, console.log).result as Opt<{ select: boolean }>)?.select && this._props.select(false) : undefined;
                 if (!clickFunc) {
-                    // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
+                    // onDragStart implies a button doc that we don't want to select when clicking.
                     if (this.layoutDoc.onDragStart && !(e.ctrlKey || e.button > 0)) stopPropagate = false;
                     preventDefault = false;
                 }
@@ -502,7 +496,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
         const items = this._props.styleProvider?.(this.Document, this._props, StyleProp.ContextMenuItems) as ContextMenuProps[];
         items?.forEach(item => ContextMenu.Instance.addItem(item));
 
-        const customScripts = Cast(this.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' })
         );
@@ -536,9 +530,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                 zorderItems.push({
                     description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
                     event: undoable(
-                        action(() => {
-                            this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged;
-                        }),
+                        action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged)),
                         'set zIndex drag'
                     ),
                     icon: 'hand-point-up',
@@ -663,7 +655,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
             .scale(this.viewingAiEditor() ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1);
     onClickFunc = () => (this.disableClickScriptFunc ? undefined : this.onClickHdlr);
     setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height + 2 * NumCast(this.Document.borderWidth))); } // prettier-ignore
-    setContentView = action((view: ViewBoxInterface<FieldViewProps>) => { this._componentView = view; }); // prettier-ignore
+    setContentView = action((view: ViewBoxInterface<FieldViewProps>) => (this._componentView = view));
     isContentActive = (): boolean | undefined => this._isContentActive;
     childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
 
@@ -763,6 +755,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
     }
     widgetOverlayFunc = () => (this.widgetDecorations ? this.widgetOverlay : null);
     viewingAiEditor = () => (this._props.showAIEditor && this._componentView?.componentAIView?.() !== undefined ? this.aiEditor : null);
+    _contentsRef = React.createRef<DocumentContentsView>();
     @computed get viewBoxContents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
@@ -778,6 +771,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                     }}>
                     <DocumentContentsView
                         {...this._props}
+                        ref={this._contentsRef}
                         layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
                         pointerEvents={this.contentPointerEvents}
                         setContentViewBox={this.setContentView}
@@ -812,8 +806,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
     captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
     fieldsDropdown = (placeholder: string) => (
         <div
-            ref={r => { r && runInAction(() => (this._titleDropDownInnerWidth = DivWidth(r)));}} // prettier-ignore
-            onPointerDown={action(() => { this._changingTitleField = true; })} // prettier-ignore
+            ref={action((r:HTMLDivElement|null) => r && (this._titleDropDownInnerWidth = DivWidth(r)))} // prettier-ignore
+            onPointerDown={action(() => (this._changingTitleField = true))}
             style={{ width: 'max-content', background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
             <FieldsDropdown
                 Doc={this.Document}
@@ -826,7 +820,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                     }
                     this._changingTitleField = false;
                 })}
-                menuClose={action(() => { this._changingTitleField = false; })} // prettier-ignore
+                menuClose={action(() => (this._changingTitleField = false))}
             />
         </div>
     );
@@ -842,7 +836,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
         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, SnappingManager.userBackgroundColor)
+            StrCast(Doc.SharingDoc()?.headingColor, SnappingManager.userBackgroundColor)
             // )
         );
         const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0;
@@ -1028,7 +1022,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
         root: Doc
     ) {
         const effectDirection = (presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection) as PresEffectDirection;
-        const duration = Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null));
+        const duration = Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null) ?? null);
         const effectProps = {
             left: effectDirection === PresEffectDirection.Left,
             right: effectDirection === PresEffectDirection.Right,
@@ -1143,7 +1137,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     public static GetDocImage(doc?: Doc) {
         return DocumentView.getDocumentView(doc)
             ?.ComponentView?.updateIcon?.()
-            .then(() => ImageCast(doc!.icon, ImageCast(doc![Doc.LayoutFieldKey(doc!)])));
+            .then(() => ImageCast(doc!.icon, ImageCast(doc![Doc.LayoutDataKey(doc!)])));
     }
 
     public get displayName() {  return 'DocumentView(' + (this.Document?.title??"") + ')';  } // prettier-ignore
@@ -1181,6 +1175,24 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     @observable public TagPanelHeight = 0;
     @observable public TagPanelEditing = false;
 
+    /**
+     * Tests whether the component Doc being rendered matches the Doc that this view thinks its rendering.
+     * When switching the layout_fieldKey, component views may react before the internal DocumentView has re-rendered.
+     * If this happens, the component view may try to write invalid data, such as when expanding a template (which
+     * depends on the DocumentView being in synch with the subcomponents).
+     * @param renderDoc sub-component Doc being rendered
+     * @returns boolean whether sub-component Doc is in synch with the layoutDoc that this view thinks its rendering
+     */
+    IsInvalid = (renderDoc?: Doc): boolean => {
+        const docContents = this._docViewInternal?._contentsRef.current;
+        return !(
+            (!renderDoc ||
+                (docContents?.layoutDoc === renderDoc && //
+                    !this.docViewPath().some(dv => dv.IsInvalid()))) &&
+            docContents?._props.layoutFieldKey === this.Document.layout_fieldKey
+        );
+    };
+
     @computed get showTags() {
         return this.Document._layout_showTags || this._props.showTags;
     }
@@ -1250,7 +1262,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentView.removeView(this);
     }
 
-    public set IsSelected(val)  { runInAction(() => { this._selected = val; }); } // prettier-ignore
+    public set IsSelected(val)  { runInAction(() => (this._selected = val)) } // prettier-ignore
     public get IsSelected()     { return this._selected; } // prettier-ignore
     public get IsContentActive(){ return this._docViewInternal?.isContentActive(); } // prettier-ignore
     public get topMost()        { return this._props.renderDepth === 0; } // prettier-ignore
@@ -1262,7 +1274,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     public get HasAIEditor()    { return !!this._docViewInternal?._componentView?.componentAIView?.(); } // prettier-ignore
 
     get LayoutFieldKey() {
-        return Doc.LayoutFieldKey(this.Document, this._props.LayoutTemplateString);
+        return Doc.LayoutDataKey(this.Document, this._props.LayoutTemplateString);
     }
 
     @computed get layout_fitWidth() {
@@ -1316,10 +1328,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
 
     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) {
+    public iconify = action((finished?: () => void, animateTime?: number) => {
         this.ComponentView?.updateIcon?.();
         const animTime = this._docViewInternal?.animateScaleTime();
-        runInAction(() => { this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime); }); // prettier-ignore
+        this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime);
         const finalFinished = action(() => {
             finished?.();
             this._docViewInternal && (this._docViewInternal._animateScaleTime = animTime);
@@ -1329,12 +1341,12 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
             this.switchViews(true, 'icon', finalFinished);
             if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') this.Document.deiconifyLayout = layoutFieldKey.replace('layout_', '');
         } else {
-            const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
+            const deiconifyLayout = StrCast(this.Document.deiconifyLayout);
             this.switchViews(!!deiconifyLayout, deiconifyLayout, finalFinished, true);
             this.Document.deiconifyLayout = undefined;
             this._props.bringToFront?.(this.Document);
         }
-    }
+    });
 
     public playAnnotation = () => {
         const audioAnnoState = this.Document._audioAnnoState ?? AudioAnnoState.stopped;
@@ -1349,7 +1361,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                         autoplay: true,
                         loop: false,
                         volume: 0.5,
-                        onend: action(() => { this.Document._audioAnnoState = AudioAnnoState.stopped; }), // prettier-ignore
+                        onend: action(() => (this.Document._audioAnnoState = AudioAnnoState.stopped)),
                     });
                     this.Document._audioAnnoState = AudioAnnoState.playing;
                     break;
@@ -1405,14 +1417,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                     tempDoc = view.Document;
                     MakeTemplate(tempDoc);
                     Doc.AddDocToList(Doc.UserDoc(), 'template_user', tempDoc);
-                    Doc.AddDocToList(DocListCast(Doc.MyTools.data)[1], 'data', makeUserTemplateButtonOrImage(tempDoc));
-                    tempDoc && Doc.AddDocToList(Cast(Doc.UserDoc().template_user, Doc, null), 'data', tempDoc);
+                    Doc.AddDocToList(DocListCast(Doc.MyTools?.data)[1], 'data', makeUserTemplateButtonOrImage(tempDoc));
+                    DocCast(Doc.UserDoc().template_user) && tempDoc && Doc.AddDocToList(DocCast(Doc.UserDoc().template_user)!, 'data', tempDoc);
                 } else {
-                    tempDoc = DocCast(view.Document[StrCast(view.Document.layout_fieldKey)]);
-                    if (!tempDoc) {
-                        tempDoc = view.Document;
-                        while (tempDoc && !Doc.isTemplateDoc(tempDoc)) tempDoc = DocCast(tempDoc.proto);
-                    }
+                    tempDoc = DocCast(Doc.LayoutField(view.Document));
                 }
             }
             Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
@@ -1434,10 +1442,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(!!defaultLayout, defaultLayout, undefined, true);
         else this.switchViews(true, detailLayoutKeySuffix, undefined, true);
     };
-    public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
+    public switchViews = action((custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
         const batch = UndoManager.StartBatch('switchView:' + view);
         // shrink doc first..
-        runInAction(() => { this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1); }); // prettier-ignore
+        this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1);
         setTimeout(
             action(() => {
                 if (useExistingLayout && custom && this.Document['layout_' + view]) {
@@ -1457,7 +1465,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
             }),
             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.
      */
@@ -1510,7 +1518,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                 ref={r => {
                     const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition
                     if (r && val !== this._enableHtmlOverlayTransitions) {
-                        setTimeout(action(() => { this._enableHtmlOverlayTransitions = val; })); // prettier-ignore
+                        setTimeout(action(() => (this._enableHtmlOverlayTransitions = val)));
                     }
                 }}
                 style={{ display: !this._htmlOverlayText ? 'none' : undefined }}>
@@ -1537,12 +1545,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
             <div
                 id={this.ViewGuid}
                 className="contentFittingDocumentView"
-                onPointerEnter={action(() => {
-                    this._isHovering = true;
-                })}
-                onPointerLeave={action(() => {
-                    this._isHovering = false;
-                })}>
+                onPointerEnter={action(() => (this._isHovering = true))} //
+                onPointerLeave={action(() => (this._isHovering = false))}>
                 {!this.Document || !this._props.PanelWidth() ? null : (
                     <div
                         className="contentFittingDocumentView-previewDoc"
@@ -1570,9 +1574,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                             fitWidth={this.layout_fitWidthFunc}
                             ScreenToLocalTransform={this.screenToContentsTransform}
                             focus={this._props.focus || emptyFunction}
-                            ref={action((r: DocumentViewInternal | null) => {
-                                r && (this._docViewInternal = r);
-                            })}
+                            ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))}
                         />
                         {this.htmlOverlay()}
                         {this.ComponentView?.infoUI?.()}
@@ -1616,7 +1618,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
             if (dv && (!containingDoc || dv.containerViewPath?.().lastElement()?.Document === containingDoc)) {
                 DocumentView.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.Document));
             } else {
-                const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc));
+                const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc))!;
                 const showDoc = !Doc.IsSystem(container) && !cv ? container : doc;
                 options.toggleTarget = undefined;
                 DocumentView.showDocument(showDoc, options, () => DocumentView.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ad6d93d43..de49f502f 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -80,7 +80,7 @@ export interface FieldViewSharedProps {
     setTitleFocus?: () => void;
     focus: FocusFuncType;
     onClickScript?: () => ScriptField | undefined;
-    onDoubleClickScript?: () => ScriptField;
+    onDoubleClickScript?: () => ScriptField | undefined;
     onPointerDownScript?: () => ScriptField;
     onPointerUpScript?: () => ScriptField;
     onKey?: (e: KeyboardEvent, textBox: FormattedTextBox) => boolean | undefined;
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index e3924eca7..0bbd6a0d3 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -108,7 +108,7 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
                 </Tooltip>
             )); // prettier-ignore
 
-        const audioannos = StrListCast(this.View.Document[Doc.LayoutFieldKey(this.View.Document) + '_audioAnnotations_text']);
+        const audioannos = StrListCast(this.View.Document[Doc.LayoutDataKey(this.View.Document) + '_audioAnnotations_text']);
         return !buttons.length && !audioannos.length ? null : (
             <div className="card-button-container" style={{ fontSize: '50px' }}>
                 {audioannos.length ? this.renderAudioButtons(this.View, audioannos.lastElement()) : null}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 2b9a78596..0c475b7bb 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -216,7 +216,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 } else if (hitDropTarget(e.target as HTMLElement, this._regenerateIconRef.current)) {
                     this._regenerateLoading = true;
                     const drag = de.complete.docDragData.draggedDocuments.lastElement();
-                    const dragField = drag[Doc.LayoutFieldKey(drag)];
+                    const dragField = drag[Doc.LayoutDataKey(drag)];
                     const descText = RTFCast(dragField)?.Text || StrCast(dragField) || RTFCast(drag.text)?.Text || StrCast(drag.text) || StrCast(this.Document.title);
                     const oldPrompt = StrCast(this.Document.ai_firefly_prompt, StrCast(this.Document.title));
                     const newPrompt = (text: string) => (oldPrompt ? `${oldPrompt} ~~~ ${text}` : text);
@@ -224,7 +224,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     added = false;
                 } else if (de.altKey || !this.dataDoc[this.fieldKey]) {
                     const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
-                    const targetField = Doc.LayoutFieldKey(layoutDoc);
+                    const targetField = Doc.LayoutDataKey(layoutDoc);
                     const targetDoc = layoutDoc[DocData];
                     if (targetDoc[targetField] instanceof ImageField) {
                         added = true;
@@ -264,7 +264,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         DocListCast(this.dataDoc[this.annotationKey]).forEach(doc => {
             doc.x = (NumCast(doc.x) / oldnativeWidth) * newnativeWidth;
             doc.y = (NumCast(doc.y) / oldnativeWidth) * newnativeWidth;
-            if (!RTFCast(doc[Doc.LayoutFieldKey(doc)])) {
+            if (!RTFCast(doc[Doc.LayoutDataKey(doc)])) {
                 doc.width = (NumCast(doc.width) / oldnativeWidth) * newnativeWidth;
                 doc.height = (NumCast(doc.height) / oldnativeWidth) * newnativeWidth;
             }
@@ -351,9 +351,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 event: () => {
                     Networking.PostToServer('/queryFireflyImageText', {
                         file: (file => {
-                            const ext = extname(file);
-                            return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
-                        })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
+                            const ext = file ? extname(file) : '';
+                            return file?.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
+                        })(ImageCast(this.Document[Doc.LayoutDataKey(this.Document)])?.url.href),
                     }).then(text => alert(text));
                 },
                 icon: 'expand-arrows-alt',
@@ -364,9 +364,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     Networking.PostToServer('/expandImage', {
                         prompt: 'sunny skies',
                         file: (file => {
-                            const ext = extname(file);
-                            return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
-                        })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
+                            const ext = file ? extname(file) : '';
+                            return file?.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
+                        })(ImageCast(this.Document[Doc.LayoutDataKey(this.Document)])?.url.href),
                     }).then(res => {
                         const info = res as Upload.ImageInformation;
                         const img = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { title: 'expand:' + this.Document.title });
@@ -510,7 +510,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 <div
                     className="imageBox-regenerateDropTarget"
                     ref={this._regenerateIconRef}
-                    onClick={() => DocumentView.showDocument(DocCast(this.Document.ai_firefly_generatedDocs), { openLocation: OpenWhere.addRight })}
+                    onClick={() => DocCast(this.Document.ai_firefly_generatedDocs) && DocumentView.showDocument(DocCast(this.Document.ai_firefly_generatedDocs)!, { openLocation: OpenWhere.addRight })}
                     style={{
                         display: (this._props.isContentActive() && (SnappingManager.CanEmbed || this.Document.ai_firefly_generatedDocs)) || this._regenerateLoading ? 'block' : 'none',
                         transform: `scale(${this.uiBtnScaling})`,
@@ -532,7 +532,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         const defaultUrl = new URL(ClientUtils.prepend(DefaultPath));
         const altpaths =
             alts
-                ?.map(doc => (doc instanceof Doc ? (ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl) : defaultUrl))
+                ?.map(doc => (doc instanceof Doc ? (ImageCast(doc[Doc.LayoutDataKey(doc)])?.url ?? defaultUrl) : defaultUrl))
                 .filter(url => url)
                 .map(url => this.choosePath(url)) ?? []; // acc  ess the primary layout data of the alternate documents
         const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
@@ -664,7 +664,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                                                 const url = firstImg.pathname;
                                                 const imgField = new ImageField(url);
                                                 this._prevImgs.length === 0 &&
-                                                    this._prevImgs.push({ prompt: StrCast(this.dataDoc.ai_firefly_prompt), seed: this.dataDoc.ai_firefly_seed as number, href: this.paths.lastElement(), pathname: field.url.pathname });
+                                                    this._prevImgs.push({ prompt: StrCast(this.dataDoc.ai_firefly_prompt), seed: this.dataDoc.ai_firefly_seed as number, href: this.paths.lastElement(), pathname: field?.url.pathname ?? '' });
                                                 this._prevImgs.unshift({ prompt: firstImg.prompt, seed: firstImg.seed, pathname: url });
                                                 this.dataDoc.ai_firefly_history = JSON.stringify(this._prevImgs);
                                                 this.dataDoc.ai_firefly_prompt = firstImg.prompt;
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index be897b3f3..706607fe1 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -96,7 +96,7 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const { script, type, onDelegate } = kvpScript;
         const chooseDelegate = forceOnDelegate || onDelegate || keyIn.startsWith('_');
         const key = chooseDelegate && keyIn.startsWith('$') ? keyIn.slice(1) : keyIn;
-        const target = chooseDelegate ? doc : DocCast(doc.proto, doc);
+        const target = chooseDelegate ? doc : DocCast(doc.proto, doc)!;
         let field: FieldType | undefined;
         switch (type) {
             case 'computed': field = new ComputedField(script); break; // prettier-ignore
@@ -261,15 +261,15 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
     getFieldView = () => {
         const rows = this.rows.filter(row => row.isChecked);
         if (rows.length > 1) {
-            const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.Document).title}`, _chromeHidden: true });
+            const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${this.Document.title}`, _chromeHidden: true });
             rows.forEach(row => {
-                const field = this.createFieldView(DocCast(this.Document), row);
+                const field = this.createFieldView(this.Document, row);
                 field && Doc.AddDocToList(parent, 'data', field);
                 row.uncheck();
             });
             return parent;
         }
-        return rows.length ? this.createFieldView(DocCast(this.Document), rows.lastElement()) : undefined;
+        return rows.length ? this.createFieldView(this.Document, rows.lastElement()) : undefined;
     };
 
     createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index c9e0aea5a..d8f968a40 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -61,7 +61,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
     };
 
     render() {
-        let doc = this._props.keyName.startsWith('_') ? this._props.doc[DocLayout] : this._props.doc;
+        let doc: Doc | undefined = this._props.keyName.startsWith('_') ? this._props.doc[DocLayout] : this._props.doc;
         const layoutField = doc !== this._props.doc;
         const key = layoutField ? this._props.keyName.replace(/^_/, '') : this._props.keyName;
         let protoCount = 0;
@@ -85,7 +85,7 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
                             style={hover}
                             className="keyValuePair-td-key-delete"
                             onClick={undoable(() => {
-                                delete (Object.keys(doc).indexOf(key) !== -1 ? doc : DocCast(this._props.doc.proto))[key];
+                                doc && delete (Object.keys(doc).indexOf(key) !== -1 ? doc : DocCast(this._props.doc.proto)!)[key];
                             }, 'set key value')}>
                             X
                         </button>
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 36d260fb9..83c44c80f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -210,7 +210,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._disposers.scroll = reaction(
             () => this.layoutDoc.layout_scrollTop,
             () => {
-                if (!(ComputedField.WithoutComputed(() => FieldValue(this.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) {
+                if (!(ComputedField.DisableCompute(() => FieldValue(this.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) {
                     this.Document[this.SidebarKey + '_panY'] = ComputedField.MakeFunction('this.layout_scrollTop');
                 }
                 this.layoutDoc[this.SidebarKey + '_freeform_scale'] = 1;
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 53783e8a3..25c708da8 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -98,7 +98,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
                   });
         screengrabber.overlayX = 70; // was -400
         screengrabber.overlayY = 590; // was 0
-        screengrabber['$' + Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
+        screengrabber['$' + Doc.LayoutDataKey(screengrabber) + '_trackScreen'] = true;
         Doc.AddToMyOverlay(screengrabber); // just adds doc to overlay
         DocumentView.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 999f9c1cd..4f02d68d6 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -284,7 +284,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     setupDictation = () => {
         if (this.dataDoc[this.fieldKey + '_dictation']) return;
         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);
+        const textField = Doc.LayoutDataKey(dictationText);
         dictationText._layout_autoHeight = false;
         const dictationTextProto = dictationText[DocData];
         dictationTextProto[`${textField}_recordingSource`] = this.dataDoc;
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 8da422039..38a43e8d4 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -1,14 +1,12 @@
-/* eslint-disable react/button-has-type */
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import ResizeObserver from 'resize-observer-polyfill';
 import { returnAlways, returnEmptyString } from '../../../ClientUtils';
-import { Doc } from '../../../fields/Doc';
+import { Doc, StrListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
-import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
@@ -26,10 +24,8 @@ import './ScriptingBox.scss';
 import * as ts from 'typescript';
 import { FieldType } from '../../../fields/ObjectField';
 
-// eslint-disable-next-line @typescript-eslint/no-var-requires
 const getCaretCoordinates = require('textarea-caret');
 
-// eslint-disable-next-line @typescript-eslint/no-var-requires
 const ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
 
 @observer
@@ -105,7 +101,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         this.dataDoc[this.fieldKey + '-functionDescription'] = value;
     }
     @computed({ keepAlive: true }) get compileParams() {
-        return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []);
+        return StrListCast(this.dataDoc[this.fieldKey + '-params']);
     }
     set compileParams(value) {
         this.dataDoc[this.fieldKey + '-params'] = new List<string>(value);
@@ -426,7 +422,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             onChange={e => this.viewChanged(e, parameter)}
                             value={typeof this.dataDoc[parameter] === 'string' ? 'S' + StrCast(this.dataDoc[parameter]) : typeof this.dataDoc[parameter] === 'number' ? 'N' + NumCast(this.dataDoc[parameter]) : 'B' + BoolCast(this.dataDoc[parameter])}>
                             {types.map((type, i) => (
-                                // eslint-disable-next-line react/no-array-index-key
                                 <option key={i} className="scriptingBox-viewOption" value={(typeof type === 'string' ? 'S' : typeof type === 'number' ? 'N' : 'B') + type}>
                                     {' '}
                                     {type.toString()}{' '}
@@ -680,7 +675,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         const definedParameters = !this.compileParams.length ? null : (
             <div className="scriptingBox-plist" style={{ width: '30%' }}>
                 {this.compileParams.map((parameter, i) => (
-                    // eslint-disable-next-line react/no-array-index-key
                     <div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
                         <EditableView
                             display="block"
@@ -760,7 +754,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 {!this.compileParams.length || !this.paramsNames ? null : (
                     <div className="scriptingBox-plist">
                         {this.paramsNames.map((parameter: string, i: number) => (
-                            // eslint-disable-next-line react/no-array-index-key
                             <div key={i} className="scriptingBox-pborder" onKeyDown={e => e.key === 'Enter' && this._overlayDisposer?.()}>
                                 <div className="scriptingBox-wrapper" style={{ maxHeight: '40px' }}>
                                     <div className="scriptingBox-paramNames"> {`${parameter}:${this.paramsTypes[i]} = `} </div>
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index bad4f5b13..5603786f0 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -6,7 +6,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as WebRequest from 'web-request';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivHeight, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils';
-import { Doc, DocListCast, Field, FieldType, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { HtmlField } from '../../../fields/HtmlField';
 import { InkTool } from '../../../fields/InkField';
@@ -645,8 +645,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     forward = (checkAvailable?: boolean) => {
-        const future = Cast(this.dataDoc[this.fieldKey + '_future'], listSpec('string'), []);
-        const history = Cast(this.dataDoc[this.fieldKey + '_history'], listSpec('string'), []);
+        const future = StrListCast(this.dataDoc[this.fieldKey + '_future']);
+        const history = StrListCast(this.dataDoc[this.fieldKey + '_history']);
         if (checkAvailable) return future.length;
         runInAction(() => {
             if (future.length) {
@@ -672,23 +672,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     back = (checkAvailable?: boolean) => {
-        const future = Cast(this.dataDoc[this.fieldKey + '_future'], listSpec('string'));
-        const history = Cast(this.dataDoc[this.fieldKey + '_history'], listSpec('string'), []);
+        const future = StrListCast(this.dataDoc[this.fieldKey + '_future']);
+        const history = StrListCast(this.dataDoc[this.fieldKey + '_history']);
         if (checkAvailable) return history.length;
         runInAction(() => {
             if (history.length) {
                 const curUrl = this._url;
-                if (future === undefined) this.dataDoc[this.fieldKey + '_future'] = new List<string>([this._url]);
+                if (!future.length) this.dataDoc[this.fieldKey + '_future'] = new List<string>([this._url]);
                 else this.dataDoc[this.fieldKey + '_future'] = new List<string>([...future, this._url]);
                 this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!));
                 this._scrollHeight = 0;
                 if (this._webUrl === this._url) {
                     this._webUrl = curUrl;
-                    setTimeout(
-                        action(() => {
-                            this._webUrl = this._url;
-                        })
-                    );
+                    setTimeout(action(() => (this._webUrl = this._url)));
                 } else {
                     this._webUrl = this._url;
                 }
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
index b023b1de6..528bcd05a 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
@@ -733,7 +733,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     const x2 = parseFloat(values[2]) * Doc.NativeWidth(doc);
                     const y2 = parseFloat(values[3]) * Doc.NativeHeight(doc) + foundChunk.startPage * Doc.NativeHeight(doc);
 
-                    const annotationKey = '$' + Doc.LayoutFieldKey(doc) + '_annotations';
+                    const annotationKey = '$' + Doc.LayoutDataKey(doc) + '_annotations';
 
                     const existingDoc = DocListCast(doc[annotationKey]).find(d => d.citation_id === citation.citation_id);
                     const highlightDoc = existingDoc ?? this.createImageCitationHighlight(x1, y1, x2, y2, citation, annotationKey, doc);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index cfb6dfa12..dc23a695d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -214,9 +214,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     }
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
-        const rootDoc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : this.rootDoc;
-        if (!pinProps && this.EditorView?.state.selection.empty) return rootDoc;
-        const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc });
+        if (!pinProps && this.EditorView?.state.selection.empty) return this.rootDoc;
+        const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.rootDoc?.title), annotationOn: this.rootDoc });
         this.addDocument(anchor);
         this._finishingLink = true;
         this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation);
@@ -245,7 +244,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                     anchor.followLinkAudio = true;
                     let stopFunc: () => void = emptyFunction;
                     target.$mediaState = mediaState.Recording;
-                    DictationManager.recordAudioAnnotation(target, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore
+                    DictationManager.recordAudioAnnotation(target, Doc.LayoutDataKey(target), stop => { stopFunc = stop }); // prettier-ignore
 
                     const reactionDisposer = reaction(
                         () => target.mediaState,
@@ -427,8 +426,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const oldAutoLinks = Doc.Links(this.Document).filter(
             link => 
                 ((!Doc.isTemplateForField(this.Document) &&
-                    (!Doc.isTemplateForField(DocCast(link.link_anchor_1)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
-                    (!Doc.isTemplateForField(DocCast(link.link_anchor_2)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) ||
+                    ((DocCast(link.link_anchor_1) && !Doc.isTemplateForField(DocCast(link.link_anchor_1)!)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
+                    ((DocCast(link.link_anchor_2) && !Doc.isTemplateForField(DocCast(link.link_anchor_2)!)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) ||
                  (Doc.isTemplateForField(this.Document) && (link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document))) &&
                 link.link_relationship === LinkManager.AutoKeywords
         ); // prettier-ignore
@@ -450,18 +449,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     updateTitle = () => {
         const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
         if (
-            !this._props.dontRegisterView && // (this.Document.isTemplateForField === "text" || !this.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+            !this._props.dontRegisterView && // only update the title if the data document's data field is changing
             title.startsWith('-') &&
             this.EditorView &&
             !this.dataDoc.title_custom &&
-            (Doc.LayoutFieldKey(this.Document) === this.fieldKey || this.fieldKey === 'text')
+            (Doc.LayoutDataKey(this.Document) === this.fieldKey || this.fieldKey === 'text')
         ) {
             let node = this.EditorView.state.doc;
             while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild;
             const str = node.textContent;
             const prefix = '-';
 
-            const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title));
+            const cfield = ComputedField.DisableCompute(() => FieldValue(this.dataDoc.title));
             if (!(cfield instanceof ComputedField)) {
                 this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
             }
@@ -588,7 +587,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         }
         const dragData = de.complete.docDragData;
         if (dragData) {
-            const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
+            const layoutProto = DocCast(this.layoutDoc.proto);
+            const dataDoc = layoutProto && Doc.IsDelegateField(layoutProto, this.fieldKey) ? layoutProto : this.dataDoc;
             const effectiveAcl = GetEffectiveAcl(dataDoc);
             const draggedDoc = dragData.droppedDocuments.lastElement();
             let added: Opt<boolean>;
@@ -596,7 +596,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl) && !dragData.draggedDocuments.includes(this.Document)) {
                 // replace text contents when dragging with Alt
                 if (de.altKey) {
-                    const fieldKey = Doc.LayoutFieldKey(draggedDoc);
+                    const fieldKey = Doc.LayoutDataKey(draggedDoc);
                     if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this.Document)) {
                         Doc.GetProto(this.dataDoc)[this.fieldKey] = Field.Copy(draggedDoc[fieldKey]);
                     }
@@ -1003,16 +1003,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 const link = CreateLinkToActiveAudio(textanchorFunc, false).lastElement();
                 if (link) {
                     link.$isDictation = true;
-                    const audioanchor = Cast(link.link_anchor_2, Doc, null);
-                    const textanchor = Cast(link.link_anchor_1, Doc, null);
+                    const audioanchor = DocCast(link.link_anchor_2);
+                    const textanchor = DocCast(link.link_anchor_1);
                     if (audioanchor) {
                         audioanchor.backgroundColor = 'tan';
                         const audiotag = this.EditorView.state.schema.nodes.audiotag.create({
                             timeCode: NumCast(audioanchor._timecodeToShow),
                             audioId: audioanchor[Id],
-                            textId: textanchor[Id],
+                            textId: textanchor?.[Id] ?? '',
                         });
-                        textanchor.$title = 'dictation:' + audiotag.attrs.timeCode;
+                        textanchor && (textanchor.$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)));
@@ -1147,11 +1147,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     // if the scroll height has changed and we're in layout_autoHeight mode, then we need to update the textHeight component of the doc.
     // Since we also monitor all component height changes, this will update the document's height.
-    resetNativeHeight = (scrollHeight: number) => {
-        const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight);
+    resetNativeHeight = action((scrollHeight: number) => {
         this.layoutDoc['_' + this.fieldKey + '_height'] = scrollHeight;
-        if (nh) this.layoutDoc._nativeHeight = scrollHeight;
-    };
+        if (!this.layoutDoc.isTemplateForField) this.layoutDoc._nativeHeight = scrollHeight;
+    });
 
     addPlugin = (plugin: Plugin) => {
         const editorView = this.EditorView;
@@ -1216,8 +1215,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 const dataData = this.dataDoc[this.fieldKey];
                 const layoutData = Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? undefined : this.layoutDoc[this.fieldKey];
                 const dataTime = dataData ? (DateCast(this.dataDoc[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
-                const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.layoutDoc)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
-                const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.dataDoc.proto)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
+                const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.layoutDoc)?.[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
+                const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.dataDoc.proto)?.[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
                 const recentData = dataTime >= layoutTime ? (protoTime >= dataTime ? protoData : dataData) : layoutTime >= protoTime ? layoutData : protoData;
                 const whichData = recentData ?? (this.layoutDoc.isTemplateDoc ? layoutData : protoData) ?? protoData;
                 return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) };
@@ -1313,7 +1312,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             // } catch (err) {
             //     console.log('Drop failed', err);
             // }
-            this.addDocument?.(DocCast(this.Document.image));
+            DocCast(this.Document.image) && this.addDocument?.(DocCast(this.Document.image)!);
         }
 
         //if (this.Document.image) this.addDocument?.(DocCast(this.Document.image));
@@ -1426,8 +1425,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const rtfField = Cast((!curText && this.layoutDoc[this.fieldKey]) || this.dataDoc[fieldKey], RichTextField);
         if (this.ProseRef) {
             this.EditorView?.destroy();
+            const edState = () => {
+                try {
+                    return rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config);
+                } catch {
+                    return EditorState.create(config);
+                }
+            };
             this._editorView = new EditorView(this.ProseRef, {
-                state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
+                state: edState(),
                 handleScrollToSelection: this.scrollToSelection,
                 dispatchTransaction: this.dispatchTransaction,
                 nodeViews: FormattedTextBox._nodeViews(this),
@@ -1439,7 +1445,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             (this.EditorView as unknown as { scrollToSelection: unknown }).scrollToSelection = this.scrollToSelection;
             const { state } = this._editorView;
             if (!rtfField) {
-                const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
+                const layoutProto = DocCast(this.layoutDoc.proto);
+                const dataDoc = layoutProto && Doc.IsDelegateField(layoutProto, this.fieldKey) ? layoutProto : this.dataDoc;
                 const startupText = Field.toString(dataDoc[fieldKey] as FieldType);
                 const textAlign = StrCast(this.dataDoc[this.fieldKey + '_align'], StrCast(Doc.UserDoc().textAlign)) || 'left';
                 if (textAlign !== 'left') {
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 4fa1fb13b..d31a58030 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -148,11 +148,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         this._activeAlignment = this.getActiveAlignment();
         this._activeFitBox = BoolCast(refDoc[refField + 'fitBox'], BoolCast(Doc.UserDoc().fitBox));
         this._activeFontFamily = !activeFamilies.length
-            ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(this.dataDoc?.[Doc.LayoutFieldKey(this.dataDoc) + '_fontFamily'], refVal('fontFamily', 'Arial')))
+            ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(this.dataDoc?.[Doc.LayoutDataKey(this.dataDoc) + '_fontFamily'], refVal('fontFamily', 'Arial')))
             : activeFamilies.length === 1
               ? String(activeFamilies[0])
               : 'various';
-        this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(this.dataDoc?.[Doc.LayoutFieldKey(this.dataDoc) + '_fontSize'], refVal('fontSize', '10px'))) : activeSizes[0];
+        this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(this.dataDoc?.[Doc.LayoutDataKey(this.dataDoc) + '_fontSize'], refVal('fontSize', '10px'))) : activeSizes[0];
         this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('fontColor', 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
         this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
 
@@ -382,7 +382,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
                     });
             }
         } else if (this.dataDoc) {
-            this.dataDoc[`${Doc.LayoutFieldKey(this.dataDoc)}_${fontField}`] = value;
+            this.dataDoc[`${Doc.LayoutDataKey(this.dataDoc)}_${fontField}`] = value;
             this.updateMenu(undefined, undefined, undefined, this.dataDoc);
         } else {
             Doc.UserDoc()[fontField] = value;
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 14ea911b1..26ccf6931 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -108,25 +108,23 @@ export class RichTextRules {
             new InputRule(/>>$/, (state, match, start, end) => {
                 const numInlines = NumCast(this.Document.$inlineTextCount);
                 this.Document.$inlineTextCount = numInlines + 1;
-                const inlineFieldKey = '$inline' + numInlines; // which field on the text document this annotation will write to
-                const inlineLayoutKey = '$layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation
+                const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to
+                const inlineLayoutKey = 'layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation
                 const textDocInline = Docs.Create.TextDocument('', {
-                    layout_fieldKey: inlineLayoutKey,
                     _width: 75,
                     _height: 35,
-                    annotationOn: this.Document[DocData],
                     _layout_fitWidth: true,
                     _layout_autoHeight: true,
-                    text_fontSize: '9px',
-                    title: 'inline comment',
                 });
+                textDocInline.layout_fieldKey = inlineLayoutKey;
+                textDocInline.annotationOn = this.Document[DocData];
                 textDocInline.title = inlineFieldKey; // give the annotation its own title
                 textDocInline.title_custom = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
-                textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
+                //textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
                 textDocInline.isDataDoc = true;
                 textDocInline.proto = this.Document[DocData]; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
-                this.Document[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
-                this.Document[inlineFieldKey] = ''; // set a default value for the annotation
+                this.Document['$' + inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
+                this.Document['$' + inlineFieldKey] = ''; // set a default value for the annotation
                 const node = state.doc.resolve(start).nodeAfter;
                 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' });
@@ -287,7 +285,8 @@ export class RichTextRules {
                             editor.dispatch(estate.tr.setSelection(new TextSelection(estate.doc.resolve(start), estate.doc.resolve(end - prefixLength))));
                         }
 
-                        DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' });
+                        const tanchor = this.TextBox.getAnchor(true);
+                        tanchor && DocUtils.MakeLink(tanchor, target, { link_relationship: 'portal to:portal from' });
 
                         const teditor = this.TextBox.EditorView;
                         if (teditor && selection) {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 23155ebf3..11f35b8ef 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -149,7 +149,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     setIsLoading =              action((input?: boolean) => { this._isLoading = !!input; }); // prettier-ignore
     setShowAIGalleryVisibilty = action((visible: boolean) => { this._showAIGallery = visible; }); // prettier-ignore
     setBezierControlPoints = action((newPoints: { p1: number[]; p2: number[] }) => {
-        this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
+        this.activeItem && this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
     });
 
     @computed get showEaseFunctions() {
@@ -158,7 +158,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @computed
     get currCPoints() {
-        return EaseFuncToPoints(this.activeItem.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease');
+        return EaseFuncToPoints(this.activeItem?.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease');
     }
 
     @computed
@@ -175,7 +175,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return DocListCast(this.Document[this.presFieldKey]);
     }
     @computed get tagDocs() {
-        return this.childDocs.map(doc => Cast(doc.presentation_targetDoc, Doc, null));
+        return this.childDocs.map(doc => DocCast(doc.presentation_targetDoc)!).filter(doc => doc);
     }
     @computed get itemIndex() {
         return NumCast(this.Document._itemIndex);
@@ -187,11 +187,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return DocCast(this.activeItem?.presentation_targetDoc);
     }
     public static targetRenderedDoc = (doc: Doc) => {
-        const targetDoc = Cast(doc?.presentation_targetDoc, Doc, null);
+        const targetDoc = DocCast(doc?.presentation_targetDoc);
         return targetDoc?.layout_unrendered ? DocCast(targetDoc.annotationOn) : targetDoc;
     };
     @computed get scrollable() {
-        if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc.type as DocumentType) || this.targetDoc._type_collection === CollectionViewType.Stacking) return true;
+        if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc?.type as DocumentType) || this.targetDoc?._type_collection === CollectionViewType.Stacking) return true;
         return false;
     }
     @computed get selectedDocumentView() {
@@ -273,8 +273,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     stopTempMedia = (targetDocField: FieldResult) => {
-        const targetDoc = DocCast(DocCast(targetDocField).annotationOn) ?? DocCast(targetDocField);
-        if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as DocumentType)) {
+        const targetDoc = DocCast(DocCast(targetDocField)?.annotationOn) ?? DocCast(targetDocField);
+        if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc?.type as DocumentType)) {
             const targMedia = DocumentView.getDocumentView(targetDoc);
             targMedia?.ComponentView?.Pause?.();
         }
@@ -295,13 +295,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.setIsLoading(true);
         const slideDefaults: { [key: string]: FieldResult } = { presentation_transition: 500, config_zoom: 1 };
         const currSlideProperties = gptSlideProperties.reduce(
-            (prev, key) => { prev[key] = Field.toString(this.activeItem[key]) ?? prev[key]; return prev; }, 
+            (prev, key) => { prev[key] = Field.toString(this.activeItem?.[key]) ?? prev[key]; return prev; }, 
             slideDefaults); // prettier-ignore
 
         gptTrailSlideCustomization(input, JSON.stringify(currSlideProperties))
             .then(res =>
                 (Object.entries(JSON.parse(res)) as string[][]).forEach(([key, val]) => {
-                    this.activeItem[key] = (+val).toString() === val ? +val : (val ?? this.activeItem[key]);
+                    this.activeItem && (this.activeItem[key] = (+val).toString() === val ? +val : (val ?? this.activeItem[key]));
                 })
             )
             .catch(e => console.error(e))
@@ -325,7 +325,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         const serial = nextSelected + 1 < this.childDocs.length && NumCast(this.childDocs[nextSelected + 1].presentation_groupWithUp) > 1;
                         if (serial) {
                             this.gotoDocument(nextSelected, this.activeItem, true, async () => {
-                                const waitTime = NumCast(this.activeItem.presentation_duration);
+                                const waitTime = NumCast(this.activeItem?.presentation_duration);
                                 await new Promise<void>(res => {
                                     setTimeout(res, Math.max(0, waitTime));
                                 });
@@ -346,7 +346,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     progressivizedItems = (doc: Doc) => {
         const targetList = PresBox.targetRenderedDoc(doc);
         if (doc.presentation_indexed !== undefined && targetList) {
-            const listItems = (Cast(targetList[Doc.LayoutFieldKey(targetList)], listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[]) ?? DocListCast(targetList[Doc.LayoutFieldKey(targetList) + '_annotations']);
+            const listItems = (Cast(targetList[Doc.LayoutDataKey(targetList)], listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[]) ?? DocListCast(targetList[Doc.LayoutDataKey(targetList) + '_annotations']);
             return listItems.filter(ldoc => !ldoc.layout_unrendered);
         }
         return undefined;
@@ -364,7 +364,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     next = () => {
         const progressiveReveal = (first: boolean) => {
             const presIndexed = Cast(this.activeItem?.presentation_indexed, 'number', null);
-            if (presIndexed !== undefined) {
+            if (presIndexed !== undefined && this.activeItem) {
                 const listItems = this.progressivizedItems(this.activeItem);
                 const listItemDoc = listItems?.[presIndexed];
                 if (listItems && listItemDoc) {
@@ -395,7 +395,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (this.childDocs[this.itemIndex + 1] !== undefined) {
             // Case 1: No more frames in current doc and next slide is defined, therefore move to next slide
             const slides = DocListCast(this.Document[StrCast(this.presFieldKey, 'data')]);
-            const curLast = this.selectedArray.size ? Math.max(...Array.from(this.selectedArray).map(d => slides.indexOf(DocCast(d)))) : this.itemIndex;
+            const curLast = this.selectedArray.size ? Math.max(...Array.from(this.selectedArray).map(d => slides.indexOf(DocCast(d) ?? d))) : this.itemIndex;
 
             // before moving onto next slide, run the subroutines :)
             const currentDoc = this.childDocs[this.itemIndex];
@@ -422,7 +422,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const { activeItem } = this;
         let prevSelected = this.itemIndex;
         // Functionality for group with up
-        let didZoom = activeItem.presentation_movement;
+        let didZoom = activeItem?.presentation_movement;
         for (; prevSelected > 0 && this.childDocs[Math.max(0, prevSelected - 1)].presentation_groupWithUp; prevSelected--) {
             didZoom = didZoom === 'none' ? this.childDocs[prevSelected].presentation_movement : didZoom;
         }
@@ -452,7 +452,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 this.stopTempMedia(from.presentation_targetDoc);
             }
             // If next slide is audio / video 'Play automatically' then the next slide should be played
-            if (this.layoutDoc.presentation_status !== PresStatus.Edit && (this.targetDoc.type === DocumentType.AUDIO || this.targetDoc.type === DocumentType.VID) && this.activeItem.presentation_mediaStart === 'auto') {
+            if (this.layoutDoc.presentation_status !== PresStatus.Edit && (this.targetDoc?.type === DocumentType.AUDIO || this.targetDoc?.type === DocumentType.VID) && this.activeItem?.presentation_mediaStart === 'auto') {
                 this.startTempMedia(this.targetDoc, this.activeItem);
             }
             if (!group) this.clearSelectedArray();
@@ -516,8 +516,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const activeFrame = activeItem.config_activeFrame ?? activeItem.config_currentFrame;
         if (activeFrame !== undefined) {
             const frameTime = NumCast(activeItem.presentation_transition, 500);
-            const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc);
-            const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext;
+            const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem?.presentation_targetDoc)?.embedContainer) : DocCast(activeItem.presentation_targetDoc);
+            const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext)?.annotationOn) : acontext;
             if (context) {
                 const ffview = CollectionFreeFormView.from(DocumentView.getFirstDocumentView(context));
                 if (ffview?.childDocs) {
@@ -528,7 +528,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         if ((pinDataTypes?.dataview && activeItem.config_data !== undefined) || (!pinDataTypes && activeItem.config_data !== undefined)) {
             bestTarget._dataTransition = `all ${transTime}ms`;
-            const fkey = Doc.LayoutFieldKey(bestTarget);
+            const fkey = Doc.LayoutDataKey(bestTarget);
             const setData = bestTargetView?.ComponentView?.setData;
             if (setData) setData(activeItem.config_data);
             else {
@@ -553,7 +553,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             }
         }
         if (pinDataTypes?.clippable || (!pinDataTypes && activeItem.config_clipWidth !== undefined)) {
-            const fkey = '_' + Doc.LayoutFieldKey(bestTarget);
+            const fkey = '_' + Doc.LayoutDataKey(bestTarget);
             if (bestTarget[fkey + '_clipWidth'] !== activeItem.config_clipWidth) {
                 bestTarget[fkey + '_clipWidth'] = activeItem.config_clipWidth;
                 changed = true;
@@ -655,7 +655,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             }
         }
         if (pinDataTypes?.dataannos || (!pinDataTypes && activeItem.config_annotations !== undefined)) {
-            const fkey = Doc.LayoutFieldKey(bestTarget);
+            const fkey = Doc.LayoutDataKey(bestTarget);
             const oldItems = DocListCast(bestTarget[fkey + '_annotations']).filter(doc => doc.layout_unrendered);
             const newItems = DocListCast(activeItem.config_annotations).map(doc => {
                 doc.hidden = false;
@@ -672,7 +672,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.config_pinLayoutData !== undefined)) {
             changed = true;
-            const layoutField = Doc.LayoutFieldKey(bestTarget);
+            const layoutField = Doc.LayoutDataKey(bestTarget);
             const transitioned = new Set<Doc>();
             StrListCast(activeItem.config_pinLayoutData)
                 .map(str => JSON.parse(str) as { id: string; x: number; y: number; back: string; fill: string; w: number; h: number; data: string; text: string })
@@ -740,7 +740,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const { activeItem, targetDoc } = this;
         const finished = (options: FocusViewOptions) => {
             afterNav?.(options);
-            targetDoc[Animation] = undefined;
+            targetDoc && (targetDoc[Animation] = undefined);
         };
         const selViewCache = Array.from(this.selectedArray);
         const dragViewCache = Array.from(this._dragArray);
@@ -756,7 +756,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             }
             finished(options);
         });
-        PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection);
+        targetDoc && activeItem && PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection);
     };
 
     static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: (options: FocusViewOptions) => void) {
@@ -806,42 +806,44 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     doHideBeforeAfter = () => {
         this.childDocs.forEach((doc, index) => {
-            const curDoc = Cast(doc, Doc, null);
-            const tagDoc = PresBox.targetRenderedDoc(curDoc);
-            const itemIndexes = this.getAllIndexes(this.tagDocs, curDoc);
-            let opacity = index === this.itemIndex ? 1 : undefined;
-            if (curDoc.presentation_hide) {
-                if (index !== this.itemIndex) {
-                    opacity = 1;
+            const curDoc = DocCast(doc);
+            if (curDoc) {
+                const tagDoc = PresBox.targetRenderedDoc(curDoc) ?? curDoc;
+                const itemIndexes = this.getAllIndexes(this.tagDocs, curDoc);
+                let opacity = index === this.itemIndex ? 1 : undefined;
+                if (curDoc.presentation_hide) {
+                    if (index !== this.itemIndex) {
+                        opacity = 1;
+                    }
                 }
-            }
-            const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex) ?? itemIndexes.slice().reverse().lastElement();
-            if (curDoc.presentation_hideBefore && index === hidingIndBef) {
-                if (index > this.itemIndex) {
-                    opacity = 0;
-                } else if (index === this.itemIndex || !curDoc.presentation_hideAfter) {
-                    opacity = 1;
+                const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex) ?? itemIndexes.slice().reverse().lastElement();
+                if (curDoc.presentation_hideBefore && index === hidingIndBef) {
+                    if (index > this.itemIndex) {
+                        opacity = 0;
+                    } else if (index === this.itemIndex || !curDoc.presentation_hideAfter) {
+                        opacity = 1;
+                    }
                 }
-            }
-            const hidingIndAft =
-                itemIndexes
-                    .slice()
-                    .reverse()
-                    .find(item => item <= this.itemIndex) ?? itemIndexes.lastElement();
-            if (curDoc.presentation_hideAfter && index === hidingIndAft) {
-                if (index < this.itemIndex) {
-                    opacity = 0;
-                } else if (index === this.itemIndex || !curDoc.presentation_hideBefore) {
-                    opacity = 1;
+                const hidingIndAft =
+                    itemIndexes
+                        .slice()
+                        .reverse()
+                        .find(item => item <= this.itemIndex) ?? itemIndexes.lastElement();
+                if (curDoc.presentation_hideAfter && index === hidingIndAft) {
+                    if (index < this.itemIndex) {
+                        opacity = 0;
+                    } else if (index === this.itemIndex || !curDoc.presentation_hideBefore) {
+                        opacity = 1;
+                    }
                 }
-            }
-            const hidingInd = itemIndexes.find(item => item === this.itemIndex);
-            if (curDoc.presentation_hide && index === hidingInd) {
-                if (index === this.itemIndex) {
-                    opacity = 0;
+                const hidingInd = itemIndexes.find(item => item === this.itemIndex);
+                if (curDoc.presentation_hide && index === hidingInd) {
+                    if (index === this.itemIndex) {
+                        opacity = 0;
+                    }
                 }
+                opacity !== undefined && (tagDoc.opacity = opacity === 1 ? undefined : opacity);
             }
-            opacity !== undefined && (tagDoc.opacity = opacity === 1 ? undefined : opacity);
         });
     };
 
@@ -940,7 +942,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     initializePresState = (startIndex: number) => {
         this.childDocs.forEach((doc, index) => {
-            const tagDoc = PresBox.targetRenderedDoc(doc);
+            const tagDoc = PresBox.targetRenderedDoc(doc) ?? doc;
             if (doc.presentation_hideBefore && index > startIndex) tagDoc.opacity = 0;
             if (doc.presentation_hideAfter && index < startIndex) tagDoc.opacity = 0;
             if (doc.presentation_indexed !== undefined && index >= startIndex) {
@@ -969,7 +971,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             this.layoutDoc.presentation_status = PresStatus.Autoplay;
             this.initializePresState(startIndex);
             const func = () => {
-                const delay = NumCast(this.activeItem.presentation_duration, this.activeItem.type === DocumentType.SCRIPTING ? 0 : 2500) + NumCast(this.activeItem.presentation_transition);
+                const delay = NumCast(this.activeItem?.presentation_duration, this.activeItem?.type === DocumentType.SCRIPTING ? 0 : 2500) + NumCast(this.activeItem?.presentation_transition);
                 this._presTimer = setTimeout(() => {
                     if (this.next() === false) this.layoutDoc.presentation_status = this._exitTrail?.() ?? PresStatus.Manual;
                     this.layoutDoc.presentation_status === PresStatus.Autoplay && func();
@@ -1060,7 +1062,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return !results.some(r => !r);
     };
 
-    childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
+    childLayoutTemplate = () => Docs.Create.PresSlideDocument();
     removeDocument = (doc: Doc | Doc[]) =>
         !toList(doc)
             .map(d => Doc.RemoveDocFromList(this.Document, this.fieldKey, d))
@@ -1077,13 +1079,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     @computed get listOfSelected() {
         return Array.from(this.selectedArray).map((doc, index) => {
-            const curDoc = Cast(doc, Doc, null);
+            const curDoc = DocCast(doc);
+            if (!curDoc) return null;
             const tagDoc = Cast(curDoc.presentation_targetDoc, Doc, null);
             if (curDoc && curDoc === this.activeItem)
                 return (
                     <div key={doc[Id]} className="selectedList-items">
                         <b>
-                            {index + 1}. {StrCast(curDoc.title)})
+                            {index + 1}. {StrCast(curDoc.title)}
                         </b>
                     </div>
                 );
@@ -1277,7 +1280,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const presCollection = collection;
         const dv = DocumentView.getDocumentView(presCollection);
         this.childDocs.forEach((doc, index) => {
-            const tagDoc = PresBox.targetRenderedDoc(doc);
+            const tagDoc = PresBox.targetRenderedDoc(doc) ?? doc;
             const srcContext = Cast(tagDoc.embedContainer, Doc, null);
             const labelCreator = (top: number, left: number, edge: number, fontSize: number) => (
                 <div className="pathOrder" key={tagDoc.id + 'pres' + index} style={{ top, left, width: edge, height: edge, fontSize }} onClick={() => this.selectElement(doc)}>
@@ -1526,17 +1529,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // Applies the slide transiiton settings to all docs in the array
     @undoBatch
     applyTo = (array: Doc[]) => {
-        this.updateMovement(this.activeItem.presentation_movement as PresMovement, true);
-        this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
-        this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true);
-        this.updateEffectDirection(this.activeItem.presentation_effectDirection as PresEffectDirection, true);
-        const { presentation_transition: pt, presentation_duration: pd, presentation_hideBefore: ph, presentation_hideAfter: pa } = this.activeItem;
-        array.forEach(curDoc => {
-            curDoc.presentation_transition = pt;
-            curDoc.presentation_duration = pd;
-            curDoc.presentation_hideBefore = ph;
-            curDoc.presentation_hideAfter = pa;
-        });
+        if (this.activeItem) {
+            this.updateMovement(this.activeItem.presentation_movement as PresMovement, true);
+            this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
+            this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true);
+            this.updateEffectDirection(this.activeItem.presentation_effectDirection as PresEffectDirection, true);
+            const { presentation_transition: pt, presentation_duration: pd, presentation_hideBefore: ph, presentation_hideAfter: pa } = this.activeItem;
+            array.forEach(curDoc => {
+                curDoc.presentation_transition = pt;
+                curDoc.presentation_duration = pd;
+                curDoc.presentation_hideBefore = ph;
+                curDoc.presentation_hideAfter = pa;
+            });
+        }
     };
 
     @computed get visibilityDurationDropdown() {
@@ -1673,15 +1678,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 onClick={() => {
                                     activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined;
                                     activeItem.presentation_hideBefore = activeItem.presentation_indexed !== undefined;
-                                    const tagDoc = PresBox.targetRenderedDoc(this.activeItem);
+                                    const tagDoc = PresBox.targetRenderedDoc(activeItem) ?? activeItem;
                                     const type = DocCast(tagDoc?.annotationOn)?.type ?? tagDoc.type;
                                     activeItem.presentation_indexedStart = type === DocumentType.COL ? 1 : 0;
                                     // a progressivized slide doesn't have sub-slides, but rather iterates over the data list of the target being progressivized.
                                     // to avoid creating a new slide to correspond to each of the target's data list, we create a computedField to refernce the target's data list.
-                                    let dataField = Doc.LayoutFieldKey(tagDoc);
+                                    let dataField = Doc.LayoutDataKey(tagDoc);
                                     if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField += '_annotations';
 
-                                    if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`);
+                                    if (DocCast(activeItem.presentation_targetDoc)?.annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`);
                                     else activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc?.["${dataField}"]`);
                                 }}>
                                 Enable
@@ -1797,12 +1802,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 onClick={() => {
                                     this.updateEffect(elem.effect, false);
                                     this.updateEffectDirection(elem.direction);
-                                    this.updateEffectTiming(this.activeItem, {
-                                        type: SpringType.CUSTOM,
-                                        stiffness: elem.stiffness,
-                                        damping: elem.damping,
-                                        mass: elem.mass,
-                                    });
+                                    this.activeItem &&
+                                        this.updateEffectTiming(this.activeItem, {
+                                            type: SpringType.CUSTOM,
+                                            stiffness: elem.stiffness,
+                                            damping: elem.damping,
+                                            mass: elem.mass,
+                                        });
                                 }}>
                                 <SlideEffect dir={elem.direction} presEffect={elem.effect} springSettings={elem} infinite>
                                     <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[i] }} />
@@ -1947,8 +1953,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     formLabelPlacement="left"
                                     closeOnSelect
                                     items={easeItems}
-                                    selectedVal={this.activeItem.presentation_easeFunc ? (StrCast(this.activeItem.presentation_easeFunc).startsWith('cubic') ? 'custom' : StrCast(this.activeItem.presentation_easeFunc)) : 'ease'}
-                                    setSelectedVal={val => typeof val === 'string' && this.setEaseFunc(this.activeItem, val !== 'custom' ? val : TIMING_DEFAULT_MAPPINGS.ease)}
+                                    selectedVal={this.activeItem?.presentation_easeFunc ? (StrCast(this.activeItem.presentation_easeFunc).startsWith('cubic') ? 'custom' : StrCast(this.activeItem.presentation_easeFunc)) : 'ease'}
+                                    setSelectedVal={val => typeof val === 'string' && this.activeItem && this.setEaseFunc(this.activeItem, val !== 'custom' ? val : TIMING_DEFAULT_MAPPINGS.ease)}
                                     dropdownType={DropdownType.SELECT}
                                     type={Type.TERT}
                                 />
@@ -2145,9 +2151,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get mediaOptionsDropdown() {
         const { activeItem } = this;
         if (activeItem && this.targetDoc) {
-            const renderTarget = PresBox.targetRenderedDoc(this.activeItem);
+            const renderTarget = PresBox.targetRenderedDoc(this.activeItem ?? this.targetDoc) ?? this.targetDoc;
             const clipStart = NumCast(renderTarget.clipStart);
-            const clipEnd = NumCast(renderTarget.clipEnd, clipStart + NumCast(renderTarget[Doc.LayoutFieldKey(renderTarget) + '_duration']));
+            const clipEnd = NumCast(renderTarget.clipEnd, clipStart + NumCast(renderTarget[Doc.LayoutDataKey(renderTarget) + '_duration']));
             const configClipEnd = NumCast(activeItem.config_clipEnd) < NumCast(activeItem.config_clipStart) ? clipEnd - clipStart : NumCast(activeItem.config_clipEnd);
             return (
                 <div className="presBox-ribbon" onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
@@ -2699,7 +2705,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const presEnd =
             !this.layoutDoc.presLoop &&
             this.itemIndex === this.childDocs.length - 1 &&
-            (this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
+            (this.activeItem?.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
         const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
         const inOverlay = Doc.IsInMyOverlay(this.Document);
         // Case 1: There are still other frames and should go through all frames before going to next slide
@@ -2892,7 +2898,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const presEnd =
             !this.layoutDoc.presLoop &&
             this.itemIndex === this.childDocs.length - 1 &&
-            (this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
+            (this.activeItem?.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
         const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0;
         return this._props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player
             <div
@@ -2984,16 +2990,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             />
                         ) : null}
                     </div>
-                    {/* {
-                        // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack
-                        <Tooltip title={<div className="dash-tooltip">{'Click on document to pin to presentaiton or make a marquee selection to pin your desired view'}</div>}>
-                            <button className="add-slide-button" onPointerDown={this.startMarqueeCreateSlide}>
-                                + NEW SLIDE
-                            </button>
-                        </Tooltip>
-                    } */}
                 </div>
-                {/* presbox chatbox */}
                 {this._chatActive && <div className="presBox-chatbox" />}
             </div>
         );
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
deleted file mode 100644
index 9ac2b5a94..000000000
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ /dev/null
@@ -1,308 +0,0 @@
-$light-blue: #aeddf8;
-$dark-blue: #5b9fdd;
-$light-background: #ececec;
-$slide-background: #d5dce2;
-$slide-active: #5b9fdd;
-
-.testingv2 {
-    background-color: red;
-}
-
-.presItem-container {
-    cursor: grab;
-    display: flex;
-    grid-template-columns: 20px auto;
-    font-family: Roboto;
-    letter-spacing: normal;
-    position: relative;
-    pointer-events: all;
-    width: 100%;
-    height: 100%;
-    font-weight: 400;
-    -webkit-touch-callout: none;
-    -webkit-user-select: none;
-    -khtml-user-select: none;
-    -moz-user-select: none;
-    -ms-user-select: none;
-    user-select: none;
-    align-items: center;
-
-    //     .presItem-number {
-    //         margin-top: 3.5px;
-    //         font-size: 12px;
-    //         font-weight: 700;
-    //         text-align: center;
-    //         justify-self: center;
-    //         align-self: flex-start;
-    //         position: relative;
-    //         display: inline-block;
-    //         overflow: hidden;
-    //     }
-}
-
-.presItem-slide {
-    position: relative;
-    height: 100%;
-    width: 100%;
-    border-bottom: 0.5px solid grey;
-    display: flex;
-    align-items: center;
-
-    .presItem-number {
-        cursor: pointer;
-        &:hover {
-            background-color: $light-blue;
-        }
-    }
-    .presItem-name {
-        display: flex;
-        min-width: 20px;
-        z-index: 300;
-        top: 2px;
-        align-self: center;
-        font-size: 11px;
-        font-family: Roboto;
-        font-weight: 500;
-        position: relative;
-        padding-left: 10px;
-        padding-right: 10px;
-        letter-spacing: normal;
-        width: max-content;
-        text-overflow: ellipsis;
-        overflow: hidden;
-        white-space: pre;
-    }
-
-    .presItem-docName {
-        min-width: 20px;
-        z-index: 300;
-        align-self: center;
-        font-size: 9px;
-        font-family: Roboto;
-        font-weight: 300;
-        position: relative;
-        padding-left: 10px;
-        padding-right: 10px;
-        letter-spacing: normal;
-        width: max-content;
-        text-overflow: ellipsis;
-        overflow: hidden;
-        white-space: pre;
-        grid-row: 2;
-        grid-column: 1/6;
-    }
-
-    .presItem-time {
-        align-self: center;
-        position: relative;
-        padding-right: 10px;
-        top: 1px;
-        font-size: 10;
-        font-weight: 300;
-        font-family: Roboto;
-        z-index: 300;
-        letter-spacing: normal;
-    }
-
-    .presItem-embedded {
-        overflow: hidden;
-        grid-row: 3;
-        grid-column: 1/8;
-        position: relative;
-        display: inline-block;
-    }
-
-    .presItem-embeddedMask {
-        width: 100%;
-        height: 100%;
-        position: absolute;
-        border-radius: 3px;
-        top: 0;
-        left: 0;
-        z-index: 1;
-        overflow: hidden;
-    }
-
-    .presItem-slideButtons {
-        display: flex;
-        position: absolute;
-        width: max-content;
-        justify-self: right;
-        justify-content: flex-end;
-
-        .slideButton {
-            cursor: pointer;
-            position: relative;
-            border-radius: 100px;
-            z-index: 300;
-            width: 18px;
-            height: 18px;
-            display: flex;
-            font-size: 12px;
-            justify-self: center;
-            align-self: center;
-            background-color: rgba(0, 0, 0, 0.5);
-            color: white;
-            justify-content: center;
-            align-items: center;
-            transition: 0.2s;
-            margin-right: 3px;
-        }
-
-        .slideButton:hover {
-            background-color: rgba(0, 0, 0, 1);
-            transform: scale(1.2);
-        }
-    }
-}
-
-// .presItem-slide:hover {
-//     .presItem-slideButtons {
-//         display: flex;
-//         grid-column: 7;
-//         grid-row: 1/3;
-//         width: max-content;
-//         justify-self: right;
-//         justify-content: flex-end;
-
-//         .slideButton {
-//             cursor: pointer;
-//             position: relative;
-//             border-radius: 100px;
-//             z-index: 300;
-//             width: 18px;
-//             height: 18px;
-//             display: flex;
-//             font-size: 12px;
-//             justify-self: center;
-//             align-self: center;
-//             background-color: rgba(0, 0, 0, 0.5);
-//             color: white;
-//             justify-content: center;
-//             align-items: center;
-//             transition: 0.2s;
-//             margin-right: 3px;
-//         }
-
-//         .slideButton:hover {
-//             background-color: rgba(0, 0, 0, 1);
-//             transform: scale(1.2);
-//         }
-//     }
-// }
-
-.presItem-slide.active {
-    //box-shadow: 0 0 0px 2.5px $dark-blue;
-    border: $dark-blue solid 2.5px;
-}
-
-.presItem-slide.group {
-    border-radius: 5px;
-}
-
-.presItem-slide.activeGroup {
-    border-radius: 5px;
-    box-shadow: 0 0 0px 2.5px $dark-blue;
-}
-
-.presItem-groupSlideContainer {
-    position: absolute;
-    /* grid-row: 3; */
-    /* grid-column: 1/8; */
-    top: 28;
-    display: block;
-    background: #92adb9;
-    width: 100%;
-    height: 10px;
-    border-radius: 0px 0px 5px 5px;
-    height: calc(100% - 28px);
-    display: grid;
-    grid-template-rows: auto auto auto;
-    grid-template-columns: 100%;
-    justify-items: left;
-    align-items: center;
-}
-
-.presItem-groupSlide {
-    position: relative;
-    background-color: #d5dce2;
-    border-radius: 5px;
-    height: calc(100% - 7px);
-    width: calc(100% - 20px);
-    left: 15px;
-    /* height: 20px; */
-    /* width: calc(100% - 19px); */
-    display: flex;
-    grid-template-rows: 16px 10px auto;
-    grid-template-columns: max-content max-content max-content max-content auto;
-}
-
-.presItem-multiDrag {
-    font-family: Roboto;
-    font-weight: 600;
-    color: white;
-    text-align: center;
-    justify-content: center;
-    align-content: center;
-    width: 100px;
-    height: 30px;
-    position: absolute;
-    background-color: $dark-blue;
-    z-index: 4000;
-    border-radius: 10px;
-    box-shadow: black 0.4vw 0.4vw 0.8vw;
-    line-height: 30px;
-}
-
-.presItem-miniSlide {
-    font-weight: 700;
-    font-size: 12;
-    grid-column: 1/8;
-    align-self: center;
-    justify-self: center;
-    background-color: #d5dce2;
-    width: 26px;
-    text-align: center;
-    height: 26px;
-    line-height: 28px;
-    border-radius: 100%;
-}
-
-.presItem-miniSlide.active {
-    box-shadow: 0 0 0px 1.5px $dark-blue;
-}
-
-.expandButton {
-    cursor: pointer;
-    position: absolute;
-    border-radius: 100px;
-    bottom: 0;
-    left: -18;
-    z-index: 300;
-    width: 15px;
-    height: 15px;
-    display: flex;
-    font-size: 12px;
-    justify-self: center;
-    align-self: center;
-    background-color: #92adb9;
-    color: white;
-    justify-content: center;
-    align-items: center;
-    transition: 0.2s;
-    margin-right: 3px;
-}
-
-.expandButton:hover {
-    background-color: rgba(0, 0, 0, 1);
-    transform: scale(1.2);
-}
-
-.presItem-groupNum {
-    color: #d5dce2;
-    position: absolute;
-    left: -15px;
-    top: 1;
-    font-weight: 600;
-    font-size: 12;
-}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
deleted file mode 100644
index 7e0375275..000000000
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ /dev/null
@@ -1,628 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
-import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
-import { Id } from '../../../../fields/FieldSymbols';
-import { List } from '../../../../fields/List';
-import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction } from '../../../../Utils';
-import { Docs } from '../../../documents/Documents';
-import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
-import { DragManager } from '../../../util/DragManager';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { Transform } from '../../../util/Transform';
-import { undoable, undoBatch } from '../../../util/UndoManager';
-import { TreeView } from '../../collections/TreeView';
-import { ViewBoxBaseComponent } from '../../DocComponent';
-import { EditableView } from '../../EditableView';
-import { Colors } from '../../global/globalEnums';
-import { PinDocView } from '../../PinFuncs';
-import { StyleProp } from '../../StyleProp';
-import { returnEmptyDocViewList } from '../../StyleProvider';
-import { DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
-import { PresBox } from './PresBox';
-import './PresElementBox.scss';
-import { PresMovement } from './PresEnums';
-/**
- * This class models the view a document added to presentation will have in the presentation.
- * It involves some functionality for its buttons and options.
- */
-@observer
-export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
-    public static LayoutString(fieldKey: string) {
-        return FieldView.LayoutString(PresElementBox, fieldKey);
-    }
-    private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
-    private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
-    private _titleRef: React.RefObject<EditableView> = React.createRef();
-    private _heightDisposer: IReactionDisposer | undefined;
-    readonly expandViewHeight = 100;
-    readonly collapsedHeight = 35;
-
-    constructor(props: FieldViewProps) {
-        super(props);
-        makeObservable(this);
-    }
-
-    @observable _dragging = false;
-
-    // the presentation view that renders this slide
-    @computed get presBoxView() {
-        return this.DocumentView?.()
-            .containerViewPath?.()
-            .slice()
-            .reverse()
-            .find(dv => dv?.ComponentView instanceof PresBox)?.ComponentView as PresBox;
-    }
-
-    // the presentation view document that renders this slide
-    @computed get presBox() {
-        return this.presBoxView?.Document;
-    }
-
-    // Since this node is being rendered with a template, this method retrieves
-    // the actual slide being rendered from the auto-generated rendering template
-    @computed get slideDoc() {
-        return this.rootDoc;
-    }
-
-    // this is the document in the workspaces that is targeted by the slide
-    @computed get targetDoc() {
-        return Cast(this.slideDoc.presentation_targetDoc, Doc, null) || this.slideDoc;
-    }
-
-    // computes index of this presentation slide in the presBox list
-    @computed get indexInPres() {
-        return this.presBoxView?.SlideIndex(this.slideDoc) ?? 0;
-    }
-
-    @computed get selectedArray() {
-        return this.presBoxView?.selectedArray;
-    }
-
-    @computed get videoRecordingIsInOverlay() {
-        return Doc.MyOverlayDocs.some(doc => doc.slides === this.slideDoc);
-    }
-
-    componentDidMount() {
-        this.layoutDoc.layout_hideLinkButton = true;
-        this._heightDisposer = reaction(
-            () => ({ expand: this.slideDoc.presentation_expandInlineButton, height: this.collapsedHeight }),
-            ({ expand, height }) => {
-                this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0);
-            },
-            { fireImmediately: true }
-        );
-    }
-    componentWillUnmount() {
-        this._heightDisposer?.();
-    }
-
-    presExpandDocumentClick = () => {
-        this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton;
-    };
-    embedHeight = () => this.collapsedHeight + this.expandViewHeight;
-    embedWidth = () => this._props.PanelWidth() / 2;
-    // prettier-ignore
-    styleProvider = ( doc: Doc | undefined, props: Opt<FieldViewProps>, property: string ) => 
-        (property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property));
-    /**
-     * The function that is responsible for rendering a preview or not for this
-     * presentation element.
-     */
-    @computed get renderEmbeddedInline() {
-        return !this.slideDoc.presentation_expandInlineButton || !this.targetDoc ? null : (
-            <div className="presItem-embedded" style={{ height: this.embedHeight(), width: '50%' }}>
-                <DocumentView
-                    Document={PresBox.targetRenderedDoc(this.slideDoc)}
-                    PanelWidth={this.embedWidth}
-                    PanelHeight={this.embedHeight}
-                    isContentActive={this._props.isContentActive}
-                    styleProvider={this.styleProvider}
-                    hideLinkButton
-                    ScreenToLocalTransform={Transform.Identity}
-                    renderDepth={this._props.renderDepth + 1}
-                    containerViewPath={returnEmptyDocViewList}
-                    childFilters={this._props.childFilters}
-                    childFiltersByRanges={this._props.childFiltersByRanges}
-                    searchFilterDocs={this._props.searchFilterDocs}
-                    addDocument={returnFalse}
-                    removeDocument={returnFalse}
-                    fitContentsToBox={returnTrue}
-                    moveDocument={this._props.moveDocument!}
-                    focus={emptyFunction}
-                    whenChildContentsActiveChanged={returnFalse}
-                    addDocTab={returnFalse}
-                    pinToPres={returnFalse}
-                />
-            </div>
-        );
-    }
-
-    @computed get renderGroupSlides() {
-        const childDocs = DocListCast(this.targetDoc.data);
-        const groupSlides = childDocs.map((doc: Doc, ind: number) => (
-            <div
-                key={doc[Id]}
-                className="presItem-groupSlide"
-                onClick={e => {
-                    e.stopPropagation();
-                    e.preventDefault();
-                    this.presBoxView?.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
-                    this.presExpandDocumentClick();
-                }}>
-                <div className="presItem-groupNum">{`${ind + 1}.`}</div>
-                <div className="presItem-name">
-                    <EditableView
-                        ref={this._titleRef}
-                        editing={undefined}
-                        contents={StrCast(doc.title)}
-                        overflow="ellipsis"
-                        GetValue={() => StrCast(doc.title)}
-                        SetValue={(value: string) => {
-                            doc.title = !value.trim().length ? '-untitled-' : value;
-                            return true;
-                        }}
-                    />
-                </div>
-            </div>
-        ));
-        return groupSlides;
-    }
-
-    @computed get transition() {
-        let transitionInS: number;
-        if (this.slideDoc.presentation_transition) transitionInS = NumCast(this.slideDoc.presentation_transition) / 1000;
-        else transitionInS = 0.5;
-        return this.slideDoc.presentation_movement === PresMovement.Jump || this.slideDoc.presentation_movement === PresMovement.None ? null : 'M: ' + transitionInS + 's';
-    }
-
-    @action
-    headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
-        const element = e.target as HTMLDivElement;
-        e.stopPropagation();
-        e.preventDefault();
-        if (element && !(e.ctrlKey || e.metaKey || e.button === 2)) {
-            this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false);
-            setupMoveUpEvents(this, e, this.startDrag, emptyFunction, clickEv => {
-                clickEv.stopPropagation();
-                clickEv.preventDefault();
-                this.presBoxView?.modifierSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, clickEv.shiftKey || clickEv.ctrlKey || clickEv.metaKey, clickEv.ctrlKey || clickEv.metaKey, clickEv.shiftKey);
-                this.presBoxView?.activeItem && this.showRecording(this.presBoxView?.activeItem);
-            });
-        }
-    };
-
-    /**
-     * Function to drag and drop the pres element to a diferent location
-     */
-    startDrag = (e: PointerEvent) => {
-        this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false);
-        const miniView: boolean = this.toolbarWidth <= 100;
-        const activeItem = this.slideDoc;
-        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.DocumentView?.()?._props.treeViewDoc;
-        dragData.moveDocument = this._props.moveDocument;
-        const dragItem: HTMLElement[] = [];
-        const classesToRestore = new Map<HTMLElement, string>();
-        if (dragArray.length === 1) {
-            const doc = this._itemRef.current || dragArray[0];
-            if (doc) {
-                classesToRestore.set(doc, doc.className);
-                doc.className = miniView ? 'presItem-miniSlide' : 'presItem-slide';
-                dragItem.push(doc);
-            }
-        } else if (dragArray.length >= 1) {
-            const doc = document.createElement('div');
-            doc.className = 'presItem-multiDrag';
-            doc.innerText = 'Move ' + (this.selectedArray?.size ?? 0) + ' slides';
-            doc.style.position = 'absolute';
-            doc.style.top = e.clientY + 'px';
-            doc.style.left = e.clientX - 50 + 'px';
-            dragItem.push(doc);
-        }
-
-        if (activeItem) {
-            runInAction(() => {
-                this._dragging = true;
-            });
-            DragManager.StartDocumentDrag(
-                dragItem.map(ele => ele),
-                dragData,
-                e.clientX,
-                e.clientY,
-                undefined,
-                action(() => {
-                    Array.from(classesToRestore).forEach(pair => (pair[0].className = pair[1]));
-                    this._dragging = false;
-                })
-            );
-            return true;
-        }
-        return false;
-    };
-
-    onPointerOver = () => {
-        document.removeEventListener('pointermove', this.onPointerMove);
-        document.addEventListener('pointermove', this.onPointerMove);
-    };
-
-    onPointerMove = (e: PointerEvent) => {
-        const slide = this._itemRef.current;
-        const dragIsPresItem = DragManager.docsBeingDragged.some(d => d.presentation_targetDoc);
-        if (slide && dragIsPresItem) {
-            const rect = slide.getBoundingClientRect();
-            const y = e.clientY - rect.top; // y position within the element.
-            const height = slide.clientHeight;
-            const halfLine = height / 2;
-            if (y <= halfLine) {
-                slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`;
-                slide.style.borderBottom = '0px';
-            } else if (y > halfLine) {
-                slide.style.borderTop = '0px';
-                slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
-            }
-        }
-        document.removeEventListener('pointermove', this.onPointerMove);
-    };
-
-    onPointerLeave = () => {
-        const slide = this._itemRef.current;
-        if (slide) {
-            slide.style.borderTop = '0px';
-            slide.style.borderBottom = '0px';
-        }
-        document.removeEventListener('pointermove', this.onPointerMove);
-    };
-
-    @action
-    toggleProperties = () => {
-        if (SnappingManager.PropertiesWidth < 5) {
-            SnappingManager.SetPropertiesWidth(250);
-        }
-    };
-
-    removePresentationItem = undoable(
-        action((e: React.MouseEvent) => {
-            e.stopPropagation();
-            if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
-                this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1;
-            }
-            this._props.removeDocument?.(this.slideDoc);
-            this.presBoxView?.removeFromSelectedArray(this.slideDoc);
-            this.removeAllRecordingInOverlay();
-        }),
-        'Remove doc from pres trail'
-    );
-
-    // set title of the individual pres slide
-    onSetValue = undoable(
-        action((value: string) => {
-            this.slideDoc.title = !value.trim().length ? '-untitled-' : value;
-            return true;
-        }),
-        'set title of pres element'
-    );
-
-    /**
-     * Method called for updating the view of the currently selected document
-     *
-     * @param targetDoc
-     * @param activeItem
-     */
-    @undoBatch
-    updateCapturedContainerLayout = (presTargetDoc: Doc, activeItem: Doc) => {
-        const targetDoc = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
-        activeItem.config_x = NumCast(targetDoc.x);
-        activeItem.config_y = NumCast(targetDoc.y);
-        activeItem.config_rotation = NumCast(targetDoc.rotation);
-        activeItem.config_width = NumCast(targetDoc.width);
-        activeItem.config_height = NumCast(targetDoc.height);
-        activeItem.config_pinLayout = !activeItem.config_pinLayout;
-        // activeItem.config_pinLayout = true;
-    };
-
-    /**
-     * Method called for updating the view of the currently selected document
-     *
-     * @param presTargetDoc
-     * @param activeItem
-     */
-    updateCapturedViewContents = undoable(
-        action((presTargetDoc: Doc, activeItem: Doc) => {
-            const target = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
-            PinDocView(activeItem, { pinData: PresBox.pinDataTypes(target) }, target);
-        }),
-        'updated captured view contents'
-    );
-
-    // a previously recorded video will have timecode defined
-    static videoIsRecorded = (activeItem: Opt<Doc>) => 'layout_currentTimecode' in (DocCast(activeItem?.recording) ?? {});
-
-    removeAllRecordingInOverlay = () => Doc.MyOverlayDocs.filter(doc => doc.slides === this.slideDoc).forEach(Doc.RemFromMyOverlay);
-
-    /// remove all videos that have been recorded from overlay (leave videso that are being recorded to avoid losing data)
-    static removeEveryExistingRecordingInOverlay = () => {
-        Doc.MyOverlayDocs.filter(doc => doc.slides !== null && PresElementBox.videoIsRecorded(DocCast(doc.slides))) //
-            .forEach(Doc.RemFromMyOverlay);
-    };
-
-    hideRecording = undoable(
-        action((e: React.MouseEvent) => {
-            e.stopPropagation();
-            this.removeAllRecordingInOverlay();
-        }),
-        'hide video recording'
-    );
-
-    showRecording = undoable(
-        action((activeItem: Doc, iconClick: boolean = false) => {
-            // remove the overlays on switch *IF* not opened from the specific icon
-            if (!iconClick) PresElementBox.removeEveryExistingRecordingInOverlay();
-
-            activeItem.recording && Doc.AddToMyOverlay(DocCast(activeItem.recording));
-        }),
-        'show video recording'
-    );
-
-    startRecording = undoable(
-        action((e: React.MouseEvent, activeItem: Doc) => {
-            e.stopPropagation();
-            if (PresElementBox.videoIsRecorded(activeItem)) {
-                // if we already have an existing recording
-                this.showRecording(activeItem, true);
-                //   // if we already have an existing recording
-                //   Doc.AddToMyOverlay(Cast(activeItem.recording, Doc, null));
-            } else {
-                // we dont have any recording
-                // Remove every recording that already exists in overlay view
-                // this is a design decision to clear to focus in on the recoding mode
-                PresElementBox.removeEveryExistingRecordingInOverlay();
-
-                // create and add a recording to the slide
-                // make recording box appear in the bottom right corner of the screen
-                Doc.AddToMyOverlay(
-                    (activeItem.recording = Docs.Create.WebCamDocument('', {
-                        _width: 384,
-                        _height: 216,
-                        overlayX: window.innerWidth - 384 - 20,
-                        overlayY: window.innerHeight - 216 - 20,
-                        layout_hideDocumentButtonBar: true,
-                        layout_hideDecorationTitle: true,
-                        layout_hideOpenButton: true,
-                        cloneFieldFilter: new List<string>(['isSystem']),
-                        slides: activeItem, // attach the slide to the recording
-                    }))
-                );
-            }
-        }),
-        'start video recording'
-    );
-
-    @undoBatch
-    lfg = (e: React.MouseEvent) => {
-        e.stopPropagation();
-        // TODO: fix this bug
-        // const { toggleChildrenRun } = this.slideDoc;
-        TreeView.ToggleChildrenRun.get(this.slideDoc)?.();
-
-        // call this.slideDoc.recurChildren() to get all the children
-        // if (iconClick) PresElementBox.showVideo = false;
-    };
-
-    @computed
-    get toolbarWidth(): number {
-        const presBoxDocView = DocumentView.getDocumentView(this.presBox);
-        const width = NumCast(this.presBox?._width);
-        return presBoxDocView ? presBoxDocView._props.PanelWidth() : width || 300;
-    }
-
-    @computed get presButtons() {
-        const { presBox, targetDoc, slideDoc: activeItem } = this;
-        const presBoxColor = StrCast(presBox?._backgroundColor);
-        const presColorBool = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
-        const hasChildren = BoolCast(this.slideDoc?.hasChildren);
-
-        const items: JSX.Element[] = [];
-
-        items.push(
-            <Tooltip key="slide" title={<div className="dash-tooltip">Update captured doc layout</div>}>
-                <div
-                    className="slideButton"
-                    onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedContainerLayout(targetDoc, activeItem), true)}
-                    style={{ opacity: activeItem.config_pinLayout ? 1 : 0.5, fontWeight: 700, display: 'flex' }}>
-                    L
-                </div>
-            </Tooltip>
-        );
-        items.push(
-            <Tooltip key="flex" title={<div className="dash-tooltip">Update captured doc content</div>}>
-                <div
-                    className="slideButton"
-                    onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedViewContents(targetDoc, activeItem))}
-                    style={{ opacity: activeItem.config_pinData || activeItem.config_pinView ? 1 : 0.5, fontWeight: 700, display: 'flex' }}>
-                    C
-                </div>
-            </Tooltip>
-        );
-        items.push(
-            <Tooltip key="slash" title={<div className="dash-tooltip">{this.videoRecordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
-                <div className="slideButton" onClick={e => (this.videoRecordingIsInOverlay ? this.hideRecording(e) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
-                    <FontAwesomeIcon icon={`video${this.videoRecordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} />
-                </div>
-            </Tooltip>
-        );
-        if (this.indexInPres !== 0) {
-            items.push(
-                <Tooltip
-                    key="arrow"
-                    title={
-                        <div className="dash-tooltip">
-                            {!activeItem.presentation_groupWithUp
-                                ? 'Not grouped with previous slide (click to group)'
-                                : activeItem.presentation_groupWithUp === 1
-                                  ? 'Run simultaneously with previous slide (click again to run after)'
-                                  : 'Run after previous slide (click to ungroup from previous)'}
-                        </div>
-                    }>
-                    <div
-                        className="slideButton"
-                        onClick={() => {
-                            activeItem.presentation_groupWithUp = (NumCast(activeItem.presentation_groupWithUp) + 1) % 3;
-                        }}
-                        style={{
-                            zIndex: 1000 - this.indexInPres,
-                            fontWeight: 700,
-                            backgroundColor: activeItem.presentation_groupWithUp ? (presColorBool ? presBoxColor : Colors.MEDIUM_BLUE) : undefined,
-                            outline: NumCast(activeItem.presentation_groupWithUp) > 1 ? 'solid black 1px' : undefined,
-                            height: activeItem.presentation_groupWithUp ? 53 : 18,
-                            transform: activeItem.presentation_groupWithUp ? 'translate(0, -17px)' : undefined,
-                        }}>
-                        <div style={{ transform: activeItem.presentation_groupWithUp ? 'rotate(180deg) translate(0, -17.5px)' : 'rotate(0deg)' }}>
-                            <FontAwesomeIcon icon="arrow-up" onPointerDown={e => e.stopPropagation()} />
-                        </div>
-                    </div>
-                </Tooltip>
-            );
-        }
-        items.push(
-            <Tooltip key="eye" title={<div className="dash-tooltip">{this.slideDoc.presentation_expandInlineButton ? 'Minimize' : 'Expand'}</div>}>
-                <div
-                    className="slideButton"
-                    onClick={e => {
-                        e.stopPropagation();
-                        this.presExpandDocumentClick();
-                    }}>
-                    <FontAwesomeIcon icon={this.slideDoc.presentation_expandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
-                </div>
-            </Tooltip>
-        );
-        if (!Doc.noviceMode && hasChildren) {
-            // TODO: replace with if treeveiw, has childrenDocs
-            items.push(
-                <Tooltip key="children" title={<div className="dash-tooltip">Run child processes (tree only)</div>}>
-                    <div
-                        className="slideButton"
-                        onClick={e => {
-                            e.stopPropagation();
-                            this.lfg(e);
-                        }}
-                        style={{ fontWeight: 700 }}>
-                        <FontAwesomeIcon icon="circle-play" onPointerDown={e => e.stopPropagation()} />
-                    </div>
-                </Tooltip>
-            );
-        }
-        items.push(
-            <Tooltip key="trash" title={<div className="dash-tooltip">Remove from presentation</div>}>
-                <div className="slideButton" onClick={this.removePresentationItem}>
-                    <FontAwesomeIcon icon="trash" onPointerDown={e => e.stopPropagation()} />
-                </div>
-            </Tooltip>
-        );
-        items.push(
-            <Tooltip key="customize-slide" title={<div className="dash-tooltip">Customize Slide</div>}>
-                <div
-                    className={'slideButton'}
-                    onClick={() => {
-                        this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false);
-                        PresBox.Instance.navigateToActiveItem();
-                        PresBox.Instance.openProperties();
-                        PresBox.Instance.slideToModify = this.Document;
-                    }}>
-                    <FontAwesomeIcon icon={'edit'} onPointerDown={e => e.stopPropagation()} />
-                </div>
-            </Tooltip>
-        );
-        return items;
-    }
-
-    @computed get mainItem() {
-        const { presBox, slideDoc: activeItem } = this;
-        const isSelected: boolean = !!this.selectedArray?.has(activeItem);
-        const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres;
-        const miniView: boolean = this.toolbarWidth <= 110;
-        const presBoxColor: string = StrCast(presBox?._backgroundColor);
-        const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
-
-        return (
-            <div
-                className="presItem-container"
-                key={activeItem[Id] + this.indexInPres}
-                ref={this._itemRef}
-                style={{
-                    backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
-                    opacity: this._dragging ? 0.3 : 1,
-                    paddingLeft: NumCast(this.layoutDoc._xPadding, this._props.xPadding),
-                    paddingRight: NumCast(this.layoutDoc._xPadding, this._props.xPadding),
-                    paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
-                    paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
-                }}
-                onDoubleClick={action(() => {
-                    this.toggleProperties();
-                    this.presBoxView?.regularSelect(activeItem, this._itemRef.current!, this._dragRef.current!, false);
-                })}
-                onPointerOver={this.onPointerOver}
-                onPointerLeave={this.onPointerLeave}
-                onPointerDown={this.headerDown}>
-                {miniView ? (
-                    <div className={`presItem-miniSlide ${isSelected ? 'active' : ''}`} ref={this._dragRef}>
-                        {`${this.indexInPres + 1}.`}
-                    </div>
-                ) : (
-                    <div
-                        ref={this._dragRef}
-                        className={`presItem-slide ${isCurrent ? 'active' : ''}${activeItem.runProcess ? ' testingv2' : ''}`}
-                        style={{
-                            display: 'infline-block',
-                            backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string,
-                            // layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
-                            border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined,
-                        }}>
-                        <div
-                            className="presItem-name"
-                            style={{
-                                display: 'inline-flex',
-                                pointerEvents: isSelected ? undefined : 'none',
-                                width: `calc(100% ${activeItem.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
-                                cursor: isSelected ? 'text' : 'grab',
-                            }}>
-                            <div
-                                className="presItem-number"
-                                title="select without navigation"
-                                onPointerDown={e => {
-                                    e.stopPropagation();
-                                    if (this._itemRef.current && this._dragRef.current) {
-                                        this.presBoxView?.modifierSelect(activeItem, this._itemRef.current, this._dragRef.current, true, false, false);
-                                    }
-                                }}
-                                onClick={e => e.stopPropagation()}>{`${this.indexInPres + 1}.  `}</div>
-                            <EditableView ref={this._titleRef} oneLine editing={!isSelected ? false : undefined} contents={StrCast(activeItem.title)} overflow="ellipsis" GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} />
-                        </div>
-                        {/* <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip> */}
-                        {/* <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip> */}
-                        <div className="presItem-slideButtons" style={{ position: 'absolute', right: 0 }}>
-                            {...this.presButtons}
-                        </div>
-                        {this.renderEmbeddedInline}
-                    </div>
-                )}
-            </div>
-        );
-    }
-
-    render() {
-        return !(this.slideDoc instanceof Doc) || this.targetDoc instanceof Promise ? null : this.mainItem;
-    }
-}
-
-Docs.Prototypes.TemplateMap.set(DocumentType.PRESELEMENT, {
-    layout: { view: PresElementBox, dataField: 'data' },
-    options: { acl: '', title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' },
-});
diff --git a/src/client/views/nodes/trails/PresSlideBox.scss b/src/client/views/nodes/trails/PresSlideBox.scss
new file mode 100644
index 000000000..9ac2b5a94
--- /dev/null
+++ b/src/client/views/nodes/trails/PresSlideBox.scss
@@ -0,0 +1,308 @@
+$light-blue: #aeddf8;
+$dark-blue: #5b9fdd;
+$light-background: #ececec;
+$slide-background: #d5dce2;
+$slide-active: #5b9fdd;
+
+.testingv2 {
+    background-color: red;
+}
+
+.presItem-container {
+    cursor: grab;
+    display: flex;
+    grid-template-columns: 20px auto;
+    font-family: Roboto;
+    letter-spacing: normal;
+    position: relative;
+    pointer-events: all;
+    width: 100%;
+    height: 100%;
+    font-weight: 400;
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    align-items: center;
+
+    //     .presItem-number {
+    //         margin-top: 3.5px;
+    //         font-size: 12px;
+    //         font-weight: 700;
+    //         text-align: center;
+    //         justify-self: center;
+    //         align-self: flex-start;
+    //         position: relative;
+    //         display: inline-block;
+    //         overflow: hidden;
+    //     }
+}
+
+.presItem-slide {
+    position: relative;
+    height: 100%;
+    width: 100%;
+    border-bottom: 0.5px solid grey;
+    display: flex;
+    align-items: center;
+
+    .presItem-number {
+        cursor: pointer;
+        &:hover {
+            background-color: $light-blue;
+        }
+    }
+    .presItem-name {
+        display: flex;
+        min-width: 20px;
+        z-index: 300;
+        top: 2px;
+        align-self: center;
+        font-size: 11px;
+        font-family: Roboto;
+        font-weight: 500;
+        position: relative;
+        padding-left: 10px;
+        padding-right: 10px;
+        letter-spacing: normal;
+        width: max-content;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        white-space: pre;
+    }
+
+    .presItem-docName {
+        min-width: 20px;
+        z-index: 300;
+        align-self: center;
+        font-size: 9px;
+        font-family: Roboto;
+        font-weight: 300;
+        position: relative;
+        padding-left: 10px;
+        padding-right: 10px;
+        letter-spacing: normal;
+        width: max-content;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        white-space: pre;
+        grid-row: 2;
+        grid-column: 1/6;
+    }
+
+    .presItem-time {
+        align-self: center;
+        position: relative;
+        padding-right: 10px;
+        top: 1px;
+        font-size: 10;
+        font-weight: 300;
+        font-family: Roboto;
+        z-index: 300;
+        letter-spacing: normal;
+    }
+
+    .presItem-embedded {
+        overflow: hidden;
+        grid-row: 3;
+        grid-column: 1/8;
+        position: relative;
+        display: inline-block;
+    }
+
+    .presItem-embeddedMask {
+        width: 100%;
+        height: 100%;
+        position: absolute;
+        border-radius: 3px;
+        top: 0;
+        left: 0;
+        z-index: 1;
+        overflow: hidden;
+    }
+
+    .presItem-slideButtons {
+        display: flex;
+        position: absolute;
+        width: max-content;
+        justify-self: right;
+        justify-content: flex-end;
+
+        .slideButton {
+            cursor: pointer;
+            position: relative;
+            border-radius: 100px;
+            z-index: 300;
+            width: 18px;
+            height: 18px;
+            display: flex;
+            font-size: 12px;
+            justify-self: center;
+            align-self: center;
+            background-color: rgba(0, 0, 0, 0.5);
+            color: white;
+            justify-content: center;
+            align-items: center;
+            transition: 0.2s;
+            margin-right: 3px;
+        }
+
+        .slideButton:hover {
+            background-color: rgba(0, 0, 0, 1);
+            transform: scale(1.2);
+        }
+    }
+}
+
+// .presItem-slide:hover {
+//     .presItem-slideButtons {
+//         display: flex;
+//         grid-column: 7;
+//         grid-row: 1/3;
+//         width: max-content;
+//         justify-self: right;
+//         justify-content: flex-end;
+
+//         .slideButton {
+//             cursor: pointer;
+//             position: relative;
+//             border-radius: 100px;
+//             z-index: 300;
+//             width: 18px;
+//             height: 18px;
+//             display: flex;
+//             font-size: 12px;
+//             justify-self: center;
+//             align-self: center;
+//             background-color: rgba(0, 0, 0, 0.5);
+//             color: white;
+//             justify-content: center;
+//             align-items: center;
+//             transition: 0.2s;
+//             margin-right: 3px;
+//         }
+
+//         .slideButton:hover {
+//             background-color: rgba(0, 0, 0, 1);
+//             transform: scale(1.2);
+//         }
+//     }
+// }
+
+.presItem-slide.active {
+    //box-shadow: 0 0 0px 2.5px $dark-blue;
+    border: $dark-blue solid 2.5px;
+}
+
+.presItem-slide.group {
+    border-radius: 5px;
+}
+
+.presItem-slide.activeGroup {
+    border-radius: 5px;
+    box-shadow: 0 0 0px 2.5px $dark-blue;
+}
+
+.presItem-groupSlideContainer {
+    position: absolute;
+    /* grid-row: 3; */
+    /* grid-column: 1/8; */
+    top: 28;
+    display: block;
+    background: #92adb9;
+    width: 100%;
+    height: 10px;
+    border-radius: 0px 0px 5px 5px;
+    height: calc(100% - 28px);
+    display: grid;
+    grid-template-rows: auto auto auto;
+    grid-template-columns: 100%;
+    justify-items: left;
+    align-items: center;
+}
+
+.presItem-groupSlide {
+    position: relative;
+    background-color: #d5dce2;
+    border-radius: 5px;
+    height: calc(100% - 7px);
+    width: calc(100% - 20px);
+    left: 15px;
+    /* height: 20px; */
+    /* width: calc(100% - 19px); */
+    display: flex;
+    grid-template-rows: 16px 10px auto;
+    grid-template-columns: max-content max-content max-content max-content auto;
+}
+
+.presItem-multiDrag {
+    font-family: Roboto;
+    font-weight: 600;
+    color: white;
+    text-align: center;
+    justify-content: center;
+    align-content: center;
+    width: 100px;
+    height: 30px;
+    position: absolute;
+    background-color: $dark-blue;
+    z-index: 4000;
+    border-radius: 10px;
+    box-shadow: black 0.4vw 0.4vw 0.8vw;
+    line-height: 30px;
+}
+
+.presItem-miniSlide {
+    font-weight: 700;
+    font-size: 12;
+    grid-column: 1/8;
+    align-self: center;
+    justify-self: center;
+    background-color: #d5dce2;
+    width: 26px;
+    text-align: center;
+    height: 26px;
+    line-height: 28px;
+    border-radius: 100%;
+}
+
+.presItem-miniSlide.active {
+    box-shadow: 0 0 0px 1.5px $dark-blue;
+}
+
+.expandButton {
+    cursor: pointer;
+    position: absolute;
+    border-radius: 100px;
+    bottom: 0;
+    left: -18;
+    z-index: 300;
+    width: 15px;
+    height: 15px;
+    display: flex;
+    font-size: 12px;
+    justify-self: center;
+    align-self: center;
+    background-color: #92adb9;
+    color: white;
+    justify-content: center;
+    align-items: center;
+    transition: 0.2s;
+    margin-right: 3px;
+}
+
+.expandButton:hover {
+    background-color: rgba(0, 0, 0, 1);
+    transform: scale(1.2);
+}
+
+.presItem-groupNum {
+    color: #d5dce2;
+    position: absolute;
+    left: -15px;
+    top: 1;
+    font-weight: 600;
+    font-size: 12;
+}
diff --git a/src/client/views/nodes/trails/PresSlideBox.tsx b/src/client/views/nodes/trails/PresSlideBox.tsx
new file mode 100644
index 000000000..3dbb3da88
--- /dev/null
+++ b/src/client/views/nodes/trails/PresSlideBox.tsx
@@ -0,0 +1,628 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@mui/material';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
+import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { List } from '../../../../fields/List';
+import { BoolCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { emptyFunction } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
+import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
+import { DragManager } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { Transform } from '../../../util/Transform';
+import { undoable, undoBatch } from '../../../util/UndoManager';
+import { TreeView } from '../../collections/TreeView';
+import { ViewBoxBaseComponent } from '../../DocComponent';
+import { EditableView } from '../../EditableView';
+import { Colors } from '../../global/globalEnums';
+import { PinDocView } from '../../PinFuncs';
+import { StyleProp } from '../../StyleProp';
+import { returnEmptyDocViewList } from '../../StyleProvider';
+import { DocumentView } from '../DocumentView';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { PresBox } from './PresBox';
+import './PresSlideBox.scss';
+import { PresMovement } from './PresEnums';
+/**
+ * This class models the view a document added to presentation will have in the presentation.
+ * It involves some functionality for its buttons and options.
+ */
+@observer
+export class PresSlideBox extends ViewBoxBaseComponent<FieldViewProps>() {
+    public static LayoutString(fieldKey: string) {
+        return FieldView.LayoutString(PresSlideBox, fieldKey);
+    }
+    private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
+    private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
+    private _titleRef: React.RefObject<EditableView> = React.createRef();
+    private _heightDisposer: IReactionDisposer | undefined;
+    readonly expandViewHeight = 100;
+    readonly collapsedHeight = 35;
+
+    constructor(props: FieldViewProps) {
+        super(props);
+        makeObservable(this);
+    }
+
+    @observable _dragging = false;
+
+    // the presentation view that renders this slide
+    @computed get presBoxView() {
+        return this.DocumentView?.()
+            .containerViewPath?.()
+            .slice()
+            .reverse()
+            .find(dv => dv?.ComponentView instanceof PresBox)?.ComponentView as Opt<PresBox>;
+    }
+
+    // the presentation view document that renders this slide
+    @computed get presBox() {
+        return this.presBoxView?.Document;
+    }
+
+    // Since this node is being rendered with a template, this method retrieves
+    // the actual slide being rendered from the auto-generated rendering template
+    @computed get slideDoc() {
+        return this.rootDoc;
+    }
+
+    // this is the document in the workspaces that is targeted by the slide
+    @computed get targetDoc() {
+        return DocCast(this.slideDoc.presentation_targetDoc, this.slideDoc)!;
+    }
+
+    // computes index of this presentation slide in the presBox list
+    @computed get indexInPres() {
+        return this.presBoxView?.SlideIndex(this.slideDoc) ?? 0;
+    }
+
+    @computed get selectedArray() {
+        return this.presBoxView?.selectedArray;
+    }
+
+    @computed get videoRecordingIsInOverlay() {
+        return Doc.MyOverlayDocs.some(doc => doc.slides === this.slideDoc);
+    }
+
+    componentDidMount() {
+        this.layoutDoc.layout_hideLinkButton = true;
+        this._heightDisposer = reaction(
+            () => ({ expand: this.slideDoc.presentation_expandInlineButton, height: this.collapsedHeight }),
+            ({ expand, height }) => {
+                this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0);
+            },
+            { fireImmediately: true }
+        );
+    }
+    componentWillUnmount() {
+        this._heightDisposer?.();
+    }
+
+    presExpandDocumentClick = () => {
+        this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton;
+    };
+    embedHeight = () => this.collapsedHeight + this.expandViewHeight;
+    embedWidth = () => this._props.PanelWidth() / 2;
+    // prettier-ignore
+    styleProvider = ( doc: Doc | undefined, props: Opt<FieldViewProps>, property: string ) => 
+        (property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property));
+    /**
+     * The function that is responsible for rendering a preview or not for this
+     * presentation element.
+     */
+    @computed get renderEmbeddedInline() {
+        return !this.slideDoc.presentation_expandInlineButton || !this.targetDoc ? null : (
+            <div className="presItem-embedded" style={{ height: this.embedHeight(), width: '50%' }}>
+                <DocumentView
+                    Document={PresBox.targetRenderedDoc(this.slideDoc) ?? this.slideDoc}
+                    PanelWidth={this.embedWidth}
+                    PanelHeight={this.embedHeight}
+                    isContentActive={this._props.isContentActive}
+                    styleProvider={this.styleProvider}
+                    hideLinkButton
+                    ScreenToLocalTransform={Transform.Identity}
+                    renderDepth={this._props.renderDepth + 1}
+                    containerViewPath={returnEmptyDocViewList}
+                    childFilters={this._props.childFilters}
+                    childFiltersByRanges={this._props.childFiltersByRanges}
+                    searchFilterDocs={this._props.searchFilterDocs}
+                    addDocument={returnFalse}
+                    removeDocument={returnFalse}
+                    fitContentsToBox={returnTrue}
+                    moveDocument={this._props.moveDocument!}
+                    focus={emptyFunction}
+                    whenChildContentsActiveChanged={returnFalse}
+                    addDocTab={returnFalse}
+                    pinToPres={returnFalse}
+                />
+            </div>
+        );
+    }
+
+    @computed get renderGroupSlides() {
+        const childDocs = DocListCast(this.targetDoc.data);
+        const groupSlides = childDocs.map((doc: Doc, ind: number) => (
+            <div
+                key={doc[Id]}
+                className="presItem-groupSlide"
+                onClick={e => {
+                    e.stopPropagation();
+                    e.preventDefault();
+                    this.presBoxView?.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+                    this.presExpandDocumentClick();
+                }}>
+                <div className="presItem-groupNum">{`${ind + 1}.`}</div>
+                <div className="presItem-name">
+                    <EditableView
+                        ref={this._titleRef}
+                        editing={undefined}
+                        contents={StrCast(doc.title)}
+                        overflow="ellipsis"
+                        GetValue={() => StrCast(doc.title)}
+                        SetValue={(value: string) => {
+                            doc.title = !value.trim().length ? '-untitled-' : value;
+                            return true;
+                        }}
+                    />
+                </div>
+            </div>
+        ));
+        return groupSlides;
+    }
+
+    @computed get transition() {
+        let transitionInS: number;
+        if (this.slideDoc.presentation_transition) transitionInS = NumCast(this.slideDoc.presentation_transition) / 1000;
+        else transitionInS = 0.5;
+        return this.slideDoc.presentation_movement === PresMovement.Jump || this.slideDoc.presentation_movement === PresMovement.None ? null : 'M: ' + transitionInS + 's';
+    }
+
+    @action
+    headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+        const element = e.target as HTMLDivElement;
+        e.stopPropagation();
+        e.preventDefault();
+        if (element && !(e.ctrlKey || e.metaKey || e.button === 2)) {
+            this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false);
+            setupMoveUpEvents(this, e, this.startDrag, emptyFunction, clickEv => {
+                clickEv.stopPropagation();
+                clickEv.preventDefault();
+                this.presBoxView?.modifierSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, clickEv.shiftKey || clickEv.ctrlKey || clickEv.metaKey, clickEv.ctrlKey || clickEv.metaKey, clickEv.shiftKey);
+                this.presBoxView?.activeItem && this.showRecording(this.presBoxView?.activeItem);
+            });
+        }
+    };
+
+    /**
+     * Function to drag and drop the pres element to a diferent location
+     */
+    startDrag = (e: PointerEvent) => {
+        this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false);
+        const miniView: boolean = this.toolbarWidth <= 100;
+        const activeItem = this.slideDoc;
+        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.DocumentView?.()?._props.treeViewDoc;
+        dragData.moveDocument = this._props.moveDocument;
+        const dragItem: HTMLElement[] = [];
+        const classesToRestore = new Map<HTMLElement, string>();
+        if (dragArray.length === 1) {
+            const doc = this._itemRef.current || dragArray[0];
+            if (doc) {
+                classesToRestore.set(doc, doc.className);
+                doc.className = miniView ? 'presItem-miniSlide' : 'presItem-slide';
+                dragItem.push(doc);
+            }
+        } else if (dragArray.length >= 1) {
+            const doc = document.createElement('div');
+            doc.className = 'presItem-multiDrag';
+            doc.innerText = 'Move ' + (this.selectedArray?.size ?? 0) + ' slides';
+            doc.style.position = 'absolute';
+            doc.style.top = e.clientY + 'px';
+            doc.style.left = e.clientX - 50 + 'px';
+            dragItem.push(doc);
+        }
+
+        if (activeItem) {
+            runInAction(() => {
+                this._dragging = true;
+            });
+            DragManager.StartDocumentDrag(
+                dragItem.map(ele => ele),
+                dragData,
+                e.clientX,
+                e.clientY,
+                undefined,
+                action(() => {
+                    Array.from(classesToRestore).forEach(pair => (pair[0].className = pair[1]));
+                    this._dragging = false;
+                })
+            );
+            return true;
+        }
+        return false;
+    };
+
+    onPointerOver = () => {
+        document.removeEventListener('pointermove', this.onPointerMove);
+        document.addEventListener('pointermove', this.onPointerMove);
+    };
+
+    onPointerMove = (e: PointerEvent) => {
+        const slide = this._itemRef.current;
+        const dragIsPresItem = DragManager.docsBeingDragged.some(d => d.presentation_targetDoc);
+        if (slide && dragIsPresItem) {
+            const rect = slide.getBoundingClientRect();
+            const y = e.clientY - rect.top; // y position within the element.
+            const height = slide.clientHeight;
+            const halfLine = height / 2;
+            if (y <= halfLine) {
+                slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`;
+                slide.style.borderBottom = '0px';
+            } else if (y > halfLine) {
+                slide.style.borderTop = '0px';
+                slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
+            }
+        }
+        document.removeEventListener('pointermove', this.onPointerMove);
+    };
+
+    onPointerLeave = () => {
+        const slide = this._itemRef.current;
+        if (slide) {
+            slide.style.borderTop = '0px';
+            slide.style.borderBottom = '0px';
+        }
+        document.removeEventListener('pointermove', this.onPointerMove);
+    };
+
+    @action
+    toggleProperties = () => {
+        if (SnappingManager.PropertiesWidth < 5) {
+            SnappingManager.SetPropertiesWidth(250);
+        }
+    };
+
+    removePresentationItem = undoable(
+        action((e: React.MouseEvent) => {
+            e.stopPropagation();
+            if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
+                this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1;
+            }
+            this._props.removeDocument?.(this.slideDoc);
+            this.presBoxView?.removeFromSelectedArray(this.slideDoc);
+            this.removeAllRecordingInOverlay();
+        }),
+        'Remove doc from pres trail'
+    );
+
+    // set title of the individual pres slide
+    onSetValue = undoable(
+        action((value: string) => {
+            this.slideDoc.title = !value.trim().length ? '-untitled-' : value;
+            return true;
+        }),
+        'set title of pres element'
+    );
+
+    /**
+     * Method called for updating the view of the currently selected document
+     *
+     * @param targetDoc
+     * @param activeItem
+     */
+    @undoBatch
+    updateCapturedContainerLayout = (presTargetDoc: Doc, activeItem: Doc) => {
+        const targetDoc = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
+        activeItem.config_x = NumCast(targetDoc.x);
+        activeItem.config_y = NumCast(targetDoc.y);
+        activeItem.config_rotation = NumCast(targetDoc.rotation);
+        activeItem.config_width = NumCast(targetDoc.width);
+        activeItem.config_height = NumCast(targetDoc.height);
+        activeItem.config_pinLayout = !activeItem.config_pinLayout;
+        // activeItem.config_pinLayout = true;
+    };
+
+    /**
+     * Method called for updating the view of the currently selected document
+     *
+     * @param presTargetDoc
+     * @param activeItem
+     */
+    updateCapturedViewContents = undoable(
+        action((presTargetDoc: Doc, activeItem: Doc) => {
+            const target = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
+            PinDocView(activeItem, { pinData: PresBox.pinDataTypes(target) }, target);
+        }),
+        'updated captured view contents'
+    );
+
+    // a previously recorded video will have timecode defined
+    static videoIsRecorded = (activeItem: Opt<Doc>) => 'layout_currentTimecode' in (DocCast(activeItem?.recording) ?? {});
+
+    removeAllRecordingInOverlay = () => Doc.MyOverlayDocs.filter(doc => doc.slides === this.slideDoc).forEach(Doc.RemFromMyOverlay);
+
+    /// remove all videos that have been recorded from overlay (leave videso that are being recorded to avoid losing data)
+    static removeEveryExistingRecordingInOverlay = () => {
+        Doc.MyOverlayDocs.filter(doc => doc.slides !== null && PresSlideBox.videoIsRecorded(DocCast(doc.slides))) //
+            .forEach(Doc.RemFromMyOverlay);
+    };
+
+    hideRecording = undoable(
+        action((e: React.MouseEvent) => {
+            e.stopPropagation();
+            this.removeAllRecordingInOverlay();
+        }),
+        'hide video recording'
+    );
+
+    showRecording = undoable(
+        action((activeItem: Doc, iconClick: boolean = false) => {
+            // remove the overlays on switch *IF* not opened from the specific icon
+            if (!iconClick) PresSlideBox.removeEveryExistingRecordingInOverlay();
+
+            DocCast(activeItem.recording) && Doc.AddToMyOverlay(DocCast(activeItem.recording)!);
+        }),
+        'show video recording'
+    );
+
+    startRecording = undoable(
+        action((e: React.MouseEvent, activeItem: Doc) => {
+            e.stopPropagation();
+            if (PresSlideBox.videoIsRecorded(activeItem)) {
+                // if we already have an existing recording
+                this.showRecording(activeItem, true);
+                //   // if we already have an existing recording
+                //   Doc.AddToMyOverlay(Cast(activeItem.recording, Doc, null));
+            } else {
+                // we dont have any recording
+                // Remove every recording that already exists in overlay view
+                // this is a design decision to clear to focus in on the recoding mode
+                PresSlideBox.removeEveryExistingRecordingInOverlay();
+
+                // create and add a recording to the slide
+                // make recording box appear in the bottom right corner of the screen
+                Doc.AddToMyOverlay(
+                    (activeItem.recording = Docs.Create.WebCamDocument('', {
+                        _width: 384,
+                        _height: 216,
+                        overlayX: window.innerWidth - 384 - 20,
+                        overlayY: window.innerHeight - 216 - 20,
+                        layout_hideDocumentButtonBar: true,
+                        layout_hideDecorationTitle: true,
+                        layout_hideOpenButton: true,
+                        cloneFieldFilter: new List<string>(['isSystem']),
+                        slides: activeItem, // attach the slide to the recording
+                    }))
+                );
+            }
+        }),
+        'start video recording'
+    );
+
+    @undoBatch
+    lfg = (e: React.MouseEvent) => {
+        e.stopPropagation();
+        // TODO: fix this bug
+        // const { toggleChildrenRun } = this.slideDoc;
+        TreeView.ToggleChildrenRun.get(this.slideDoc)?.();
+
+        // call this.slideDoc.recurChildren() to get all the children
+        // if (iconClick) PresSlideBox.showVideo = false;
+    };
+
+    @computed
+    get toolbarWidth(): number {
+        const presBoxDocView = DocumentView.getDocumentView(this.presBox);
+        const width = NumCast(this.presBox?._width);
+        return presBoxDocView ? presBoxDocView._props.PanelWidth() : width || 300;
+    }
+
+    @computed get presButtons() {
+        const { presBox, targetDoc, slideDoc: activeItem } = this;
+        const presBoxColor = StrCast(presBox?._backgroundColor);
+        const presColorBool = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
+        const hasChildren = BoolCast(this.slideDoc?.hasChildren);
+
+        const items: JSX.Element[] = [];
+
+        items.push(
+            <Tooltip key="slide" title={<div className="dash-tooltip">Update captured doc layout</div>}>
+                <div
+                    className="slideButton"
+                    onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedContainerLayout(targetDoc, activeItem), true)}
+                    style={{ opacity: activeItem.config_pinLayout ? 1 : 0.5, fontWeight: 700, display: 'flex' }}>
+                    L
+                </div>
+            </Tooltip>
+        );
+        items.push(
+            <Tooltip key="flex" title={<div className="dash-tooltip">Update captured doc content</div>}>
+                <div
+                    className="slideButton"
+                    onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedViewContents(targetDoc, activeItem))}
+                    style={{ opacity: activeItem.config_pinData || activeItem.config_pinView ? 1 : 0.5, fontWeight: 700, display: 'flex' }}>
+                    C
+                </div>
+            </Tooltip>
+        );
+        items.push(
+            <Tooltip key="slash" title={<div className="dash-tooltip">{this.videoRecordingIsInOverlay ? 'Hide Recording' : `${PresSlideBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
+                <div className="slideButton" onClick={e => (this.videoRecordingIsInOverlay ? this.hideRecording(e) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
+                    <FontAwesomeIcon icon={`video${this.videoRecordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} />
+                </div>
+            </Tooltip>
+        );
+        if (this.indexInPres !== 0) {
+            items.push(
+                <Tooltip
+                    key="arrow"
+                    title={
+                        <div className="dash-tooltip">
+                            {!activeItem.presentation_groupWithUp
+                                ? 'Not grouped with previous slide (click to group)'
+                                : activeItem.presentation_groupWithUp === 1
+                                  ? 'Run simultaneously with previous slide (click again to run after)'
+                                  : 'Run after previous slide (click to ungroup from previous)'}
+                        </div>
+                    }>
+                    <div
+                        className="slideButton"
+                        onClick={() => {
+                            activeItem.presentation_groupWithUp = (NumCast(activeItem.presentation_groupWithUp) + 1) % 3;
+                        }}
+                        style={{
+                            zIndex: 1000 - this.indexInPres,
+                            fontWeight: 700,
+                            backgroundColor: activeItem.presentation_groupWithUp ? (presColorBool ? presBoxColor : Colors.MEDIUM_BLUE) : undefined,
+                            outline: NumCast(activeItem.presentation_groupWithUp) > 1 ? 'solid black 1px' : undefined,
+                            height: activeItem.presentation_groupWithUp ? 53 : 18,
+                            transform: activeItem.presentation_groupWithUp ? 'translate(0, -17px)' : undefined,
+                        }}>
+                        <div style={{ transform: activeItem.presentation_groupWithUp ? 'rotate(180deg) translate(0, -17.5px)' : 'rotate(0deg)' }}>
+                            <FontAwesomeIcon icon="arrow-up" onPointerDown={e => e.stopPropagation()} />
+                        </div>
+                    </div>
+                </Tooltip>
+            );
+        }
+        items.push(
+            <Tooltip key="eye" title={<div className="dash-tooltip">{this.slideDoc.presentation_expandInlineButton ? 'Minimize' : 'Expand'}</div>}>
+                <div
+                    className="slideButton"
+                    onClick={e => {
+                        e.stopPropagation();
+                        this.presExpandDocumentClick();
+                    }}>
+                    <FontAwesomeIcon icon={this.slideDoc.presentation_expandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
+                </div>
+            </Tooltip>
+        );
+        if (!Doc.noviceMode && hasChildren) {
+            // TODO: replace with if treeveiw, has childrenDocs
+            items.push(
+                <Tooltip key="children" title={<div className="dash-tooltip">Run child processes (tree only)</div>}>
+                    <div
+                        className="slideButton"
+                        onClick={e => {
+                            e.stopPropagation();
+                            this.lfg(e);
+                        }}
+                        style={{ fontWeight: 700 }}>
+                        <FontAwesomeIcon icon="circle-play" onPointerDown={e => e.stopPropagation()} />
+                    </div>
+                </Tooltip>
+            );
+        }
+        items.push(
+            <Tooltip key="trash" title={<div className="dash-tooltip">Remove from presentation</div>}>
+                <div className="slideButton" onClick={this.removePresentationItem}>
+                    <FontAwesomeIcon icon="trash" onPointerDown={e => e.stopPropagation()} />
+                </div>
+            </Tooltip>
+        );
+        items.push(
+            <Tooltip key="customize-slide" title={<div className="dash-tooltip">Customize Slide</div>}>
+                <div
+                    className={'slideButton'}
+                    onClick={() => {
+                        this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false);
+                        PresBox.Instance.navigateToActiveItem();
+                        PresBox.Instance.openProperties();
+                        PresBox.Instance.slideToModify = this.Document;
+                    }}>
+                    <FontAwesomeIcon icon={'edit'} onPointerDown={e => e.stopPropagation()} />
+                </div>
+            </Tooltip>
+        );
+        return items;
+    }
+
+    @computed get mainItem() {
+        const { presBox, slideDoc: activeItem } = this;
+        const isSelected: boolean = !!this.selectedArray?.has(activeItem);
+        const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres;
+        const miniView: boolean = this.toolbarWidth <= 110;
+        const presBoxColor: string = StrCast(presBox?._backgroundColor);
+        const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
+
+        return (
+            <div
+                className="presItem-container"
+                key={activeItem[Id] + this.indexInPres}
+                ref={this._itemRef}
+                style={{
+                    backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
+                    opacity: this._dragging ? 0.3 : 1,
+                    paddingLeft: NumCast(this.layoutDoc._xPadding, this._props.xPadding),
+                    paddingRight: NumCast(this.layoutDoc._xPadding, this._props.xPadding),
+                    paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
+                    paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
+                }}
+                onDoubleClick={action(() => {
+                    this.toggleProperties();
+                    this.presBoxView?.regularSelect(activeItem, this._itemRef.current!, this._dragRef.current!, false);
+                })}
+                onPointerOver={this.onPointerOver}
+                onPointerLeave={this.onPointerLeave}
+                onPointerDown={this.headerDown}>
+                {miniView ? (
+                    <div className={`presItem-miniSlide ${isSelected ? 'active' : ''}`} ref={this._dragRef}>
+                        {`${this.indexInPres + 1}.`}
+                    </div>
+                ) : (
+                    <div
+                        ref={this._dragRef}
+                        className={`presItem-slide ${isCurrent ? 'active' : ''}${activeItem.runProcess ? ' testingv2' : ''}`}
+                        style={{
+                            display: 'infline-block',
+                            backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string,
+                            // layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
+                            border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined,
+                        }}>
+                        <div
+                            className="presItem-name"
+                            style={{
+                                display: 'inline-flex',
+                                pointerEvents: isSelected ? undefined : 'none',
+                                width: `calc(100% ${activeItem.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
+                                cursor: isSelected ? 'text' : 'grab',
+                            }}>
+                            <div
+                                className="presItem-number"
+                                title="select without navigation"
+                                onPointerDown={e => {
+                                    e.stopPropagation();
+                                    if (this._itemRef.current && this._dragRef.current) {
+                                        this.presBoxView?.modifierSelect(activeItem, this._itemRef.current, this._dragRef.current, true, false, false);
+                                    }
+                                }}
+                                onClick={e => e.stopPropagation()}>{`${this.indexInPres + 1}.  `}</div>
+                            <EditableView ref={this._titleRef} oneLine editing={!isSelected ? false : undefined} contents={StrCast(activeItem.title)} overflow="ellipsis" GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} />
+                        </div>
+                        {/* <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip> */}
+                        {/* <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip> */}
+                        <div className="presItem-slideButtons" style={{ position: 'absolute', right: 0 }}>
+                            {...this.presButtons}
+                        </div>
+                        {this.renderEmbeddedInline}
+                    </div>
+                )}
+            </div>
+        );
+    }
+
+    render() {
+        return !(this.slideDoc instanceof Doc) || this.targetDoc instanceof Promise ? null : this.mainItem;
+    }
+}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.PRESSLIDE, {
+    layout: { view: PresSlideBox, dataField: 'data' },
+    options: { acl: '', title: 'presSlide', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true },
+});
diff --git a/src/client/views/nodes/trails/index.ts b/src/client/views/nodes/trails/index.ts
index 7b18974df..a5bc55221 100644
--- a/src/client/views/nodes/trails/index.ts
+++ b/src/client/views/nodes/trails/index.ts
@@ -1,3 +1,3 @@
 export * from './PresBox';
-export * from './PresElementBox';
+export * from './PresSlideBox';
 export * from './PresEnums';
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index e3ee51424..c45d8e052 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -210,7 +210,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
     generateFireflyImage = (imgDesc: string) => {
         const selView = DocumentView.Selected().lastElement();
         const selDoc = selView?.Document;
-        if (selDoc && (selView._props.renderDepth > 1 || selDoc[Doc.LayoutFieldKey(selDoc)] instanceof ImageField)) {
+        if (selDoc && (selView._props.renderDepth > 1 || selDoc[Doc.LayoutDataKey(selDoc)] instanceof ImageField)) {
             const oldPrompt = StrCast(selDoc.ai_firefly_prompt, StrCast(selDoc.title));
             const newPrompt = oldPrompt ? `${oldPrompt} ~~~ ${imgDesc}` : imgDesc;
             return DrawingFillHandler.drawingToImage(selDoc, 100, newPrompt, selDoc)
diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx
index 86e28a768..f60eb486e 100644
--- a/src/client/views/search/FaceRecognitionHandler.tsx
+++ b/src/client/views/search/FaceRecognitionHandler.tsx
@@ -60,7 +60,7 @@ export class FaceRecognitionHandler {
      * @param imgDoc image with faces
      * @returns faceDoc array
      */
-    public static ImageDocFaceAnnos = (imgDoc: Doc) => DocListCast(imgDoc[`${Doc.LayoutFieldKey(imgDoc)}_annotations`]).filter(doc => doc.face);
+    public static ImageDocFaceAnnos = (imgDoc: Doc) => DocListCast(imgDoc[`${Doc.LayoutDataKey(imgDoc)}_annotations`]).filter(doc => doc.face);
 
     /**
      * returns a list of all face collection Docs on the current dashboard
@@ -207,7 +207,7 @@ export class FaceRecognitionHandler {
         } else if (imgDoc.type === DocumentType.LOADING && !imgDoc.loadingError) {
             setTimeout(() => this.classifyFacesInImage(imgDoc), 1000);
         } else {
-            const imgUrl = ImageCast(imgDoc[Doc.LayoutFieldKey(imgDoc)]);
+            const imgUrl = ImageCast(imgDoc[Doc.LayoutDataKey(imgDoc)]);
             if (imgUrl && !DocListCast(Doc.MyFaceCollection.examinedFaceDocs).includes(imgDoc[DocData])) {
                 // only examine Docs that have an image and that haven't already been examined.
                 Doc.AddDocToList(Doc.MyFaceCollection, 'examinedFaceDocs', imgDoc[DocData]);
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 1f6e80bd1..ad0f56cb9 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -218,7 +218,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
                     .filter(d => d)
                     // eslint-disable-next-line no-loop-func
                     .map(async d => {
-                        const fieldKey = Doc.LayoutFieldKey(d);
+                        const fieldKey = Doc.LayoutDataKey(d);
                         const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView');
                         const data = d[annos ? fieldKey + '_annotations' : fieldKey];
                         const dataDocs = await DocListCastAsync(data);
diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx
index 2260d1f73..0e234e966 100644
--- a/src/client/views/smartdraw/StickerPalette.tsx
+++ b/src/client/views/smartdraw/StickerPalette.tsx
@@ -56,10 +56,10 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
         if (!doc.savedAsSticker) {
             const docView = DocumentView.getDocumentView(doc);
             await docView?.ComponentView?.updateIcon?.(true);
-            const { clone } = await Doc.MakeClone(doc);
+            const { clone } = Doc.MakeClone(doc);
             clone.title = doc.title;
-            const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutFieldKey(clone)]))?.url?.href;
-            Doc.AddDocToList(Doc.MyStickers, 'data', makeUserTemplateButtonOrImage(clone, image));
+            const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutDataKey(clone)]))?.url?.href;
+            Doc.MyStickers && Doc.AddDocToList(Doc.MyStickers, 'data', makeUserTemplateButtonOrImage(clone, image));
             doc.savedAsSticker = true;
         }
     };
@@ -327,9 +327,10 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
     );
     renderPaletteView = () => (
         <>
-            {this.renderDoc(Doc.MyStickers, (r: DocumentView) => {
-                this._docView = r;
-            })}
+            {Doc.MyStickers &&
+                this.renderDoc(Doc.MyStickers, (r: DocumentView) => {
+                    this._docView = r;
+                })}
             <Button text="Add" icon={<FontAwesomeIcon icon="square-plus" />} color={SettingsManager.userColor} onClick={() => this.setPaletteMode(StickerPaletteMode.create)} />
         </>
     );
diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx
index 8ac502348..37586fcc0 100644
--- a/src/debug/Repl.tsx
+++ b/src/debug/Repl.tsx
@@ -13,7 +13,7 @@ import { makeInterface } from '../fields/Schema';
 class Repl extends React.Component {
     @observable text: string = '';
 
-    @observable executedCommands: { command: string; result: any }[] = [];
+    @observable executedCommands: { command: string; result: unknown }[] = [];
 
     onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
         this.text = e.target.value;
@@ -30,7 +30,7 @@ class Repl extends React.Component {
             if (!script.compiled) {
                 this.executedCommands.push({ command: this.text, result: 'Compile Error' });
             } else {
-                const result = script.run({ makeInterface }, err => this.executedCommands.push({ command: this.text, result: err.message || err }));
+                const result = script.run({ makeInterface }, err => this.executedCommands.push({ command: this.text, result: err }));
                 result.success && this.executedCommands.push({ command: this.text, result: result.result });
             }
             this.text = '';
@@ -40,7 +40,7 @@ class Repl extends React.Component {
     @computed
     get commands() {
         return this.executedCommands.map(command => (
-            <div style={{ marginTop: '5px' }}>
+            <div key={command.command} style={{ marginTop: '5px' }}>
                 <p>{command.command}</p>
                 {/* <pre>{JSON.stringify(command.result, null, 2)}</pre> */}
                 <pre>{command.result instanceof RefField || command.result instanceof ObjectField ? 'object' : String(command.result)}</pre>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 2db8d3b85..abce7ed26 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -17,7 +17,7 @@ import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScr
 import { InkEraserTool, InkInkTool, InkTool } from './InkField';
 import { List } from './List';
 import { ObjectField, serverOpType } from './ObjectField';
-import { PrefetchProxy, ProxyField } from './Proxy';
+import { PrefetchProxy } from './Proxy';
 import { FieldId, RefField } from './RefField';
 import { RichTextField } from './RichTextField';
 import { listSpec } from './Schema';
@@ -47,7 +47,7 @@ export namespace Field {
      * @returns string representation of the field
      */
     export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean, schemaCell?: boolean): string {
-        const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+        const cfield = ComputedField.DisableCompute(() => FieldValue(doc[key]));
         const valFunc = (field: FieldType): string => {
             const res =
                 field instanceof ComputedField && showComputedValue
@@ -133,13 +133,13 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
     return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
 }
 export function NumListCast(field: FieldResult, defaultVal: number[] = []) {
-    return Cast(field, listSpec('number'), defaultVal);
+    return Cast(field, listSpec('number'), defaultVal)!;
 }
 export function StrListCast(field: FieldResult, defaultVal: string[] = []) {
-    return Cast(field, listSpec('string'), defaultVal);
+    return Cast(field, listSpec('string'), defaultVal)!;
 }
 export function DocListCast(field: FieldResult, defaultVal: Doc[] = []) {
-    return Cast(field, listSpec(Doc), defaultVal).filter(d => d instanceof Doc) as Doc[];
+    return Cast(field, listSpec(Doc), defaultVal)!.filter(d => d instanceof Doc);
 }
 
 export enum aclLevel {
@@ -176,13 +176,27 @@ export function updateCachedAcls(doc: Doc) {
         }
 
         if (doc.proto instanceof Promise) {
-            doc.proto.then(proto => updateCachedAcls(DocCast(proto)));
+            doc.proto.then(proto => DocCast(proto) && updateCachedAcls(DocCast(proto)!));
             return doc.proto;
         }
     }
     return undefined;
 }
 
+/**
+ * computes a field name for where to store and expanded template Doc
+ * The format is layout_[ROOT_TEMPLATE_NMAE]_[ROOT_TEMPLATE_CHILD_NAME]_...
+ * @param template the template (either a root or a root child Doc)
+ * @param layoutFieldKey the fieldKey of the container of the template
+ * @returns field key to store expanded template Doc
+ */
+export function expandedFieldName(template: Doc, layoutFieldKey?: string) {
+    const layout_key = !layoutFieldKey?.endsWith(']') 
+            ? 'layout'          // layout_SOMETHING = SOMETHING => layout_[SOMETHING] = [SOMETHING]
+            : layoutFieldKey; // prettier-ignore
+    const tempTitle = '[' + StrCast(template.title).replace(/^\[(.*)\]$/, '$1') + ']';
+    return `${layout_key}_${tempTitle}`; // prettier-ignore
+}
 @scriptingGlobal
 @Deserializable('Doc', (obj: unknown) => updateCachedAcls(obj as Doc), ['id'])
 export class Doc extends RefField {
@@ -266,22 +280,28 @@ export class Doc extends RefField {
     public static set ActiveDashboard(val: Opt<Doc>) {  Doc.UserDoc().activeDashboard = val; } // prettier-ignore
     public static get MyFilterHotKeys()       { return DocListCast(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter)?.data).filter(key => key.toolType !== "-opts-"); } // prettier-ignore
     public static RemFromFilterHotKeys(doc: Doc) {
-        return Doc.RemoveDocFromList(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter), 'data', doc);
+        return (filters => filters && Doc.RemoveDocFromList(filters, 'data', doc))(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter));
     }
     public static AddToFilterHotKeys(doc: Doc) {
-        return Doc.AddDocToList(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter), 'data', doc);
+        return (btns => btns && Doc.AddDocToList(btns, 'data', doc))(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter));
     }
     public static IsInMyOverlay(doc: Doc)     { return Doc.MyOverlayDocs.includes(doc); } // prettier-ignore
-    public static AddToMyOverlay(doc: Doc)    { return Doc.ActiveDashboard ? Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
-    public static RemFromMyOverlay(doc: Doc)  { return Doc.ActiveDashboard ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myOverlayDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
-    public static AddToMyPublished(doc: Doc)  {
+    public static AddToMyOverlay(doc: Doc) {
+        return Doc.ActiveDashboard && Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc);
+    } //  : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+    public static RemFromMyOverlay(doc: Doc) {
+        return Doc.ActiveDashboard && Doc.RemoveDocFromList(Doc.ActiveDashboard, 'myOverlayDocs', doc);
+    } //  : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+    public static AddToMyPublished(doc: Doc) {
         doc.$title_custom = true;
-        doc.$layout_showTitle = 'title'; 
-        Doc.ActiveDashboard ? Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
-    public static RemFromMyPublished(doc: Doc){ 
+        doc.$layout_showTitle = 'title';
+        Doc.ActiveDashboard && Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc);
+    } //  : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+    public static RemFromMyPublished(doc: Doc) {
         doc.$title_custom = false;
         doc.$layout_showTitle = undefined;
-        Doc.ActiveDashboard ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myPublishedDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+        Doc.ActiveDashboard && Doc.RemoveDocFromList(Doc.ActiveDashboard, 'myPublishedDocs', doc);
+    } // : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
     public static IsComicStyle(doc?: Doc)     { return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic' ; } // prettier-ignore
 
     constructor(id?: FieldId, forceSave?: boolean) {
@@ -407,20 +427,17 @@ export class Doc extends RefField {
     }
     @computed get __DATA__(): Doc {
         const self = this[SelfProxy];
-        return self.rootDocument && !self.isTemplateForField ? self : Doc.GetProto(Cast(self[DocLayout].rootDocument, Doc, null) || self);
+        return self.rootDocument && !self.isTemplateForField ? self : Doc.GetProto(DocCast(self[DocLayout].rootDocument, self)!);
     }
     @computed get __LAYOUT__(): Doc {
         const self = this[SelfProxy];
         const templateLayoutDoc = Cast(Doc.LayoutField(self), Doc, null);
         if (templateLayoutDoc) {
-            let renderFieldKey: string = '';
             const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layout_fieldKey, 'layout')];
-            if (typeof layoutField === 'string') {
-                [renderFieldKey] = layoutField.split("fieldKey={'")[1].split("'"); // layoutField.split("'")[1];
-            } else {
-                return Cast(layoutField, Doc, null);
+            if (typeof layoutField !== 'string') {
+                return DocCast(layoutField, self)!;
             }
-            return Cast(self['layout_' + templateLayoutDoc.title + '(' + renderFieldKey + ')'], Doc, null) || templateLayoutDoc;
+            return DocCast(self[expandedFieldName(templateLayoutDoc)], templateLayoutDoc)!;
         }
         return self;
     }
@@ -597,7 +614,7 @@ export namespace Doc {
     // return the doc's proto, but rather recursively searches through the proto inheritance chain
     // and returns the document who's proto is undefined or whose proto is marked as a data doc ('isDataDoc').
     export function GetProto(doc: Doc): Doc {
-        const proto = doc && (Doc.GetT(doc, 'isDataDoc', 'boolean', true) ? doc : DocCast(doc.proto, doc));
+        const proto = doc && (Doc.GetT(doc, 'isDataDoc', 'boolean', true) ? doc : DocCast(doc.proto, doc)!);
         return proto === doc ? proto : Doc.GetProto(proto);
     }
     export function GetDataDoc(doc: Doc): Doc {
@@ -630,7 +647,7 @@ export namespace Doc {
      * @returns true if successful, false otherwise.
      */
     export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, ignoreProto = false) {
-        const key = fieldKey || Doc.LayoutFieldKey(listDoc);
+        const key = fieldKey || Doc.LayoutDataKey(listDoc);
         const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc['$' + key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
         if (list) {
             const ind = list.indexOf(doc);
@@ -647,7 +664,7 @@ export namespace Doc {
      * @returns true if successful, false otherwise.
      */
     export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean, ignoreProto?: boolean) {
-        const key = fieldKey || Doc.LayoutFieldKey(listDoc);
+        const key = fieldKey || Doc.LayoutDataKey(listDoc);
         const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc['$' + key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
         if (list) {
             if (!allowDuplicates) {
@@ -686,6 +703,14 @@ export namespace Doc {
         return DocListCast(Doc.Get(doc[DocData], 'proto_embeddings', true));
     }
 
+    /**
+     * Makes an embedding of a Doc.  This Doc shares the data portion of the origiginal Doc.
+     * If the copied Doc has no prototype, then instead of copying the Doc, this just creates
+     * a new Doc that is a delegate of the original Doc.
+     * @param doc Doc to embed
+     * @param id id to use for embedded Doc
+     * @returns a new Doc that is an embedding of the original Doc
+     */
     export function MakeEmbedding(doc: Doc, id?: string) {
         const embedding = (!Doc.IsDataProto(doc) && doc.proto) || doc.type === DocumentType.CONFIG ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
         embedding.createdFrom = doc;
@@ -705,17 +730,9 @@ export namespace Doc {
 
     // this lists out all the tag ids that can be  in a RichTextField that might contain document ids.
     // if a document is cloned, we need to make sure to clone all of these referenced documents as well;
-    export const DocsInTextFieldIds = ['audioId', 'textId', 'anchorId', 'docId'];
-    export async function makeClone(
-        doc: Doc,
-        cloneMap: Map<string, Doc>,
-        linkMap: Map<string, Doc>,
-        rtfs: { copy: Doc; key: string; field: RichTextField }[],
-        exclusions: string[],
-        pruneDocs: Doc[],
-        cloneLinks: boolean,
-        cloneTemplates: boolean
-    ): Promise<Doc> {
+    const FindDocsInRTF = new RegExp(/(audioId|textId|anchorId|docId)"\s*:\s*"(.*?)"/g);
+
+    export function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<string, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], pruneDocs: Doc[], cloneLinks: boolean, cloneTemplates: boolean): Doc {
         if (Doc.IsBaseProto(doc) || ((Doc.isTemplateDoc(doc) || Doc.isTemplateForField(doc)) && !cloneTemplates)) {
             return doc;
         }
@@ -723,80 +740,59 @@ export namespace Doc {
         const copy = new Doc(undefined, true);
         cloneMap.set(doc[Id], copy);
         const filter = [...exclusions, ...StrListCast(doc.cloneFieldFilter)];
-        await Promise.all(
-            Object.keys(doc).map(async key => {
-                if (filter.includes(key)) return;
-                const assignKey = (val: Opt<FieldType>) => {
-                    copy[key] = val;
-                };
-                const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-                const field = ProxyField.WithoutProxy(() => doc[key]);
-                const copyObjectField = async (objField: ObjectField) => {
-                    const list = await Cast(doc[key], listSpec(Doc));
-                    const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
-                    if (docs !== undefined && docs.length) {
-                        const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates)));
-                        assignKey(new List<Doc>(clones));
-                    } else {
-                        assignKey(ObjectField.MakeCopy(objField));
-                        if (objField instanceof RichTextField) {
-                            if (DocsInTextFieldIds.some(id => objField.Data.includes(`"${id}":`))) {
-                                const docidsearch = new RegExp('(' + DocsInTextFieldIds.map(exp => '(' + exp + ')').join('|') + ')":"([a-z-A-Z0-9_]*)"', 'g');
-                                const rawdocids = objField.Data.match(docidsearch);
-                                const docids = rawdocids?.map((str: string) =>
-                                    DocsInTextFieldIds.reduce((output, exp) => output.replace(new RegExp(`${exp}":`, 'g'), ''), str)
-                                        .replace(/"/g, '')
-                                        .trim()
-                                );
-                                const results = docids && (await DocServer.GetRefFields(docids));
-                                const rdocs = results && Array.from(Object.keys(results)).map(rkey => DocCast(results.get(rkey)));
-                                rdocs?.map(d => d && Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
-                                rtfs.push({ copy, key, field: objField });
-                            }
-                        }
-                    }
-                };
-                const docAtKey = doc[key];
+        Object.keys(doc)
+            .filter(key => !filter.includes(key))
+            .map(key => {
+                const assignKey = (val: Opt<FieldType>) => (copy[key] = val);
+
                 if (key === 'author') {
-                    assignKey(ClientUtils.CurrentUserEmail());
-                } else if (docAtKey instanceof Doc) {
-                    if (pruneDocs.includes(docAtKey)) {
-                        // prune doc and do nothing
-                    } else if (
-                        !Doc.IsSystem(docAtKey) &&
-                        (key.startsWith('layout') ||
-                            ['embedContainer', 'annotationOn', 'proto'].includes(key) || //
-                            (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === ClientUtils.CurrentUserEmail()))
-                    ) {
-                        assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
-                    } else {
-                        assignKey(docAtKey);
+                    return assignKey(ClientUtils.CurrentUserEmail());
+                }
+                const cfield = ComputedField.DisableCompute(() => doc[key]);
+                if (cfield instanceof ComputedField) {
+                    return assignKey(cfield[Copy]());
+                }
+                const field = doc[key];
+                if (field instanceof Doc) {
+                    const doCopy = () => Doc.IsSystem(field) ||
+                    !( key.startsWith('layout') ||
+                        ['embedContainer', 'annotationOn', 'proto'].includes(key) || //
+                        (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === ClientUtils.CurrentUserEmail()) ); // prettier-ignore
+                    return !pruneDocs.includes(field) && 
+                    assignKey(doCopy()
+                        ? field //
+                        : Doc.makeClone(field, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates)); // prettier-ignore
+                }
+                if (field instanceof RichTextField) {
+                    rtfs.push({ copy, key, field });
+                    let docId: string | undefined;
+                    while ((docId = (FindDocsInRTF.exec(field.Data) ?? [undefined, undefined, undefined])[2])) {
+                        const docCopy = DocServer.GetCachedRefField(docId);
+                        docCopy && Doc.makeClone(docCopy, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates);
                     }
-                } else if (field instanceof RefField) {
-                    assignKey(field);
-                } else if (cfield instanceof ComputedField) {
-                    assignKey(cfield[Copy]());
-                } else if (field instanceof ObjectField) {
-                    await copyObjectField(field);
-                } else if (field instanceof Promise) {
-                    // eslint-disable-next-line no-debugger
-                    debugger; // This shouldn't happen...
-                } else {
-                    assignKey(field);
+                    return assignKey(ObjectField.MakeCopy(field));
                 }
-            })
-        );
-        Array.from(doc[DirectLinks]).forEach(async link => {
+                if (field instanceof ObjectField) {
+                    if (DocListCast(field).length) {
+                        return assignKey(new List<Doc>(DocListCast(field).map(d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates))));
+                    }
+                    return assignKey(ObjectField.MakeCopy(field)); // otherwise just copy the field
+                }
+                if (!(field instanceof Promise)) return assignKey(field);
+                // eslint-disable-next-line no-debugger
+                debugger; // This shouldn't happen...
+            });
+        Array.from(doc[DirectLinks]).forEach(link => {
             if (
                 cloneLinks ||
-                ((cloneMap.has(DocCast(link.link_anchor_1)?.[Id]) || cloneMap.has(DocCast(DocCast(link.link_anchor_1)?.annotationOn)?.[Id])) &&
-                    (cloneMap.has(DocCast(link.link_anchor_2)?.[Id]) || cloneMap.has(DocCast(DocCast(link.link_anchor_2)?.annotationOn)?.[Id])))
+                ((cloneMap.has(DocCast(link.link_anchor_1)?.[Id] ?? '') || cloneMap.has(DocCast(DocCast(link.link_anchor_1)?.annotationOn)?.[Id] ?? '')) &&
+                    (cloneMap.has(DocCast(link.link_anchor_2)?.[Id] ?? '') || cloneMap.has(DocCast(DocCast(link.link_anchor_2)?.annotationOn)?.[Id] ?? '')))
             ) {
-                linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
+                linkMap.set(link[Id], Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates));
             }
         });
         copy.cloneOf = doc;
-        const cfield = ComputedField.WithoutComputed(() => FieldValue(doc.title));
+        const cfield = ComputedField.DisableCompute(() => FieldValue(doc.title));
         if (Doc.Get(copy, 'title', true) && !(cfield instanceof ComputedField)) copy.title = '>:' + doc.title;
         cloneMap.set(doc[Id], copy);
 
@@ -823,25 +819,27 @@ export namespace Doc {
         return docs.map(doc => Doc.MakeClone(doc, cloneLinks, cloneTemplates, cloneMap));
     }
 
-    export async function MakeClone(doc: Doc, cloneLinks = true, cloneTemplates = true, cloneMap: Map<string, Doc> = new Map()) {
+    /**
+     * Copies a Doc and all of the Docs that it references.  This is a deep copy of the Doc.
+     * However, the additional flags allow you to control whether to copy links and templates.
+     * @param doc Doc to clone
+     * @param cloneLinks whether to clone links to this Doc
+     * @param cloneTemplates whether to clone the templates used by this Doc
+     * @param cloneMap a map from the Doc ids of the original Doc to the new Docs
+     * @returns a clone of the original Doc
+     */
+    export function MakeClone(doc: Doc, cloneLinks = true, cloneTemplates = true, cloneMap: Map<string, Doc> = new Map()) {
         const linkMap = new Map<string, Doc>();
         const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = [];
-        const clone = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.embedContainer ? [DocCast(doc.embedContainer)] : [], cloneLinks, cloneTemplates);
+        const clone = Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], DocCast(doc.embedContainer) ? [DocCast(doc.embedContainer)!] : [], cloneLinks, cloneTemplates);
         const repaired = new Set<Doc>();
         const linkedDocs = Array.from(linkMap.values());
         linkedDocs.forEach(link => Doc.AddLink?.(link, true));
         rtfMap.forEach(({ copy, key, field }) => {
-            const replacer = (match: string, attr: string, id: string /* , offset: any, string: any */) => {
-                const mapped = cloneMap.get(id);
-                return attr + '"' + (mapped ? mapped[Id] : id) + '"';
-            };
-            const replacer2 = (match: string, href: string, id: string /* , offset: any, string: any */) => {
-                const mapped = cloneMap.get(id);
-                return href + (mapped ? mapped[Id] : id);
-            };
+            const replacer = (match: string, attr: string, id: string) => attr + '":"' + (cloneMap.get(id)?.[Id] ?? id) + '"';
+            const replacer2 = (match: string, href: string, id: string) => href + (cloneMap.get(id)?.[Id] ?? id);
             const re = new RegExp(`(${Doc.localServerPath()})([^"]*)`, 'g');
-            const docidsearch = new RegExp('(' + DocsInTextFieldIds.map(exp => `"${exp}":`).join('|') + ')"([^"]+)"', 'g');
-            copy[key] = new RichTextField(field.Data.replace(docidsearch, replacer).replace(re, replacer2), field.Text);
+            copy[key] = new RichTextField(field.Data.replace(FindDocsInRTF, replacer).replace(re, replacer2), field.Text);
         });
         const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs];
         clonedDocs.forEach(cloneDoc => Doc.repairClone(cloneDoc, cloneMap, cloneTemplates, repaired));
@@ -849,23 +847,29 @@ export namespace Doc {
     }
 
     const _pendingMap = new Set<string>();
-    //
-    // Returns an expanded template layout for a target data document if there is a template relationship
-    // between the two. If so, the layoutDoc is expanded into a new document that inherits the properties
-    // of the original layout while allowing for individual layout properties to be overridden in the expanded layout.
-    export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc) {
+    /**
+     * Returns an expanded template layout for a target data document if there is a template relationship
+     * between the two. If so, the layoutDoc is expanded into a new document that inherits the properties
+     * of the original layout while allowing for individual layout properties to be overridden in the expanded layout
+     * @param templateLayoutDoc a rendering template Doc
+     * @param targetDoc the Doc that the render template will be applied to
+     * @param layoutFieldKey the accumulated layoutFieldKey for the container of this expanded template
+     * @returns a Doc to use to render the targetDoc in the style of the template layout
+     */
+    export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, layoutFieldKey?: string) {
         // nothing to do if the layout isn't a template or we don't have a target that's different than the template
         if (!targetDoc || templateLayoutDoc === targetDoc || (!Doc.isTemplateForField(templateLayoutDoc) && !Doc.isTemplateDoc(templateLayoutDoc))) {
             return templateLayoutDoc;
         }
 
-        const templateField = StrCast(templateLayoutDoc.isTemplateForField, Doc.LayoutFieldKey(templateLayoutDoc)); // the field that the template renders
+        const templateField = StrCast(templateLayoutDoc.isTemplateForField, Doc.LayoutDataKey(templateLayoutDoc)); // the field that the template renders
+
         // First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc
         // using the template layout doc's id as the field key.
         // If it doesn't find the expanded layout, then it makes a delegate of the template layout and
         // saves it on the data doc indexed by the template layout's id.
         //
-        const expandedLayoutFieldKey = 'layout_' + templateLayoutDoc.title + '(' + templateField + ')';
+        const expandedLayoutFieldKey = expandedFieldName(templateLayoutDoc, layoutFieldKey);
         let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey];
 
         if (templateLayoutDoc.rootDocument instanceof Promise) {
@@ -876,7 +880,7 @@ export namespace Doc {
                 expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
             } else {
                 // eslint-disable-next-line no-param-reassign
-                templateLayoutDoc.rootDocument && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
+                templateLayoutDoc.rootDocument && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)!); // if the template has already been applied (ie, a nested template), then use the template's prototype
                 if (!targetDoc[expandedLayoutFieldKey]) {
                     _pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
                     setTimeout(
@@ -885,6 +889,7 @@ export namespace Doc {
                             const dataDoc = Doc.GetProto(targetDoc);
                             newLayoutDoc.rootDocument = targetDoc;
                             newLayoutDoc.embedContainer = targetDoc;
+                            newLayoutDoc.cloneOnCopy = true;
                             newLayoutDoc.acl_Guest = SharingPermissions.Edit;
                             if (dataDoc[templateField] === undefined && (templateLayoutDoc[templateField] as List<Doc>)?.length) {
                                 dataDoc[templateField] = ObjectField.MakeCopy(templateLayoutDoc[templateField] as List<Doc>);
@@ -901,87 +906,78 @@ export namespace Doc {
         return expandedTemplateLayout instanceof Doc ? expandedTemplateLayout : undefined; // layout is undefined if the expandedTemplateLayout is pending.
     }
 
-    // if the childDoc is a template for a field, then this will return the expanded layout with its data doc.
-    // otherwise, it just returns the childDoc
-    export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt<Doc>, childDoc: Doc) {
+    /**
+     * Returns a layout and data Doc pair to use to render the specified childDoc of the container.
+     * if the childDoc is a template for a field, then this will return the expanded layout with its data doc.
+     * otherwise, only a layout is returned since that will contain the data as its prototype.
+     * @param containerDoc the template container (that may be nested within a template, the root of a template, or not a template)
+     * @param containerDataDoc the template container's data doc (if the container is nested within the template)
+     * @param childDoc the doc to render
+     * @param layoutFieldKey the accumulated layoutFieldKey for the container of the doc being rendered
+     * @returns a layout Doc to render and an optional data Doc if the layout is a template
+     */
+    export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt<Doc>, childDoc: Doc, layoutFieldKey?: string) {
         if (!childDoc || childDoc instanceof Promise || !Doc.GetProto(childDoc)) {
             console.log('Warning: GetLayoutDataDocPair childDoc not defined');
             return { layout: childDoc, data: childDoc };
         }
         const data = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.isTemplateDoc(childDoc) && !Doc.isTemplateForField(childDoc)) ? undefined : containerDataDoc;
         const templateRoot = DocCast(containerDoc?.rootDocument);
-        return { layout: Doc.expandTemplateLayout(childDoc, templateRoot), data };
+        return { layout: Doc.expandTemplateLayout(childDoc, templateRoot, layoutFieldKey), data };
     }
 
-    export function FindReferences(infield: Doc | List<Doc>, 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));
-            return;
+    /**
+     * Recursively travels through all the metadata reachable from the Doc to find all referenced Docs
+     * @param doc Doc to search
+     * @param references all Docs reachable from the Doc
+     * @param system whether to include system Docs
+     * @returns all Docs reachable from the Doc
+     */
+    export function FindReferences(doc: Doc | List<Doc> | undefined, references: Set<Doc>, system: boolean | undefined) {
+        if (!doc || doc instanceof Promise) {
+            return references;
+        }
+        if (!(doc instanceof Doc)) {
+            doc?.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
+            return references;
         }
-        const doc = infield as Doc;
         if (references.has(doc)) {
-            references.add(doc);
-            return;
+            return references;
+        }
+        if (system !== undefined && ((system && !Doc.IsSystem(doc)) || (!system && Doc.IsSystem(doc)))) {
+            return references;
         }
-        const excludeLists = [Doc.MyRecentlyClosed, Doc.MyHeaderBar, Doc.MyDashboards].includes(doc);
-        if (system !== undefined && ((system && !Doc.IsSystem(doc)) || (!system && Doc.IsSystem(doc)))) return;
+
         references.add(doc);
-        Object.keys(doc).forEach(key => {
-            if (key === 'proto') {
-                if (doc.proto instanceof Doc) {
-                    Doc.FindReferences(doc.proto, references, system);
+        Object.keys(doc)
+            .filter(key => key !== 'author' && !(ComputedField.DisableCompute(() => FieldValue(doc[key])) instanceof ComputedField))
+            .map(key => doc[key])
+            .forEach(field => {
+                if (field instanceof List) {
+                    return ![Doc.MyRecentlyClosed, Doc.MyHeaderBar, Doc.MyDashboards].includes(doc) && Doc.FindReferences(field, references, system);
                 }
-            } else {
-                const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-                const field = key === 'author' ? ClientUtils.CurrentUserEmail() : ProxyField.WithoutProxy(() => doc[key]);
-                if (field instanceof RefField) {
-                    if (field instanceof Doc) {
-                        if (key === 'myLinkDatabase') {
-                            field instanceof Doc && references.add(field);
-                            // skip docs that have been closed and are scheduled for garbage collection
-                        } else {
-                            Doc.FindReferences(field, references, system);
-                        }
-                    }
-                } else if (cfield instanceof ComputedField) {
-                    /* empty */
-                } else if (field instanceof ObjectField) {
-                    if (field instanceof Doc) {
-                        Doc.FindReferences(field, references, system);
-                    } else if (field instanceof List) {
-                        !excludeLists && Doc.FindReferences(field, references, system);
-                    } else if (field instanceof ProxyField) {
-                        if (key === 'myLinkDatabase') {
-                            field instanceof Doc && references.add(field);
-                            // skip docs that have been closed and are scheduled for garbage collection
-                        } else {
-                            Doc.FindReferences(field.value, references, system);
-                        }
-                    } else if (field instanceof PrefetchProxy) {
-                        Doc.FindReferences(field.value, references, system);
-                    } else if (field instanceof RichTextField) {
-                        const re = /"docId"\s*:\s*"(.*?)"/g;
-                        let match: string[] | null;
-                        while ((match = re.exec(field.Data)) !== null) {
-                            const urlString = match[1];
-                            if (urlString) {
-                                const rdoc = DocServer.GetCachedRefField(urlString);
-                                if (rdoc) {
-                                    references.add(rdoc);
-                                    Doc.FindReferences(rdoc, references, system);
-                                }
-                            }
-                        }
+                if (field instanceof Doc) {
+                    return Doc.FindReferences(field, references, system);
+                }
+                if (field instanceof RichTextField) {
+                    let docId: string | undefined;
+                    while ((docId = (FindDocsInRTF.exec(field.Data) ?? [undefined, undefined, undefined])[2])) {
+                        Doc.FindReferences(DocServer.GetCachedRefField(docId), references, system);
                     }
-                } else if (field instanceof Promise) {
-                    // eslint-disable-next-line no-debugger
-                    debugger; // This shouldn't happend...
                 }
-            }
-        });
+            });
+        return references;
     }
 
+    /**
+     * Copies a Doc by copying its embedding (and optionally its prototype).  Values within the Doc are copied except for Docs which are
+     * sipmly referenced (except if they're marked to be copied - eg., template layout Docs)
+     * @param doc Doc to copy
+     * @param copyProto  whether to copy the Docs proto
+     * @param copyProtoId  the id to use for the proto if copied
+     * @param retitle whether to retitle the copy by adding a copy number to the title
+     * @returns the copied Doc
+     */
     export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc {
         const copy = runInAction(() => new Doc(copyProtoId, true));
         updateCachedAcls(copy);
@@ -994,24 +990,18 @@ export namespace Doc {
                         copy[key] = Doc.MakeCopy(doc.proto, false);
                     }
                 } else {
-                    const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
-                    const field = key === 'author' ? ClientUtils.CurrentUserEmail() : ProxyField.WithoutProxy(() => doc[key]);
-                    if (field instanceof RefField) {
-                        copy[key] = field;
-                    } else if (cfield instanceof ComputedField) {
-                        copy[key] = cfield[Copy](); // ComputedField.MakeFunction(cfield.script.originalScript);
-                    } else if (field instanceof ObjectField) {
-                        const docAtKey = doc[key];
-                        copy[key] =
-                            docAtKey instanceof Doc && (key.includes('layout[') || docAtKey.cloneOnCopy)
-                                ? new ProxyField(Doc.MakeCopy(docAtKey)) // copy the expanded render template
-                                : ObjectField.MakeCopy(field);
-                    } else if (field instanceof Promise) {
+                    const field = key === 'author' ? ClientUtils.CurrentUserEmail() : doc[key];
+                    if (field instanceof Promise) {
                         // eslint-disable-next-line no-debugger
                         debugger; // This shouldn't happend...
-                    } else {
-                        copy[key] = field;
                     }
+                    copy[key] = (() => {
+                        const cfield = ComputedField.DisableCompute(() => FieldValue(doc[key]));
+                        if (cfield instanceof ComputedField) return cfield[Copy]();
+                        if (field instanceof Doc) return field.cloneOnCopy ? Doc.MakeCopy(field) : field; // copy the expanded render template
+                        if (field instanceof ObjectField) return ObjectField.MakeCopy(field);
+                        return field;
+                    })();
                 }
             });
         if (copyProto) {
@@ -1027,6 +1017,14 @@ export namespace Doc {
         return copy;
     }
 
+    /**
+     * Makes a delegate of a prototype Doc.  Delegates inherit all of the properties of the
+     * prototype Doc, but can add new properties or mask existing prototype properties.
+     * @param doc prototype Doc to make a delgate of
+     * @param id id to use for delegate
+     * @param title title to use for delegate
+     * @returns a new Doc that is a delegate of the original Doc
+     */
     export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc;
     export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc>;
     export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc> {
@@ -1096,13 +1094,13 @@ export namespace Doc {
     //
     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 = keepFieldKey ? Doc.LayoutFieldKey(templateField) : StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutFieldKey(templateField);
+        const metadataFieldKey = keepFieldKey ? Doc.LayoutDataKey(templateField) : StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutDataKey(templateField);
 
         // update the original template to mark it as a template
         templateField.isTemplateForField = metadataFieldKey;
         !keepFieldKey && (templateField.title = metadataFieldKey);
 
-        const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)];
+        const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutDataKey(templateField)];
         // move any data that the template field had been rendering over to the template doc so that things will still be rendered
         // when the template field is adjusted to point to the new metadatafield key.
         // note 1: if the template field contained a list of documents, each of those documents will be converted to templates as well.
@@ -1148,55 +1146,74 @@ export namespace Doc {
         @observable _searchQuery: string = '';
     }
 
-    // the document containing the view layout information - will be the Document itself unless the Document has
-    // a layout field or 'layout' is given.
-    export function Layout(doc: Doc, template?: Doc): Doc {
-        const expandedTemplate = template && Cast(doc['layout_' + template.title + '(' + StrCast(template.isTemplateForField, 'data') + ')'], Doc, null);
+    /**
+     * The layout Doc containing the view layout information - this will be :
+     *   a) the Doc being rendered itself unless
+     *   b) a template Doc stored in the field sepcified by Doc's layout_fieldKey
+     *   c) or the specifeid 'template' Doc;
+     * If a template is specified, it will be expanded to create an instance specific to the rendered doc;
+     * @param doc the doc to render
+     * @param template a Doc to use as a template for the layout
+     * @returns
+     */
+    export function LayoutDoc(doc: Doc, template?: Doc): Doc {
+        const expandedTemplate = template && Cast(doc[expandedFieldName(template)], Doc, null);
         return expandedTemplate || doc[DocLayout];
     }
+    /**
+     * The JSX or Doc value defining how to render the Doc.
+     * @param doc Doc to render
+     * @returns a JSX string or Doc that describes how to render the Doc
+     */
     export function LayoutField(doc: Doc) {
         return doc[StrCast(doc.layout_fieldKey, 'layout')];
     }
-    export function LayoutFieldKey(doc: Doc, templateLayoutString?: string): string {
+    /**
+     * The field key of the Doc where the primary Data can be found to render the Doc.
+     * eg., for an image, this is likely to be the 'data' field which contains an image url,
+     * and for a text doc, this is likely to be the 'text' field ontaingin the text of the doc.
+     * @param doc Doc to render
+     * @param templateLayoutString optional JSX string that specifies the doc's data field key
+     * @returns field key where data is stored on Doc
+     */
+    export function LayoutDataKey(doc: Doc, templateLayoutString?: string): string {
         const match = StrCast(templateLayoutString || doc[DocLayout].layout).match(/fieldKey={'([^']+)'}/);
-        return match?.[1] || ''; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey
+        return match?.[1] || '';
     }
     export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
     }
     export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
-        return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0));
+        return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0));
     }
     export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
         if (!doc) return 0;
         const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) / NumCast(doc._width)) * NumCast(doc._height); // divide before multiply to avoid floating point errrorin case nativewidth = width
-        const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0);
+        const dheight = NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0);
         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.LayoutDataKey(doc)) + '_nativeWidth'] = width;
     }
     export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) {
-        doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height;
+        doc[(fieldKey || Doc.LayoutDataKey(doc)) + '_nativeHeight'] = height;
     }
 
     const manager = new UserDocData();
-    export function SearchQuery(): string {
+    export function SearchQuery() {
         return manager._searchQuery;
     }
     export function SetSearchQuery(query: string) {
-        runInAction(() => {
-            manager._searchQuery = query;
-        });
+        manager._searchQuery = query;
     }
-    export function UserDoc(): Doc {
+    export function UserDoc() {
         return manager._user_doc;
     }
-    export function SharingDoc(): Doc {
+    export function SharingDoc() {
         return Doc.MySharedDocs;
     }
-    export function LinkDBDoc(): Doc {
-        return Cast(Doc.UserDoc().myLinkDatabase, Doc, null);
+    export function LinkDBDoc() {
+        return DocCast(Doc.UserDoc().myLinkDatabase);
     }
     export function SetUserDoc(doc: Doc) {
         return (manager._user_doc = doc);
@@ -1221,7 +1238,7 @@ export namespace 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 }));
+        result && brushManager.SearchMatchDoc.set(doc, { searchMatch: backward ? -num : num });
         return doc;
     }
     export function ClearSearchMatches() {
@@ -1299,7 +1316,7 @@ export namespace Doc {
                 highlightedDocs.add(doc[DocData]);
                 doc[DocData][Highlight] = true;
                 // want to highlight the targets of presentation docs explicitly since following a pres target does not highlight PDf <Annotations> which are not DocumentViews
-                if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = true;
+                if (DocCast(doc.presentation_targetDoc)) DocCast(doc.presentation_targetDoc)![Highlight] = true;
             }
         });
     }
@@ -1311,7 +1328,7 @@ export namespace Doc {
                 highlightedDocs.delete(doc[DocData]);
                 doc[Highlight] = doc[DocData][Highlight] = false;
                 doc[Animation] = undefined;
-                if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = false;
+                if (DocCast(doc.presentation_targetDoc)) DocCast(doc.presentation_targetDoc)![Highlight] = false;
             });
         });
     }
@@ -1338,7 +1355,7 @@ export namespace Doc {
     export function setDocRangeFilter(container: Opt<Doc>, key: string, range?: readonly number[], modifiers?: 'remove') {
         if (!container) return;
 
-        const childFiltersByRanges = Cast(container._childFiltersByRanges, listSpec('string'), []);
+        const childFiltersByRanges = StrListCast(container._childFiltersByRanges);
 
         for (let i = 0; i < childFiltersByRanges.length; i += 3) {
             if (childFiltersByRanges[i] === key) {
@@ -1346,7 +1363,7 @@ export namespace Doc {
                 break;
             }
         }
-        if (range !== undefined) {
+        if (range) {
             childFiltersByRanges.push(key);
             childFiltersByRanges.push(range[0].toString());
             childFiltersByRanges.push(range[1].toString());
@@ -1401,7 +1418,7 @@ export namespace Doc {
         });
     }
     export function readDocRangeFilter(doc: Doc, key: string) {
-        const childFiltersByRanges = Cast(doc._childFiltersByRanges, listSpec('string'), []);
+        const childFiltersByRanges = StrListCast(doc._childFiltersByRanges);
         for (let i = 0; i < childFiltersByRanges.length; i += 3) {
             if (childFiltersByRanges[i] === key) {
                 return [Number(childFiltersByRanges[i + 1]), Number(childFiltersByRanges[i + 2])];
@@ -1436,8 +1453,9 @@ export namespace Doc {
         DocServer.GetRefFields(docids).then(async fieldlist => {
             const list = Array.from(fieldlist.values())
                 .map(d => DocCast(d))
-                .filter(d => d);
-            const docs = clone ? (await Promise.all(Doc.MakeClones(list, false, false))).map(res => res.clone) : list;
+                .filter(d => d)
+                .map(d => d!);
+            const docs = clone ? Doc.MakeClones(list, false, false).map(res => res.clone) : list;
             if (ptx !== undefined && pty !== undefined && newPoint !== undefined) {
                 const firstx = list.length ? NumCast(list[0].x) + ptx - newPoint[0] : 0;
                 const firsty = list.length ? NumCast(list[0].y) + pty - newPoint[1] : 0;
@@ -1457,16 +1475,16 @@ export namespace Doc {
      * @returns
      */
     export function getDescription(doc: Doc) {
-        const curDescription = StrCast(doc['$' + Doc.LayoutFieldKey(doc) + '_description']);
+        const curDescription = StrCast(doc['$' + Doc.LayoutDataKey(doc) + '_description']);
         const docText = (async (tdoc:Doc) => {
             switch (tdoc.type) {
                 case DocumentType.PDF: return curDescription || StrCast(tdoc.text).split(/\s+/).slice(0, 50).join(' '); // first 50 words of pdf text
-                case DocumentType.IMG: return curDescription || imageUrlToBase64(ImageCastWithSuffix(tdoc[Doc.LayoutFieldKey(tdoc)], '_o') ?? '')
+                case DocumentType.IMG: return curDescription || imageUrlToBase64(ImageCastWithSuffix(tdoc[Doc.LayoutDataKey(tdoc)], '_o') ?? '')
                                                 .then(hrefBase64 => gptImageLabel(hrefBase64, 'Give three to five labels to describe this image.'));
-                case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutFieldKey(tdoc)])?.Text ?? StrCast(tdoc[Doc.LayoutFieldKey(tdoc)]);
+                case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutDataKey(tdoc)])?.Text ?? StrCast(tdoc[Doc.LayoutDataKey(tdoc)]);
                 default:               return StrCast(tdoc.title).startsWith("Untitled") ? "" : StrCast(tdoc.title);
         }}); // prettier-ignore
-        return docText(doc).then(text => (doc['$' + Doc.LayoutFieldKey(doc) + '_description'] = text));
+        return docText(doc).then(text => (doc['$' + Doc.LayoutDataKey(doc) + '_description'] = text));
     }
 
     // prettier-ignore
@@ -1714,7 +1732,7 @@ export function IdToDoc(id: string) {
     return DocCast(DocServer.GetCachedRefField(id));
 }
 // eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function idToDoc(id: string): Doc {
+ScriptingGlobals.add(function idToDoc(id: string): Doc | undefined {
     return IdToDoc(id);
 });
 // eslint-disable-next-line prefer-arrow-callback
@@ -1768,7 +1786,7 @@ ScriptingGlobals.add(function docCastAsync(doc: FieldResult): FieldResult<Doc> {
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function activePresentationItem() {
     const curPres = Doc.ActivePresentation;
-    return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
+    return curPres && DocListCast(curPres[Doc.LayoutDataKey(curPres)])[NumCast(curPres._itemIndex)];
 });
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: string, modifiers: 'match' | 'check' | 'x' | 'remove') {
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 22bbcb9ab..ba03c0d38 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -255,7 +255,7 @@ export class ListImpl<T extends FieldType> extends ObjectField {
         if (fields) {
             this[SelfProxy].push(...fields);
         }
-        // eslint-disable-next-line no-constructor-return
+
         return list; // need to return the proxy here, otherwise we don't get any of our list handler functions
     }
 
@@ -317,8 +317,8 @@ export class ListImpl<T extends FieldType> extends ObjectField {
 
 // declare List as a type so you can use it in type declarations, e.g., { l: List, ...}
 export type List<T extends FieldType> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
-// decalre List as a value so you can invoke 'new' on it, e.g., new List<Doc>()  (since List<T> IS ListImpl<T>, we can safely cast the 'new' return value to return List<T>)
-// eslint-disable-next-line no-redeclare
+// declare List as a value so you can invoke 'new' on it, e.g., new List<Doc>()  (since List<T> IS ListImpl<T>, we can safely cast the 'new' return value to return List<T>)
+// eslint-disable-next-line no-use-before-define
 export const List: { new <T extends FieldType>(fields?: T[]): List<T> } = ListImpl as unknown as { new <T extends FieldType>(fields?: T[]): List<T> };
 
 ScriptingGlobals.add('List', List);
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index 48c336e60..21b9fe89b 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -16,9 +16,26 @@ function deserializeProxy(field: serializedProxyType) {
 }
 @Deserializable('proxy', (obj: unknown) => deserializeProxy(obj as serializedProxyType))
 export class ProxyField<T extends Doc> extends ObjectField {
+    @serializable(primitive())
+    readonly fieldId: string = '';
+
+    private failed = false;
+
+    // This getter/setter and nested object thing is
+    // because mobx doesn't play well with observable proxies
+    @observable.ref
+    private _cache: { readonly field: T | undefined; p: FieldWaiting<T> | undefined } = { field: undefined, p: undefined };
+    private get cache(): { field: T | undefined; p: FieldWaiting<T> | undefined } {
+        return this._cache;
+    }
+    private set cache(val: { field: T | undefined; p: FieldWaiting<T> | undefined }) {
+        runInAction(() => (this._cache = { ...val }));
+    }
+
     constructor();
     constructor(value: T);
     constructor(fieldId: string);
+    constructor(value: T | string);
     constructor(value?: T | string) {
         super();
         if (typeof value === 'string') {
@@ -30,13 +47,12 @@ export class ProxyField<T extends Doc> extends ObjectField {
         }
     }
 
-    [ToValue](/* doc: any */) {
+    [ToValue]() {
         return ProxyField.toValue(this);
     }
 
     [Copy]() {
-        if (this.cache.field) return new ProxyField<T>(this.cache.field);
-        return new ProxyField<T>(this.fieldId);
+        return new ProxyField<T>(this.cache.field ?? this.fieldId);
     }
 
     [ToJavascriptString]() {
@@ -46,27 +62,9 @@ export class ProxyField<T extends Doc> extends ObjectField {
         return Field.toScriptString(this[ToValue]()?.value as T); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ?
     }
     [ToString]() {
-        return Field.toString(this[ToValue]()?.value);
+        return Field.toString(this[ToValue]()?.value as T);
     }
 
-    @serializable(primitive())
-    readonly fieldId: string = '';
-
-    // This getter/setter and nested object thing is
-    // because mobx doesn't play well with observable proxies
-    @observable.ref
-    private _cache: { readonly field: T | undefined; p: FieldWaiting<T> | undefined } = { field: undefined, p: undefined };
-    private get cache(): { field: T | undefined; p: FieldWaiting<T> | undefined } {
-        return this._cache;
-    }
-    private set cache(val: { field: T | undefined; p: FieldWaiting<T> | undefined }) {
-        runInAction(() => {
-            this._cache = { ...val };
-        });
-    }
-
-    private failed = false;
-
     @computed get value(): T | undefined | FieldWaiting<T> {
         if (this.cache.field) return this.cache.field;
         if (this.failed) return undefined;
@@ -94,32 +92,19 @@ export class ProxyField<T extends Doc> extends ObjectField {
         return field;
     }
 }
-
-// eslint-disable-next-line no-redeclare, @typescript-eslint/no-namespace
 export namespace ProxyField {
     let useProxy = true;
-    export function DisableProxyFields() {
+    export function DisableDereference<T>(fn: () => T) {
         useProxy = false;
-    }
-
-    export function EnableProxyFields() {
-        useProxy = true;
-    }
-
-    export function WithoutProxy<T>(fn: () => T) {
-        DisableProxyFields();
         try {
             return fn();
         } finally {
-            EnableProxyFields();
+            useProxy = true;
         }
     }
 
     export function toValue(value: { value: unknown }) {
-        if (useProxy) {
-            return { value: value.value };
-        }
-        return undefined;
+        return useProxy ? { value: value.value } : undefined;
     }
 }
 
@@ -129,5 +114,6 @@ function prefetchValue(proxy: PrefetchProxy<Doc>) {
 }
 
 @scriptingGlobal
-@Deserializable('prefetch_proxy', (obj:unknown) => prefetchValue(obj as PrefetchProxy<Doc>))
+// eslint-disable-next-line no-use-before-define
+@Deserializable('prefetch_proxy', (obj: unknown) => prefetchValue(obj as PrefetchProxy<Doc>))
 export class PrefetchProxy<T extends Doc> extends ProxyField<T> {}
diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts
index 89e5cda8d..cdd278d67 100644
--- a/src/fields/Schema.ts
+++ b/src/fields/Schema.ts
@@ -1,6 +1,3 @@
-/* eslint-disable guard-for-in */
-/* eslint-disable no-restricted-syntax */
-/* eslint-disable no-redeclare */
 /* eslint-disable no-use-before-define */
 import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from './Types';
 import { Doc, FieldType } from './Doc';
@@ -16,7 +13,6 @@ type AllToInterface<T extends Interface[]> = {
 
 export const emptySchema = createSchema({});
 export const Document = makeInterface(emptySchema);
-// eslint-disable-next-line no-redeclare
 export type Document = makeInterface<[typeof emptySchema]>;
 
 export interface InterfaceFunc<T extends Interface[]> {
@@ -36,9 +32,10 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
     const proto = new Proxy(
         {},
         {
-            get(target: any, prop, receiver) {
+            get(target: unknown, prop, receiver) {
                 const field = receiver.doc?.[prop];
                 if (prop in schema) {
+                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                     const desc = prop === 'proto' ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should?
                     if (typeof desc === 'object' && 'defaultVal' in desc && 'type' in desc) {
                         // defaultSpec
@@ -59,7 +56,7 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
                 }
                 return field;
             },
-            set(target: any, prop, value, receiver) {
+            set(target: unknown, prop, value, receiver) {
                 receiver.doc && (receiver.doc[prop] = value); // receiver.doc may be undefined as the result of a change in acls
                 return true;
             },
@@ -77,10 +74,10 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc)
         const type = schema[key];
         Object.defineProperty(proto, key, {
             get() {
-                return Cast(this.__doc[key], type as any);
+                return Cast(this.__doc[key], type as never);
             },
             set(setValue) {
-                const value = Cast(setValue, type as any);
+                const value = Cast(setValue, type as never);
                 if (value !== undefined) {
                     this.__doc[key] = value;
                     return;
@@ -89,7 +86,7 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc)
             },
         });
     }
-    return function (doc: any) {
+    return function (doc: unknown) {
         if (!(doc instanceof Doc)) {
             throw new Error("Currently wrapping a schema in another schema isn't supported");
         }
@@ -101,18 +98,18 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc)
 
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
 export function createSchema<T extends Interface>(schema: T): T & { proto: ToConstructor<Doc> } {
-    return undefined as any;
+    return undefined as never;
     // (schema as any).proto = Doc;
     // return schema as any;
 }
 
 export function listSpec<U extends ToConstructor<FieldType>>(type: U): ListSpec<ToType<U>> {
-    return { List: type as any }; // TODO Types
+    return { List: type as never }; // TODO Types
 }
 
 export function defaultSpec<T extends ToConstructor<FieldType>>(type: T, defaultVal: ToType<T>): DefaultFieldConstructor<ToType<T>> {
     return {
-        type: type as any,
+        type: type as never,
         defaultVal,
     };
 }
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 6e2d7eb78..37b65684b 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -149,7 +149,6 @@ export class ScriptField extends ObjectField {
                 ...params,
             },
             transformer,
-            typecheck: false,
             editable: true,
             addReturn: addReturn,
             capturedVariables,
@@ -189,14 +188,12 @@ export class ScriptField extends ObjectField {
 export class ComputedField extends ScriptField {
     static undefined = '__undefined';
     static useComputed = true;
-    static DisableComputedFields() { this.useComputed = false; } // prettier-ignore
-    static EnableComputedFields()  { this.useComputed = true; } // prettier-ignore
-    static WithoutComputed<T>(fn: () => T) {
-        this.DisableComputedFields();
+    static DisableCompute<T>(fn: () => T) {
+        this.useComputed = false;
         try {
             return fn();
         } finally {
-            this.EnableComputedFields();
+            this.useComputed = true;
         }
     }
 
@@ -239,7 +236,8 @@ export class ComputedField extends ScriptField {
     public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt<number>) {
         if (!doc[`${fieldKey}_indexed`]) {
             const flist = new List<number>(numberRange(curTimecode + 1).map(emptyFunction) as unknown as number[]);
-            flist[curTimecode] = Cast(doc[fieldKey], 'number', null);
+            if (Cast(doc[fieldKey], 'number', null) === undefined) delete flist[curTimecode];
+            else flist[curTimecode] = Cast(doc[fieldKey], 'number', null)!;
             doc[`${fieldKey}_indexed`] = flist;
         }
         const getField = ScriptField.CompileScript(`getIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey}, ${defaultVal})`, {}, true, {});
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index dc7516f38..e6755f828 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -8,7 +8,7 @@ import { ScriptField } from './ScriptField';
 import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
 
 // eslint-disable-next-line no-use-before-define
-export type ToConstructor<T extends FieldType> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T;
+export type ToConstructor<T extends FieldType> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: never[]) => T;
 
 export type DefaultFieldConstructor<T extends FieldType> = {
     type: ToConstructor<T>;
@@ -17,7 +17,7 @@ export type DefaultFieldConstructor<T extends FieldType> = {
 // type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
 export type ListSpec<T extends FieldType> = { List: ToConstructor<T> };
 
-export type InterfaceValue = ToConstructor<FieldType> | ListSpec<FieldType> | DefaultFieldConstructor<FieldType> | ((doc?: Doc) => any);
+export type InterfaceValue = ToConstructor<FieldType> | ListSpec<FieldType> | DefaultFieldConstructor<FieldType> | ((doc?: Doc) => never);
 
 export type ToType<T extends InterfaceValue> = T extends 'string'
     ? string
@@ -31,9 +31,9 @@ export type ToType<T extends InterfaceValue> = T extends 'string'
             // eslint-disable-next-line @typescript-eslint/no-unused-vars
             T extends DefaultFieldConstructor<infer _U>
             ? never
-            : T extends { new (...args: any[]): List<FieldType> }
+            : T extends { new (...args: never[]): List<FieldType> }
               ? never
-              : T extends { new (...args: any[]): infer R }
+              : T extends { new (...args: never[]): infer R }
                 ? R
                 : T extends (doc?: Doc) => infer R
                   ? R
@@ -41,29 +41,26 @@ export type ToType<T extends InterfaceValue> = T extends 'string'
 
 export interface Interface {
     [key: string]: InterfaceValue;
-    // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
 }
 export type ToInterface<T extends Interface> = {
     [P in Exclude<keyof T, 'proto'>]: T[P] extends DefaultFieldConstructor<infer F> ? Exclude<FieldResult<F>, undefined> : FieldResult<ToType<T[P]>>;
 };
 
-// type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
-
-export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
-export type Tail<T extends any[]> = ((...t: T) => any) extends (_: any, ...tail: infer TT) => any ? TT : [];
-export type HasTail<T extends any[]> = T extends [] | [any] ? false : true;
+export type Head<T extends unknown[]> = T extends [unknown, ...unknown[]] ? T[0] : Interface;
+export type Tail<T extends unknown[]> = ((...t: T) => unknown) extends (_: unknown, ...tail: infer TT) => unknown ? TT : [];
+export type HasTail<T extends unknown[]> = T extends [] | [unknown] ? false : true;
 // TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
-export type WithoutRefField<T extends FieldType> = T extends RefField ? never : T;
+export type WithoutRefField<T extends FieldType> = T extends RefField ? unknown : T;
 
 export type CastCtor = ToConstructor<FieldType> | ListSpec<FieldType>;
 
 type WithoutList<T extends FieldType> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
 
-export function Cast<T extends CastCtor>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
-export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
+export function Cast<T extends CastCtor>(field: FieldResult, ctor: T): FieldResult<ToType<T>> | undefined;
+export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<ToType<T>> | null): WithoutList<ToType<T>> | undefined;
 export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined {
     if (field instanceof Promise) {
-        return defaultVal === undefined ? (field.then(f => Cast(f, ctor) as any) as any) : defaultVal === null ? undefined : defaultVal;
+        return defaultVal === undefined ? (field.then(f => Cast(f, ctor) as ToType<T>) as ToType<T>) : defaultVal === null ? undefined : defaultVal;
     }
     if (field !== undefined && !(field instanceof Promise)) {
         if (typeof ctor === 'string') {
@@ -72,10 +69,12 @@ export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal
             }
         } else if (typeof ctor === 'object') {
             if (field instanceof List) {
-                return field as any;
+                return field as ToType<T>;
             }
+            // eslint-disable-next-line @typescript-eslint/no-explicit-any
         } else if (field instanceof (ctor as any)) {
             return field as ToType<T>;
+            // eslint-disable-next-line @typescript-eslint/no-explicit-any
         } else if (field instanceof ProxyField && field.value instanceof (ctor as any)) {
             return field.value as ToType<T>;
         }
@@ -83,57 +82,27 @@ export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal
     return defaultVal === null ? undefined : defaultVal;
 }
 
-export function toList(doc: Doc | Doc[]) {
-    return doc instanceof Doc ? [doc] : doc;
-}
+export function toList(doc: Doc | Doc[]) { return doc instanceof Doc ? [doc] : doc; } // prettier-ignore
 
 export function DocCast(field: FieldResult, defaultVal?: Doc) {
-    const doc = Cast(field, Doc, null);
-    return doc && !(doc instanceof Promise) ? doc : (defaultVal as Doc);
-}
-
-export function NumCast(field: FieldResult, defaultVal: number | null = 0) {
-    return Cast(field, 'number', defaultVal);
-}
-
-export function StrCast(field: FieldResult, defaultVal: string | null = '') {
-    return Cast(field, 'string', defaultVal);
-}
+    return ((doc: Doc | undefined) => (doc && !(doc instanceof Promise) ? doc : defaultVal))(Cast(field, Doc, null));
+}
+export function NumCast   (field: FieldResult, defaultVal: number | null = 0)          { return Cast(field, 'number', defaultVal)!; } // prettier-ignore
+export function StrCast   (field: FieldResult, defaultVal: string | null = '')         { return Cast(field, 'string', defaultVal)!; } // prettier-ignore
+export function BoolCast  (field: FieldResult, defaultVal: boolean | null = false)     { return Cast(field, 'boolean', defaultVal)!; } // prettier-ignore
+export function DateCast  (field: FieldResult, defaultVal: DateField | null = null)    { return Cast(field, DateField, defaultVal); } // prettier-ignore
+export function RTFCast   (field: FieldResult, defaultVal: RichTextField | null = null){ return Cast(field, RichTextField, defaultVal); } // prettier-ignore
+export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null)  { return Cast(field, ScriptField, defaultVal); } // prettier-ignore
+export function CsvCast   (field: FieldResult, defaultVal: CsvField | null = null)     { return Cast(field, CsvField, defaultVal); } // prettier-ignore
+export function WebCast   (field: FieldResult, defaultVal: WebField | null = null)     { return Cast(field, WebField, defaultVal); } // prettier-ignore
+export function VideoCast (field: FieldResult, defaultVal: VideoField | null = null)   { return Cast(field, VideoField, defaultVal); } // prettier-ignore
+export function AudioCast (field: FieldResult, defaultVal: AudioField | null = null)   { return Cast(field, AudioField, defaultVal); } // prettier-ignore
+export function PDFCast   (field: FieldResult, defaultVal: PdfField | null = null)     { return Cast(field, PdfField, defaultVal); } // prettier-ignore
+export function ImageCast (field: FieldResult, defaultVal: ImageField | null = null)   { return Cast(field, ImageField, defaultVal); } // prettier-ignore
 
-export function BoolCast(field: FieldResult, defaultVal: boolean | null = false) {
-    return Cast(field, 'boolean', defaultVal);
-}
-export function DateCast(field: FieldResult) {
-    return Cast(field, DateField, null);
-}
-export function RTFCast(field: FieldResult) {
-    return Cast(field, RichTextField, null);
-}
-
-export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null) {
-    return Cast(field, ScriptField, defaultVal);
-}
-export function CsvCast(field: FieldResult, defaultVal: CsvField | null = null) {
-    return Cast(field, CsvField, defaultVal);
-}
-export function WebCast(field: FieldResult, defaultVal: WebField | null = null) {
-    return Cast(field, WebField, defaultVal);
-}
-export function VideoCast(field: FieldResult, defaultVal: VideoField | null = null) {
-    return Cast(field, VideoField, defaultVal);
-}
-export function AudioCast(field: FieldResult, defaultVal: AudioField | null = null) {
-    return Cast(field, AudioField, defaultVal);
-}
-export function PDFCast(field: FieldResult, defaultVal: PdfField | null = null) {
-    return Cast(field, PdfField, defaultVal);
-}
-export function ImageCast(field: FieldResult, defaultVal: ImageField | null = null) {
-    return Cast(field, ImageField, defaultVal);
-}
 export function ImageCastToNameType(field: FieldResult, defaultVal: ImageField | null = null) {
     const href = ImageCast(field, defaultVal)?.url.href;
-    return href ? [href.replace(/.[^.]*$/, ''), href.split('.').lastElement()] : ["", ""];
+    return href ? [href.replace(/.[^.]*$/, ''), href.split('.').lastElement()] : ['', ''];
 }
 export function ImageCastWithSuffix(field: FieldResult, suffix: string, defaultVal: ImageField | null = null) {
     const [name, type] = ImageCastToNameType(field, defaultVal);
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 205c500a5..799fa3758 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -241,19 +241,19 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
 
         dataDoc[DirectLinks].forEach(link => distributeAcls(key, acl, link, visited, !!allowUpgrade));
 
-        DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => {
+        DocListCast(dataDoc[Doc.LayoutDataKey(dataDoc)]).forEach(d => {
             distributeAcls(key, acl, d, visited, !!allowUpgrade);
             d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, !!allowUpgrade);
         });
 
-        DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => {
+        DocListCast(dataDoc[Doc.LayoutDataKey(dataDoc) + '_annotations']).forEach(d => {
             distributeAcls(key, acl, d, visited, !!allowUpgrade);
             d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, !!allowUpgrade);
         });
 
-        Object.keys(target) // share expanded layout templates (eg, for presElementBox'es )
-            .filter(lkey => lkey.includes('layout[') && DocCast(target[lkey]))
-            .forEach(lkey => distributeAcls(key, acl, DocCast(target[lkey]), visited, !!allowUpgrade));
+        Object.keys(target) // share expanded layout templates (eg, for PresSlideBox'es )
+            .filter(lkey => lkey.includes('layout_[') && DocCast(target[lkey]))
+            .forEach(lkey => distributeAcls(key, acl, DocCast(target[lkey])!, visited, !!allowUpgrade));
 
         if (GetEffectiveAcl(dataDoc) === AclAdmin) {
             dataDoc[key] = acl;
-- 
cgit v1.2.3-70-g09d2


From 1f294ef4a171eec72a069a9503629eaf7975d983 Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Thu, 17 Apr 2025 13:52:58 -0400
Subject: fix for resizing docs with native width  and modify native dim set
 (fixes dailyjournal resizing).

---
 src/client/documents/DocUtils.ts                   | 32 ++--------------------
 src/client/util/CurrentUserUtils.ts                |  2 +-
 src/client/views/DocumentDecorations.tsx           |  4 +--
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 .../views/nodes/chatbot/vectorstore/Vectorstore.ts |  3 +-
 5 files changed, 9 insertions(+), 34 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index 7845c80aa..7b36c3a66 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -711,8 +711,9 @@ export namespace DocUtils {
                       nativeWidth: 40,
                       nativeHeight: 40,
                   })
-                : defaultTextTemplate?.type === DocumentType.JOURNAL
-                  ? Docs.Create.DailyJournalDocument('', {
+                : (defaultTextTemplate?.type === DocumentType.JOURNAL ? Docs.Create.DailyJournalDocument:Docs.Create.TextDocument)(
+                    '', 
+                    {
                         annotationOn,
                         backgroundColor,
                         x,
@@ -723,33 +724,6 @@ export namespace DocUtils {
                             : {
                                   _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200,
                                   _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35,
-                                  _layout_fitWidth: true,
-                                  _layout_autoHeight: true,
-                                  backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor),
-                                  borderColor: Doc.UserDoc().borderColor as string,
-                                  borderWidth: Doc.UserDoc().borderWidth as number,
-                                  text_centered: BoolCast(Doc.UserDoc().textCentered),
-                                  text_fitBox: BoolCast(Doc.UserDoc().fitBox),
-                                  text_align: StrCast(Doc.UserDoc().textAlign),
-                                  text_fontColor: StrCast(Doc.UserDoc().fontColor),
-                                  text_fontFamily: StrCast(Doc.UserDoc().fontFamily),
-                                  text_fontWeight: StrCast(Doc.UserDoc().fontWeight),
-                                  text_fontStyle: StrCast(Doc.UserDoc().fontStyle),
-                                  text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration),
-                              }),
-                    })
-                  : Docs.Create.TextDocument('', {
-                        annotationOn,
-                        backgroundColor,
-                        x,
-                        y,
-                        title,
-                        ...(defaultTextTemplate
-                            ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance
-                            : {
-                                  _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200,
-                                  _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35,
-                                  _layout_fitWidth: true,
                                   _layout_autoHeight: true,
                                   backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor),
                                   borderColor: Doc.UserDoc().borderColor as string,
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 1887c1716..99a67ebb2 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -406,7 +406,7 @@ pie title Minerals in my tap water
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("", opts),     opts: { _width: 300, _height: 300, }},
             // AARAV ADD //
-            {key: "DailyJournal",creator:opts => Docs.Create.DailyJournalDocument("", opts),opts: { _width: 300, _height: 300}},
+            {key: "DailyJournal",creator:opts => Docs.Create.DailyJournalDocument("", opts),opts: { _width: 300, _height: 300, }},
             {key: "Chat",       creator: Docs.Create.ChatDocument,                          opts: { _width: 500, _height: 500, _layout_fitWidth: true, }},
             {key: "MetaNote",   creator: metaNoteTemplate,                                  opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}},
             {key: "ViewSlide",  creator: slideView,                                         opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}},
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 0756d3d9e..120467e8a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -551,7 +551,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 scale.x === 1 ? (scale.x = scale.y) : (scale.y = scale.x);
             }
 
-            if (['right', 'left'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeWidth(doc)) {
+            if (['right', 'left', 'bottomRight'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeWidth(doc)) {
                 const setData = Doc.NativeWidth(doc[DocData]) === doc.nativeWidth;
                 doc.nativeWidth = scale.x * Doc.NativeWidth(doc);
                 if (setData) Doc.SetNativeWidth(doc[DocData], NumCast(doc.nativeWidth));
@@ -559,7 +559,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                     doc._nativeHeight = (initHeight / initWidth) * nwidth; // initializes the nativeHeight for a PDF
                 }
             }
-            if (['bottom', 'top'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeHeight(doc)) {
+            if (['bottom', 'top', 'bottomRight'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeHeight(doc)) {
                 const setData = Doc.NativeHeight(doc[DocData]) === doc.nativeHeight && (!doc.layout_reflowVertical || opts.ctrlKey);
                 doc._nativeHeight = scale.y * Doc.NativeHeight(doc);
                 if (setData) Doc.SetNativeHeight(doc[DocData], NumCast(doc._nativeHeight));
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 521934152..284014e54 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1208,7 +1208,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     }
     @computed private get nativeScaling() {
         if (this.shouldNotScale) return 1;
-        const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
+        const minTextScale = [DocumentType.RTF, DocumentType.JOURNAL].includes(this.Document.type as DocumentType) ? 0.1 : 0;
         const ai = this._showAIEditor && this.nativeWidth === this.layoutDoc.width ? 95 : 0;
         const effNW = Math.max(this.effectiveNativeWidth - ai, 1);
         const effNH = Math.max(this.effectiveNativeHeight - ai, 1);
diff --git a/src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts b/src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts
index afd34f28d..c1915a398 100644
--- a/src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts
+++ b/src/client/views/nodes/chatbot/vectorstore/Vectorstore.ts
@@ -42,7 +42,8 @@ export class Vectorstore {
     constructor(id: string, doc_ids: () => string[]) {
         const pineconeApiKey = process.env.PINECONE_API_KEY;
         if (!pineconeApiKey) {
-            throw new Error('PINECONE_API_KEY is not defined.');
+            console.log('PINECONE_API_KEY is not defined - Vectorstore will be unavailable');
+            return;
         }
 
         // Initialize Pinecone and OpenAI clients with API keys from the environment.
-- 
cgit v1.2.3-70-g09d2


From 19adcca1a89405a47da57fee64fe5fde4720c16a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 5 May 2025 14:05:32 -0400
Subject: got rid of dash _xPadding/_yPadding in favor of just using
 _xMargin/_yMargin.  cleaned up linearView field names

---
 src/client/documents/DocUtils.ts                   |  2 +-
 src/client/documents/Documents.ts                  | 14 +++---
 src/client/util/CurrentUserUtils.ts                | 52 +++++++++++-----------
 src/client/util/LinkManager.ts                     |  4 +-
 src/client/views/InkingStroke.tsx                  |  4 +-
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/ObservableReactComponent.tsx      |  8 ++--
 src/client/views/PropertiesButtons.tsx             |  2 +-
 .../views/collections/CollectionCardDeckView.tsx   |  2 +-
 .../views/collections/CollectionCarouselView.tsx   |  2 +-
 .../views/collections/CollectionStackingView.tsx   |  6 +--
 src/client/views/collections/TabDocView.tsx        |  4 +-
 src/client/views/collections/TreeView.tsx          |  8 ++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  6 +--
 .../collectionGrid/CollectionGridView.tsx          |  2 +-
 .../collectionLinear/CollectionLinearView.tsx      | 16 +++----
 src/client/views/nodes/DocumentView.tsx            |  4 +-
 src/client/views/nodes/FieldView.tsx               |  4 +-
 src/client/views/nodes/LabelBox.tsx                | 12 ++---
 src/client/views/nodes/PDFBox.tsx                  |  4 +-
 src/client/views/nodes/ScreenshotBox.tsx           |  4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  8 ++--
 src/client/views/nodes/trails/PresSlideBox.tsx     |  8 ++--
 src/fields/documentSchemas.ts                      |  2 -
 24 files changed, 86 insertions(+), 94 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index df14dce5a..9135899d7 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -603,7 +603,7 @@ export namespace DocUtils {
                 layout_hideAllLinks: true,
                 _width: 15,
                 _height: 15,
-                _xPadding: 0,
+                _xMargin: 0,
                 onClick: FollowLinkScript(),
                 _timecodeToShow: Cast(doc._timecodeToShow, 'number', null),
             });
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a4a668085..bbe872a91 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -198,7 +198,6 @@ export class DocumentOptions {
     _height?: NUMt = new NumInfo("height of document in container's coordiantes");
     data_nativeWidth?: NUMt = new NumInfo('native width of data field contents (e.g., the pixel width of an image)', false);
     data_nativeHeight?: NUMt = new NumInfo('native height of data field contents (e.g., the pixel height of an image)', false);
-    linearBtnWidth?: NUMt = new NumInfo('unexpanded width of a linear menu button (button "width" changes when it expands)', false);
     _nativeWidth?: NUMt = new NumInfo('Deprecated: use nativeWidth. native width of document contents (e.g., the pixel width of an image)', false);
     _nativeHeight?: NUMt = new NumInfo('Deprecated: use nativeHeight. native height of document contents (e.g., the pixel height of an image)', false);
     nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)', false);
@@ -294,10 +293,8 @@ export class DocumentOptions {
     _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);
-    _xPadding?: NUMt = new NumInfo('x padding', false);
-    _yPadding?: NUMt = new NumInfo('y padding', false);
+    _xMargin?: NUMt = new NumInfo('gap between left edge of document and contents of freeform/masonry/stacking layouts', false);
+    _yMargin?: NUMt = new NumInfo('gap between top edge of dcoument and contents offreeform/masonry/stacking layouts', false);
     _createDocOnCR?: boolean; // whether carriage returns and tabs create new text documents
     _columnsHideIfEmpty?: BOOLt = new BoolInfo('whether stacking view column headings should be hidden');
     _caption_xMargin?: NUMt = new NumInfo('x margin of caption inside of a carousel collection', false, true);
@@ -427,10 +424,9 @@ export class DocumentOptions {
     badgeValue?: ScriptField;
 
     // LINEAR VIEW
-    linearView_IsOpen?: BOOLt = new BoolInfo('is linear view open');
-    linearView_Expandable?: BOOLt = new BoolInfo('can linear view be expanded');
-    linearView_Dropdown?: BOOLt = new BoolInfo('can linear view be opened as a dropdown');
-    linearView_SubMenu?: BOOLt = new BoolInfo('is doc a sub menu of more linear views');
+    linearView_btnWidth?: NUMt = new NumInfo('unexpanded width of a linear menu button (button "width" changes when it expands)', false);
+    linearView_isOpen?: BOOLt = new BoolInfo('is linear view open');
+    linearView_expandable?: BOOLt = new BoolInfo('can linear view be expanded');
     flexGap?: NUMt = new NumInfo('Linear view flex gap');
     flexDirection?: 'unset' | 'row' | 'column' | 'row-reverse' | 'column-reverse';
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 8f4d568ab..d460cd0d7 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -52,7 +52,7 @@ export interface Button {
     numBtnMax?: number;
     switchToggle?: boolean;
     width?: number;
-    linearBtnWidth?: number;
+    linearView_btnWidth?: number;
     toolType?: string; // type of pen tool
     expertMode?: boolean;// available only in expert mode
     btnList?: List<string>;
@@ -205,7 +205,7 @@ export class CurrentUserUtils {
         const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
 
         const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({
-            layout: LabelBox.LayoutString(fieldKey), letterSpacing: "unset", _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px",  _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
+            layout: LabelBox.LayoutString(fieldKey), letterSpacing: "unset", _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px",  _width: 150, _height: 70, _xMargin: 10, _yMargin: 10, ...opts
         });
         const imageBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.ImageDocument( "http://www.cs.brown.edu/~bcz/noImage.png", { layout:ImageBox.LayoutString(fieldKey), "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG,  _width: 360 / 4, _height: 270 / 4,  _layout_showTitle: "title", ...opts });
         const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts  });
@@ -402,7 +402,7 @@ pie title Minerals in my tap water
             {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, title_custom: true,  waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}},
+            {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xMargin: 10, _yMargin: 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("", opts),     opts: { _width: 300, _height: 300, }},
             // AARAV ADD //
@@ -663,7 +663,7 @@ pie title Minerals in my tap water
     }
 
     static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.LinearDocument(docs, {
-        ...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, layout_boxShadow: "0 0", _forceActive: true,
+        ...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, layout_boxShadow: "0 0", _forceActive: true, linearView: true,
         dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
         _lockedPosition: true, isSystem: true, flexDirection: "row"
     })
@@ -697,7 +697,7 @@ pie title Minerals in my tap water
         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: dropActionType.move,
-            childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: true, ignoreClick: true
+            childDontRegisterViews: true, linearView_isOpen: true, linearView_expandable: true, ignoreClick: true
         };
         reaction(() => UndoManager.redoStack.slice(), () => { Doc.GetProto(btns.find(btn => btn.title === "Redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4; }, { fireImmediately: true });
         reaction(() => UndoManager.undoStack.slice(), () => { Doc.GetProto(btns.find(btn => btn.title === "Undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4; }, { fireImmediately: true });
@@ -789,24 +789,24 @@ pie title Minerals in my tap water
             { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",      toolType: Gestures.Circle,                           scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`,   onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
             { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",      toolType: Gestures.Rectangle,                        scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`,   onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
             { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",       toolType: Gestures.Line,                             scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`,   onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
-            { title: "Ink",    toolTip: "Ink",                              btnType: ButtonType.MultiToggleButton,                 toolType: InkTool.Ink,        showUntilToggle: true,  scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, 
+            { title: "Ink",    toolTip: "Ink",                              btnType: ButtonType.MultiToggleButton,                 toolType: InkTool.Ink,        showUntilToggle: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, 
                 subMenu: [
                     { title: "Pen",      toolTip: "Pen (Ctrl+P)",           btnType: ButtonType.ToggleButton, icon: "pen-nib",     toolType: InkInkTool.Pen,         ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }},
                     { title: "Highlight",toolTip: "Highlight (Ctrl+H)",     btnType: ButtonType.ToggleButton, icon: "highlighter", toolType: InkInkTool.Highlight,   ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }},
                     { title: "Write",    toolTip: "Write (Ctrl+Shift+P)",   btnType: ButtonType.ToggleButton, icon: "pen",         toolType: InkInkTool.Write,       ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' },  funcs: {hidden:"IsNoviceMode()" }},      
                 ]},
-            { title: "Width",      toolTip: "Stroke width",                 btnType: ButtonType.NumberSliderButton,                toolType: InkProperty.StrokeWidth,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}, numBtnMin: 1, linearBtnWidth:40},
+            { title: "Width",      toolTip: "Stroke width",                 btnType: ButtonType.NumberSliderButton,                toolType: InkProperty.StrokeWidth,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}, numBtnMin: 1, linearView_btnWidth:40},
             { title: "Color",      toolTip: "Stroke color",                 btnType: ButtonType.ColorButton,  icon: "pen",         toolType: InkProperty.StrokeColor,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}},
-            { title: "Eraser",     toolTip: "Eraser (Ctrl+E)",              btnType: ButtonType.MultiToggleButton,                 toolType: InkTool.Eraser,     showUntilToggle: true,  scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' },
+            { title: "Eraser",     toolTip: "Eraser (Ctrl+E)",              btnType: ButtonType.MultiToggleButton,                 toolType: InkTool.Eraser,     showUntilToggle: true, scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' },
               subMenu: [
                     { title: "Stroke",   toolTip: "Eraser complete strokes",btnType: ButtonType.ToggleButton, icon: "eraser",      toolType:InkEraserTool.Stroke,    ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}},
                     { title: "Segment",  toolTip: "Erase between intersections",btnType:ButtonType.ToggleButton,icon:"xmark",      toolType:InkEraserTool.Segment,   ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}},
                     { title: "Area",     toolTip: "Erase like a pencil",    btnType: ButtonType.ToggleButton, icon: "circle-xmark",toolType:InkEraserTool.Radius,    ignoreClick: true, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}},
             ]},
-            { title: " Size",      toolTip: "Size of area pencil eraser",   btnType: ButtonType.NumberSliderButton,                toolType: InkProperty.EraserWidth,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"NotRadiusEraser()"}, numBtnMin: 1, linearBtnWidth:40},
+            { title: " Size",      toolTip: "Size of area pencil eraser",   btnType: ButtonType.NumberSliderButton,                toolType: InkProperty.EraserWidth,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"NotRadiusEraser()"}, numBtnMin: 1, linearView_btnWidth:40},
             { title: "Mask",       toolTip: "Make Stroke a Stencil Mask",   btnType: ButtonType.ToggleButton, icon: "user-circle", toolType: InkProperty.Mask,                          scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
             { title: "Labels",     toolTip: "Show Labels Inside Shapes",    btnType: ButtonType.ToggleButton, icon: "text-width",  toolType: InkProperty.Labels,                        scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}},
-            { title: "Smart Draw", toolTip: "Draw with AI",                btnType: ButtonType.ToggleButton, icon: "user-pen",    toolType: InkTool.SmartDraw,                         scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'},  funcs: {hidden: "IsNoviceMode()"}},
+            { title: "Smart Draw", toolTip: "Draw with AI",                 btnType: ButtonType.ToggleButton, icon: "user-pen",    toolType: InkTool.SmartDraw,                         scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'},  funcs: {hidden: "IsNoviceMode()"}},
         ];
     }
 
@@ -843,7 +843,7 @@ pie title Minerals in my tap water
             { title: "Template",icon: "scroll",        toolTip: "Default Note Template",btnType: ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.RTF,                                                                                                              scripts: { onClick: '{ return setDefaultTemplate(_readOnly_); }'} },
             { title: "Fill",    icon: "fill-drip",     toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton,  expertMode: false,                                                                                                          ignoreClick: true, width: 30,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'} }, // Only when a document is selected
             { title: "Border",  icon: "border-style",  toolTip: "Border Color",         btnType: ButtonType.ColorButton,  expertMode: false,                                                                                                          ignoreClick: true, width: 30,  scripts: { script:  'return setBorderColor(value, _readOnly_)'} }, // Only when a document is selected
-            { title: "B.Width",                        toolTip: "Border width",         btnType: ButtonType.NumberSliderButton,                                                                                                                       ignoreClick: true,             scripts: {script: '{ return setBorderWidth(value, _readOnly_);}'}, numBtnMin: 0, linearBtnWidth:40},
+            { title: "B.Width",                        toolTip: "Border width",         btnType: ButtonType.NumberSliderButton,                                                                                                                       ignoreClick: true,             scripts: {script: '{ return setBorderWidth(value, _readOnly_);}'}, numBtnMin: 0, linearView_btnWidth:40},
             { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton, expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode, true)'},                        scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,  expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'},                  width: 30,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
             { title: "Num",     icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton,   expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
@@ -851,17 +851,17 @@ pie title Minerals in my tap water
             { title: "Chat",    icon: "lightbulb",     toolTip: "Toggle Chat Assistant",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",               funcs: {},                                                                            width: 30,  scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
             { title: "Filter",  icon: "=",             toolTip: "Filter cards by tags", btnType: ButtonType.MultiToggleButton, 
                                                                                         subMenu: this.filterTools(),      expertMode: false, toolType:DocumentType.COL,            funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'},ignoreClick:true, width: 30},
-            { title: "Sort",    icon: "Sort",          toolTip: "Sort Documents",       subMenu: this.sortTools(),        expertMode: false, toolType:DocumentType.COL,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            { title: "Text",    icon: "Text",          toolTip: "Text functions",       subMenu: this.textTools(),        expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                            linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            { title: "Ink",     icon: "Ink",           toolTip: "Ink functions",        subMenu: this.inkTools(),         expertMode: false, toolType:DocumentType.INK,            funcs: {hidden: `IsExploreMode()`,                                  linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
-            { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: this.freeTools(),        expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: this.viewTools(),        expertMode: false, toolType:DocumentType.COL,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: this.stackTools(),       expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Sort",    icon: "Sort",          toolTip: "Sort Documents",       subMenu: this.sortTools(),        expertMode: false, toolType:DocumentType.COL,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_isOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Text",    icon: "Text",          toolTip: "Text functions",       subMenu: this.textTools(),        expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                            linearView_isOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Ink",     icon: "Ink",           toolTip: "Ink functions",        subMenu: this.inkTools(),         expertMode: false, toolType:DocumentType.INK,            funcs: {hidden: `IsExploreMode()`,                                  linearView_isOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
+            { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: this.freeTools(),        expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_isOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: this.viewTools(),        expertMode: false, toolType:DocumentType.COL,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_isOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: this.stackTools(),       expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_isOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
                    
-            { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: this.webTools(),         expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
-            { title: "Video",   icon: "Video",         toolTip: "Video functions",      subMenu: this.videoTools(),       expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
-            { title: "Image",   icon: "Image",         toolTip: "Image functions",      subMenu: this.imageTools(),       expertMode: false, toolType:DocumentType.IMG,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
-            { title: "Schema",  icon: "Schema",        toolTip: "Schema functions",     subMenu: this.schemaTools(),      expertMode: false, toolType:CollectionViewType.Schema,   funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, linearBtnWidth:58 }, // Only when Schema is selected
+            { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: this.webTools(),         expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_isOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
+            { title: "Video",   icon: "Video",         toolTip: "Video functions",      subMenu: this.videoTools(),       expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_isOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
+            { title: "Image",   icon: "Image",         toolTip: "Image functions",      subMenu: this.imageTools(),       expertMode: false, toolType:DocumentType.IMG,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_isOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
+            { title: "Schema",  icon: "Schema",        toolTip: "Schema functions",     subMenu: this.schemaTools(),      expertMode: false, toolType:CollectionViewType.Schema,   funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_isOpen: `SelectedDocType(this.toolType, this.expertMode)`}, linearView_btnWidth:58 }, // Only when Schema is selected
          ];
     }
 
@@ -872,7 +872,7 @@ pie title Minerals in my tap water
             ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit,
             color: Colors.WHITE, 
             _nativeWidth: params.width ?? 30, _width: params.width ?? 30,
-            _height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth,
+            _height: 30, _nativeHeight: 30, linearView_btnWidth: params.linearView_btnWidth,
             toolType: params.toolType, expertMode: params.expertMode,
             _dragOnlyWithinContainer: true, _lockedPosition: true,
             embedContainer: btnContainer
@@ -891,9 +891,9 @@ pie title Minerals in my tap water
             return this.setupContextMenuButton(params, menuBtnDoc, menuDoc);
         }  
         // linear view
-        const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]),
+        const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_isOpen"]),
             childDontRegisterViews: true, flexGap: 0, _height: 30, _width: 30, ignoreClick: !params.scripts?.onClick,
-            linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc};
+            linearView_expandable: true, embedContainer: menuDoc};
         
         const items = (menuBtn?:Doc) => !menuBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtn) );
         const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList;
@@ -905,7 +905,7 @@ pie title Minerals in my tap water
 
     /// 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: dropActionType.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);
@@ -932,7 +932,7 @@ pie title Minerals in my tap water
         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: dropActionType.move,
-            childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: false, ignoreClick: true
+            childDontRegisterViews: true, linearView_isOpen: true, linearView_expandable: false, ignoreClick: true
         };
         return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
     }
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index d8e0c4cbe..6461605a6 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -265,10 +265,10 @@ export function UPDATE_SERVER_CACHE() {
     cacheDocumentIds = newCacheUpdate;
 
     // print out cached docs
-    Doc.MyDockedBtns?.linearView_IsOpen && console.log('Set cached docs = ');
+    Doc.MyDockedBtns?.linearView_isOpen && console.log('Set cached docs = ');
     const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc));
     const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
-    Doc.MyDockedBtns?.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
+    Doc.MyDockedBtns?.linearView_isOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
 
     rp.post(ClientUtils.prepend('/setCacheDocumentIds'), {
         body: {
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 0136f6abe..253db08de 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -497,8 +497,8 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             {...this._props}
                             setHeight={undefined}
                             setContentViewBox={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
-                            yPadding={10}
-                            xPadding={10}
+                            yMargin={10}
+                            xMargin={10}
                             fieldKey="text"
                             // dontRegisterView={true}
                             noSidebar
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ad6bb09c7..c49b7e6de 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -173,7 +173,7 @@ export class MainView extends ObservableReactComponent<object> {
             views => views.length > 1 && document.activeElement instanceof HTMLElement && document.activeElement?.blur()
         );
         reaction(
-            () => Doc.MyDockedBtns?.linearView_IsOpen,
+            () => Doc.MyDockedBtns?.linearView_isOpen,
             open => SnappingManager.SetPrintToConsole(!!open)
         );
         const scriptTag = document.createElement('script');
diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx
index 2290516dc..cf4bf991e 100644
--- a/src/client/views/ObservableReactComponent.tsx
+++ b/src/client/views/ObservableReactComponent.tsx
@@ -16,15 +16,13 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, obj
         makeObservable(this);
     }
     __passiveWheel: HTMLElement | null = null;
-    _isContentActive: () => boolean | undefined = () => false;
+    __isContentActive: () => boolean | undefined = () => false;
 
     /**
      * default method to stop wheel events from bubbling up to parent components.
      * @param e
      */
-    onPassiveWheel = (e: WheelEvent) => {
-        if (this._isContentActive?.()) e.stopPropagation();
-    };
+    onPassiveWheel = (e: WheelEvent) => this.__isContentActive?.() && e.stopPropagation();
 
     /**
      * This fixes the problem where a component uses wheel events to scroll, but is nested inside another component that
@@ -36,7 +34,7 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, obj
      * @param onPassiveWheel an optional function to call to handle the wheel event (and block its propagation. If omitted, the event won't propagate.
      */
     fixWheelEvents = (ele: HTMLElement | null, isContentActive: () => boolean | undefined, onPassiveWheel?: (e: WheelEvent) => void) => {
-        this._isContentActive = isContentActive;
+        this.__isContentActive = isContentActive;
         this.__passiveWheel?.removeEventListener('wheel', onPassiveWheel ?? this.onPassiveWheel);
         this.__passiveWheel = ele;
         ele?.addEventListener('wheel', onPassiveWheel ?? this.onPassiveWheel, { passive: false });
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 28566ba1d..ded342df0 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -165,7 +165,7 @@ export class PropertiesButtons extends React.Component {
     //                 // containerDoc._forceActive =
     //                 //containerDoc._freeform_fitContentsToBox =
     //                 containerDoc._isLightbox = !containerDoc._isLightbox;
-    //                 //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
+    //                 //containerDoc._xMargin = containerDoc._yMargin = containerDoc._isLightbox ? 10 : undefined;
     //                 const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
     //                 //dv.Document.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' });
     //                 containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.layout_linkDisplay = false)));
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index d5edc3e0b..3dc7bc515 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -133,7 +133,7 @@ export class CollectionCardView extends CollectionSubView() {
     }
 
     @computed get yMargin() {
-        return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth()));
+        return this._props.yMargin || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth()));
     }
 
     @computed get cardDeckWidth() {
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index ac1981012..3c5bc10de 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -206,7 +206,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                             marginLeft: this.captionMarginX,
                             width: `calc(100% - ${this.captionMarginX * 2}px)`,
                         }}>
-                        <FormattedTextBox xPadding={10} yPadding={10} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={this.curDoc()} TemplateDataDocument={undefined} />
+                        <FormattedTextBox xMargin={10} yMargin={10} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={this.curDoc()} TemplateDataDocument={undefined} />
                     </div>
                 )}
             </>
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index be570564b..25a222cbb 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -89,7 +89,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth()));
     }
     @computed get yMargin() {
-        return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth()));
+        return this._props.yMargin || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth()));
     }
 
     @computed get gridGap() {
@@ -381,8 +381,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 hideDecorations={this._props.childHideDecorations}
                 childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                xPadding={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)}
-                yPadding={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)}
+                xMargin={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)}
+                yMargin={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)}
                 rejectDrop={this._props.childRejectDrop}
                 addDocument={this._props.addDocument}
                 moveDocument={this._props.moveDocument}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 4348bc7dc..c4373aaa7 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -163,8 +163,8 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
                     childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter}
                     searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
                     fitContentsToBox={returnTrue}
-                    xPadding={this.xPadding}
-                    yPadding={this.yPadding}
+                    xMargin={this.xPadding}
+                    yMargin={this.yPadding}
                 />
                 <div className="miniOverlay" onPointerDown={this.miniDown}>
                     <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index fb2d0955f..5b2f1ff81 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1042,8 +1042,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 disableBrushing={this.treeView._props.disableBrushing}
                 hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
                 dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
-                xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
-                yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
+                xMargin={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
+                yMargin={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
                 childFilters={returnEmptyFilter}
                 childFiltersByRanges={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
@@ -1148,8 +1148,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                 moveDocument={this.move}
                 removeDocument={this._props.removeDoc}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
-                xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
-                yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
+                xMargin={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
+                yMargin={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
                 addDocTab={this._props.addDocTab}
                 pinToPres={this.treeView._props.pinToPres}
                 disableBrushing={this.treeView._props.disableBrushing}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2364fd74a..05958e6b9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -256,8 +256,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     override contentBounds = () => {
         const { x, y, r, b } = aggregateBounds(
             this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
-            NumCast(this.layoutDoc._xPadding, NumCast(this.layoutDoc._xMargin, this._props.xPadding ?? 0)),
-            NumCast(this.layoutDoc._yPadding, NumCast(this.layoutDoc._yMargin, this._props.yPadding ?? 0))
+            NumCast(this.layoutDoc._xMargin, this._props.xMargin ?? 0),
+            NumCast(this.layoutDoc._yMargin, this._props.yMargin ?? 0)
         );
         const [width, height] = [r - x, b - y];
         return {
@@ -1775,7 +1775,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     () => {
                         if (this.Document.isGroup && this.childDocs.length === this.childDocList?.length) {
                             const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: NumCast(cd._width), height: NumCast(cd._height) }));
-                            return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
+                            return aggregateBounds(clist, NumCast(this.layoutDoc._xMargin), NumCast(this.layoutDoc._yMargin));
                         }
                         return undefined;
                     },
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 1d5e70be7..3320eacb6 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -58,7 +58,7 @@ export class CollectionGridView extends CollectionSubView() {
         return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth()));
     }
     @computed get yMargin() {
-        return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth()));
+        return this._props.yMargin || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth()));
     }
     @computed get gridGap() {
         return NumCast(this.Document._gridGap, 10);
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 3c2a99b1e..d0a1e6f0d 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -27,7 +27,7 @@ import './CollectionLinearView.scss';
 /**
  * CollectionLinearView is the class for rendering the horizontal collection
  * of documents, it useful for horizontal menus. It can either be expandable
- * or not using the linearView_Expandable field.
+ * or not using the linearView_expandable field.
  * It is used in the following locations:
  * - It is used in the popup menu on the bottom left (see docButtons() in MainView.tsx)
  * - It is used for the context sensitive toolbar at the top (see contMenuButtons() in CollectionMenu.tsx)
@@ -52,7 +52,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
     componentDidMount() {
         this._widthDisposer = reaction(
-            () => 5 + NumCast(this.dataDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0),
+            () => 5 + NumCast(this.dataDoc.linearView_btnWidth, this.dimension()) + (this.layoutDoc.linearView_isOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0),
             width => {
                 this.childDocs.length && (this.layoutDoc._width = width);
             },
@@ -208,7 +208,7 @@ export class CollectionLinearView extends CollectionSubView() {
     render() {
         const flexDir = StrCast(this.Document.flexDirection); // Specify direction of linear view content
         const flexGap = NumCast(this.Document.flexGap); // Specify the gap between linear view content
-        const isExpanded = BoolCast(this.layoutDoc.linearView_IsOpen);
+        const isExpanded = BoolCast(this.layoutDoc.linearView_isOpen);
 
         const menuOpener = (
             <Toggle
@@ -219,9 +219,9 @@ export class CollectionLinearView extends CollectionSubView() {
                 type={Type.TERT}
                 onPointerDown={e => e.stopPropagation()}
                 toggleType={ToggleType.BUTTON}
-                toggleStatus={BoolCast(this.layoutDoc.linearView_IsOpen)}
+                toggleStatus={BoolCast(this.layoutDoc.linearView_isOpen)}
                 onClick={() => {
-                    this.layoutDoc.linearView_IsOpen = !isExpanded;
+                    this.layoutDoc.linearView_isOpen = !isExpanded;
                     ScriptCast(this.Document.onClick)?.script.run({ this: this.Document }, console.log);
                 }}
                 tooltip={isExpanded ? 'Close' : 'Open'}
@@ -231,10 +231,10 @@ export class CollectionLinearView extends CollectionSubView() {
         );
 
         return (
-            <div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsOpen ? undefined : 'transparent' }}>
+            <div className="collectionLinearView-outer" style={{ backgroundColor: this.layoutDoc.linearView_isOpen ? undefined : 'transparent' }}>
                 <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}>
-                    {!this.layoutDoc.linearView_Expandable ? null : menuOpener}
-                    {!this.layoutDoc.linearView_IsOpen ? null : (
+                    {!this.layoutDoc.linearView_expandable ? null : menuOpener}
+                    {!this.layoutDoc.linearView_isOpen && this.layoutDoc.linearView_expandable ? null : (
                         <div
                             className="collectionLinearView-content"
                             style={{
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 284014e54..d756f3226 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -913,8 +913,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                 }}>
                 <FormattedTextBox
                     {...this._props}
-                    yPadding={10}
-                    xPadding={10}
+                    yMargin={10}
+                    xMargin={10}
                     fieldKey={this.showCaption}
                     styleProvider={this.captionStyleProvider}
                     dontRegisterView
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index a6872f8dc..f6b405a43 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -52,8 +52,8 @@ export interface FieldViewSharedProps {
     renderDepth: number;
     scriptContext?: unknown; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
     screenXPadding?: (view: DocumentView | undefined) => number; // padding in screen space coordinates (used by text box to reflow around UI buttons in carouselView)
-    xPadding?: number;
-    yPadding?: number;
+    xMargin?: number;
+    yMargin?: number;
     dontRegisterView?: boolean;
     dropAction?: dropActionType;
     dragAction?: dropActionType;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index b08ed84b7..f901c32fc 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -181,10 +181,10 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         fontFamily: StrCast(this.layoutDoc._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)) || 'inherit',
                         letterSpacing: StrCast(this.layoutDoc.letterSpacing),
                         textTransform: StrCast(this.layoutDoc[this.fieldKey + '_transform']) as Property.TextTransform,
-                        paddingLeft: NumCast(this.layoutDoc._xPadding),
-                        paddingRight: NumCast(this.layoutDoc._xPadding),
-                        paddingTop: NumCast(this.layoutDoc._yPadding),
-                        paddingBottom: NumCast(this.layoutDoc._yPadding),
+                        paddingLeft: NumCast(this.layoutDoc._xMargin),
+                        paddingRight: NumCast(this.layoutDoc._xMargin),
+                        paddingTop: NumCast(this.layoutDoc._yMargin),
+                        paddingBottom: NumCast(this.layoutDoc._yMargin),
                         width: this._props.PanelWidth(),
                         height: this._props.PanelHeight(),
                         whiteSpace: boxParams.multiLine ? 'pre-wrap' : 'pre',
@@ -192,8 +192,8 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     <div
                         key={this._forceRerender}
                         style={{
-                            width: this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xPadding),
-                            height: this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yPadding),
+                            width: this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin),
+                            height: this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin),
                             outline: 'unset !important',
                         }}
                         onKeyDown={e => {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 83c44c80f..55e6d5596 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -557,8 +557,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         NativeHeight={this.sidebarNativeHeightFunc}
                         PanelHeight={this._props.PanelHeight}
                         PanelWidth={this.sidebarWidth}
-                        xPadding={0}
-                        yPadding={0}
+                        xMargin={0}
+                        yMargin={0}
                         viewField={this.SidebarKey}
                         isAnnotationOverlay={false}
                         originTopLeft
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 4f02d68d6..603dcad5c 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -335,8 +335,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                                 select={emptyFunction}
                                 isContentActive={emptyFunction}
                                 NativeDimScaling={returnOne}
-                                xPadding={25}
-                                yPadding={10}
+                                xMargin={25}
+                                yMargin={10}
                                 whenChildContentsActiveChanged={emptyFunction}
                                 removeDocument={returnFalse}
                                 moveDocument={returnFalse}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c51f6c38b..abf44b54d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1164,7 +1164,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     };
 
     @computed get tagsHeight() {
-        return this.DocumentView?.().showTags ? Math.max(0, 20 - Math.max(this._props.yPadding ?? 0, NumCast(this.layoutDoc._yMargin))) * this.ScreenToLocalBoxXf().Scale : 0;
+        return this.DocumentView?.().showTags ? Math.max(0, 20 - Math.max(this._props.yMargin ?? 0, NumCast(this.layoutDoc._yMargin))) * this.ScreenToLocalBoxXf().Scale : 0;
     }
 
     @computed get contentScaling() {
@@ -1880,7 +1880,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         }
     };
     tryUpdateScrollHeight = () => {
-        const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
+        const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yMargin || 0);
         const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
         if (this.EditorView && children && !SnappingManager.IsDragging) {
             const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
@@ -2112,8 +2112,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const rounded = StrCast(this.layoutDoc._layout_borderRounding) === '100%' ? '-rounded' : '';
         setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
 
-        const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), 0, this._props.xPadding ?? 0, this._props.screenXPadding?.(this._props.DocumentView?.()) ?? 0);
-        const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, this._props.yPadding ?? 0); // prettier-ignore
+        const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), 0, this._props.xMargin ?? 0, this._props.screenXPadding?.(this._props.DocumentView?.()) ?? 0);
+        const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, this._props.yMargin ?? 0); // prettier-ignore
         const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._header_height}px' >
         return this.isLabel ? (
             <LabelBox {...this._props} />
diff --git a/src/client/views/nodes/trails/PresSlideBox.tsx b/src/client/views/nodes/trails/PresSlideBox.tsx
index 3dbb3da88..3440b29dd 100644
--- a/src/client/views/nodes/trails/PresSlideBox.tsx
+++ b/src/client/views/nodes/trails/PresSlideBox.tsx
@@ -559,10 +559,10 @@ export class PresSlideBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 style={{
                     backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
                     opacity: this._dragging ? 0.3 : 1,
-                    paddingLeft: NumCast(this.layoutDoc._xPadding, this._props.xPadding),
-                    paddingRight: NumCast(this.layoutDoc._xPadding, this._props.xPadding),
-                    paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
-                    paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
+                    paddingLeft: NumCast(this.layoutDoc._xMargin, this._props._xMargin),
+                    paddingRight: NumCast(this.layoutDoc._xMargin, this._props._xMargin),
+                    paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yMargin),
+                    paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yMargin),
                 }}
                 onDoubleClick={action(() => {
                     this.toggleProperties();
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index b27816f55..df832d088 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -35,8 +35,6 @@ export const documentSchema = createSchema({
     _nativeHeight: 'number', // "
     _width: 'number', // width of document in its container's coordinate system
     _height: 'number', // "
-    _xPadding: 'number', // pixels of padding on left/right of collectionfreeformview contents when freeform_fitContentsToBox is set
-    _yPadding: 'number', // pixels of padding on top/bottom of collectionfreeformview contents when freeform_fitContentsToBox is set
     _xMargin: 'number', // margin added on left/right of most documents to add separation from their container
     _yMargin: 'number', // margin added on top/bottom of most documents to add separation from their container
     _overflow: 'string', // sets overflow behvavior for CollectionFreeForm views
-- 
cgit v1.2.3-70-g09d2


From d0cbb43ec3fa19f76a570f5e0038bfc72c9f37b9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 7 May 2025 11:21:53 -0400
Subject: fixed dont_center on Docs in gridview.  fixed ai view screentolocal
 in docView, and made ai view more streamlined.  got rid of ai history sidebar
 in images.   fixed imageeditor to use a mask that doesn't show the original
 conctents.  fixed gptpopup scrolling, turning off of spinner, and improved
 ability to filter Docs

---
 src/client/views/ViewBoxInterface.ts               |   1 -
 .../collectionGrid/CollectionGridView.tsx          |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  14 +-
 src/client/views/nodes/DocumentView.scss           |   3 +-
 src/client/views/nodes/DocumentView.tsx            |  59 ++++----
 src/client/views/nodes/IconTagBox.tsx              |  24 ++--
 src/client/views/nodes/ImageBox.scss               |   2 +-
 src/client/views/nodes/ImageBox.tsx                |  96 ++++---------
 src/client/views/nodes/imageEditor/ImageEditor.tsx |   5 +-
 .../imageEditor/imageEditorUtils/ImageHandler.ts   |   6 +-
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |   7 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         | 149 +++++++++++----------
 src/client/views/smartdraw/SmartDrawHandler.tsx    |   2 +-
 13 files changed, 168 insertions(+), 202 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts
index 0ddac8914..d8dab8e89 100644
--- a/src/client/views/ViewBoxInterface.ts
+++ b/src/client/views/ViewBoxInterface.ts
@@ -64,5 +64,4 @@ export abstract class ViewBoxInterface<P> extends ObservableReactComponent<React
     dontRegisterView?: () => boolean; // KeyValueBox's don't want to register their views
     isUnstyledView?: () => boolean; // SchemaView and KeyValue are unstyled -- not titles, no opacity, no animations
     componentAIView?: () => JSX.Element;
-    componentAIViewHistory?: () => JSX.Element;
 }
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 3320eacb6..b837b3a86 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -206,7 +206,7 @@ export class CollectionGridView extends CollectionSubView() {
             setContentViewBox={emptyFunction}
             whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
             onClickScript={this.onChildClickHandler}
-            dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'}
+            dontCenter={StrCast(this.layoutDoc.layout_dontCenter, StrCast(childLayout.layout_dontCenter)) as 'x' | 'y' | 'xy'}
             showTags={BoolCast(this.layoutDoc.showChildTags) || BoolCast(this.Document._layout_showTags)}
         />
     );
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 940c4cb99..3805b0dca 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -148,7 +148,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static getValues(doc: Doc, time: number, fillIn: boolean = true) {
         return CollectionFreeFormDocumentView.animFields.reduce(
             (p, val) => {
-                p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce(
+                p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : [])!.reduce(
                     (prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev),
                     undefined as unknown as number
                 );
@@ -161,7 +161,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static getStringValues(doc: Doc, time: number) {
         return CollectionFreeFormDocumentView.animStringFields.reduce(
             (p, val) => {
-                p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as unknown as string);
+                p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])])!.reduce((prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as unknown as string);
                 return p;
             },
             {} as { [val: string]: Opt<string> }
@@ -171,7 +171,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static setStringValues(time: number, d: Doc, vals: { [val: string]: Opt<string> }) {
         const timecode = Math.round(time);
         Object.keys(vals).forEach(val => {
-            const findexed = Cast(d[`${val}_indexed`], listSpec('string'), []).slice();
+            const findexed = Cast(d[`${val}_indexed`], listSpec('string'), [])!.slice();
             findexed[timecode] = vals[val] || '';
             d[`${val}_indexed`] = new List<string>(findexed);
         });
@@ -180,7 +180,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) {
         const timecode = Math.round(time);
         Object.keys(vals).forEach(val => {
-            const findexed = Cast(d[`${val}_indexed`], listSpec('number'), []).slice();
+            const findexed = Cast(d[`${val}_indexed`], listSpec('number'), [])!.slice();
             findexed[timecode] = vals[val] as unknown as number;
             d[`${val}_indexed`] = new List<number>(findexed);
         });
@@ -204,15 +204,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         docs.forEach(doc => {
             this.animFields.forEach(val => {
                 const findexed = Cast(doc[`${val.key}_indexed`], listSpec('number'), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as unknown as number);
+                (findexed?.length ?? 0) <= timecode + 1 && findexed?.push(undefined as unknown as number);
             });
             this.animStringFields.forEach(val => {
                 const findexed = Cast(doc[`${val}_indexed`], listSpec('string'), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as unknown as string);
+                (findexed?.length ?? 0) <= timecode + 1 && findexed?.push(undefined as unknown as string);
             });
             this.animDataFields(doc).forEach(val => {
                 const findexed = Cast(doc[`${val}_indexed`], listSpec(InkField), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as unknown as InkField);
+                (findexed?.length ?? 0) <= timecode + 1 && findexed?.push(undefined as unknown as InkField);
             });
         });
         return newTimer;
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 5ac66f2cd..c4351a200 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -119,6 +119,7 @@
         display: flex;
         justify-content: center;
         align-items: center;
+        margin: auto;
         position: relative; // allows contents to be positioned relative/below title
         > .formattedTextBox {
             position: absolute; // position a child text box
@@ -302,6 +303,6 @@
     background: transparent;
 
     .documentView-editorView-resizer {
-        height: 5px;
+        height: 2px;
     }
 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d756f3226..bdb97d7bb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -648,11 +648,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
 
     rootSelected = () => this._rootSelected;
     panelHeight = () => this._props.PanelHeight() - this.headerMargin - 2 * NumCast(this.Document.borderWidth);
-    screenToLocalContent = () =>
-        this._props
-            .ScreenToLocalTransform()
-            .translate(-NumCast(this.Document.borderWidth), -this.headerMargin - NumCast(this.Document.borderWidth))
-            .scale(this.viewingAiEditor() ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1);
+    aiShift = () => (!this.viewingAiEditor() ? 0 : (this._props.PanelWidth() - this.aiContentsWidth()) / 2);
+    aiScale = () => (this.viewingAiEditor() ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1);
     onClickFunc = () => (this.disableClickScriptFunc ? undefined : this.onClickHdlr);
     setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height + 2 * NumCast(this.Document.borderWidth))); } // prettier-ignore
     setContentView = action((view: ViewBoxInterface<FieldViewProps>) => (this._componentView = view));
@@ -678,7 +675,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
         return this._props.styleProvider?.(doc, props, property);
     };
 
-    @observable _aiWinHeight = 88;
+    @observable _aiWinHeight = 32;
 
     TagsBtnHeight = 22;
     @computed get currentScale() {
@@ -703,33 +700,21 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
     @computed get uiBtnScaling() {  return Math.max(this.maxWidgetSize / this.TagsBtnHeight, 1) * Math.min(1, this.viewScaling); } // prettier-ignore
 
     aiContentsWidth = () => (this.aiContentsHeight() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1);
-    aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight * this.uiBtnScaling);
+    aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - (this._aiWinHeight + (this.tagsOverlayFunc() ? 22 : 0)) * this.uiBtnScaling);
     @computed get aiEditor() {
         return (
-            <>
-                <div
-                    className="documentView-editorView-history"
-                    ref={r => this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))}
-                    style={{
-                        transform: `scale(${this.uiBtnScaling})`,
-                        height: this.aiContentsHeight() / this.uiBtnScaling,
-                        width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling,
-                    }}>
-                    {this._componentView?.componentAIViewHistory?.() ?? null}
-                </div>
-                <div
-                    className="documentView-editorView"
-                    style={{
-                        background: SnappingManager.userVariantColor,
-                        width: `${100 / this.uiBtnScaling}%`, //
-                        transform: `scale(${this.uiBtnScaling})`,
-                    }}
-                    ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
-                    <div className="documentView-editorView-resizer" />
-                    {this._componentView?.componentAIView?.() ?? null}
-                    {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
-                </div>
-            </>
+            <div
+                className="documentView-editorView"
+                style={{
+                    background: SnappingManager.userVariantColor,
+                    width: `${100 / this.uiBtnScaling}%`, //
+                    transform: `scale(${this.uiBtnScaling})`,
+                }}
+                ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
+                <div className="documentView-editorView-resizer" />
+                {this._componentView?.componentAIView?.() ?? null}
+                {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
+            </div>
         );
     }
     @computed get tagsOverlay() {
@@ -780,7 +765,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                         PanelHeight={this.viewingAiEditor() ? this.aiContentsHeight : this.panelHeight}
                         setHeight={this.setHeight}
                         isContentActive={this.isContentActive}
-                        ScreenToLocalTransform={this.screenToLocalContent}
                         rootSelected={this.rootSelected}
                         onClickScript={this.onClickFunc}
                         setTitleFocus={this.setTitleFocus}
@@ -1291,7 +1275,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         if (this.ComponentView?.screenBounds?.()) {
             return this.ComponentView.screenBounds();
         }
-        const xf = this.screenToContentsTransform().scale(this.nativeScaling).inverse();
+        const xf = this.screenToContentBoundsTransform().inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
 
         // transition is returned so that the bounds will 'update' at the end of an animated transition.  This is needed by xAnchor in LinkBox
@@ -1498,9 +1482,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     isHovering = () => this._isHovering;
     selfView = () => this;
     /**
-     * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView)
+     * @returns Transform to the document view's available panel space (in the coordinate system of whatever contains the DocumentView)
      */
     screenToViewTransform = () => this._props.ScreenToLocalTransform();
+    /**
+     * @returns Transform to the document view after centering in available panel space(in the coordinate system of whatever contains the DocumentView)
+     */
+    private screenToContentBoundsTransform = () => this.screenToViewTransform().translate(-this.centeringX, -this.centeringY);
     /**
      * @returns Transform to the coordinate system of the contents of the document view (includes native dimension scaling and centering)
      */
@@ -1508,7 +1496,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         this._props
             .ScreenToLocalTransform()
             .translate(-this.centeringX, -this.centeringY)
-            .scale(1 / this.nativeScaling);
+            .translate(-(this._docViewInternal?.aiShift() ?? 0), 0)
+            .scale((this._docViewInternal?.aiScale() ?? 1) / this.nativeScaling);
 
     htmlOverlay = () => {
         const effect = StrCast(this._htmlOverlayEffect?.presentation_effect, StrCast(this._htmlOverlayEffect?.followLinkAnimEffect));
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index 0bbd6a0d3..d04ec3a10 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -58,16 +58,20 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
         const tag = StrCast(key.toolType);
         const color = dv._props.styleProvider?.(dv.layoutDoc, dv.ComponentView?._props, StyleProp.FontColor) as string;
         return (
-            <Toggle
-                tooltip={`Click to add/remove the tag ${tag}`}
-                toggleStatus={TagItem.docHasTag(dv.Document, tag)}
-                toggleType={ToggleType.BUTTON}
-                icon={<FontAwesomeIcon className={`fontIconBox-icon-${ToggleType.BUTTON}`} icon={icon} color={color} />}
-                size={Size.XSMALL}
-                type={Type.PRIM}
-                onClick={() => this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag))}
-                color={color}
-            />
+            <div>
+                {' '}
+                {/* tooltips require the wrapped item to be an element ref */}
+                <Toggle
+                    tooltip={`Click to add/remove the tag ${tag}`}
+                    toggleStatus={TagItem.docHasTag(dv.Document, tag)}
+                    toggleType={ToggleType.BUTTON}
+                    icon={<FontAwesomeIcon className={`fontIconBox-icon-${ToggleType.BUTTON}`} icon={icon} color={color} />}
+                    size={Size.XSMALL}
+                    type={Type.PRIM}
+                    onClick={() => this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag))}
+                    color={color}
+                />
+            </div>
         );
     };
 
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 9f7a5d03f..5a6292fab 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -236,7 +236,7 @@
     .imageBox-aiView-input {
         overflow: hidden;
         text-overflow: ellipsis;
-        max-width: 65%;
+        max-width: 80%;
         width: 100%;
         color: black;
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d16baada6..bf6915570 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,6 +1,6 @@
-import { Button, Colors, EditableText, IconButton, Size, Toggle, ToggleType, Type } from '@dash/components';
+import { Button, Colors, EditableText, IconButton, NumberDropdown, Size, Toggle, ToggleType, Type } from '@dash/components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Slider, Tooltip } from '@mui/material';
+import { Tooltip } from '@mui/material';
 import axios from 'axios';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -37,7 +37,6 @@ import { OverlayView } from '../OverlayView';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PinDocView, PinProps } from '../PinFuncs';
 import { DrawingFillHandler } from '../smartdraw/DrawingFillHandler';
-import { FireflyImageData, isFireflyImageData } from '../smartdraw/FireflyConstants';
 import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler';
 import { StickerPalette } from '../smartdraw/StickerPalette';
 import { StyleProp } from '../StyleProp';
@@ -102,7 +101,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable private _regenInput = '';
     @observable private _canInteract = true;
     @observable private _regenerateLoading = false;
-    @observable private _prevImgs: FireflyImageData[] = StrCast(this.Document.ai_firefly_history) ? JSON.parse(StrCast(this.Document.ai_firefly_history)) : [];
 
     // Add these observable properties to the ImageBox class
     @observable private _outpaintingInProgress = false;
@@ -845,34 +843,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable _filterFunc: ((doc: Doc) => boolean) | undefined = undefined;
     @observable private _fireflyRefStrength = 0;
 
-    componentAIViewHistory = () => (
-        <div className="imageBox-aiView-history">
-            <Button text="Clear History" type={Type.SEC} size={Size.XSMALL} />
-            {this._prevImgs.map(img => (
-                <div key={img.pathname}>
-                    <img
-                        className="imageBox-aiView-img"
-                        src={ClientUtils.prepend(img.pathname.replace(extname(img.pathname), '_s' + extname(img.pathname)))}
-                        onClick={() => {
-                            this.dataDoc[this.fieldKey] = new ImageField(img.pathname);
-                            this.dataDoc.ai_firefly_prompt = img.prompt;
-                            this.dataDoc.ai_firefly_seed = img.seed;
-                        }}
-                    />
-                    <span>{img.prompt}</span>
-                </div>
-            ))}
-        </div>
-    );
-
     componentAIView = () => {
-        const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey]));
         return (
             <div className="imageBox-aiView">
                 <div className="imageBox-aiView-regenerate">
-                    <span className="imageBox-aiView-firefly" style={{ color: SnappingManager.userColor }}>
-                        Firefly:
-                    </span>
                     <input
                         style={{ color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
                         className="imageBox-aiView-input"
@@ -886,57 +860,39 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         <Button
                             text="Create"
                             type={Type.TERT}
+                            size={Size.XSMALL}
                             color={SnappingManager.userColor}
                             background={SnappingManager.userBackgroundColor}
                             // style={{ alignSelf: 'flex-end' }}
                             icon={this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
                             iconPlacement="right"
-                            onClick={action(async () => {
+                            onClick={action(() => {
                                 this._regenerateLoading = true;
-                                if (this._fireflyRefStrength) {
-                                    DrawingFillHandler.drawingToImage(this.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title))?.then(action(() => (this._regenerateLoading = false)));
-                                } else {
-                                    SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput || StrCast(this.Document.title), true).then(
-                                        action(newImgs => {
-                                            const firstImg = newImgs[0];
-                                            if (isFireflyImageData(firstImg)) {
-                                                const url = firstImg.pathname;
-                                                const imgField = new ImageField(url);
-                                                this._prevImgs.length === 0 &&
-                                                    this._prevImgs.push({ prompt: StrCast(this.dataDoc.ai_firefly_prompt), seed: this.dataDoc.ai_firefly_seed as number, href: this.paths.lastElement(), pathname: field?.url.pathname ?? '' });
-                                                this._prevImgs.unshift({ prompt: firstImg.prompt, seed: firstImg.seed, pathname: url });
-                                                this.dataDoc.ai_firefly_history = JSON.stringify(this._prevImgs);
-                                                this.dataDoc.ai_firefly_prompt = firstImg.prompt;
-                                                this.dataDoc[this.fieldKey] = imgField;
-                                                this._regenerateLoading = false;
-                                                this._regenInput = '';
-                                            }
-                                        })
-                                    );
-                                }
+                                DrawingFillHandler.drawingToImage(this.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title))?.then(action(() => (this._regenerateLoading = false)));
                             })}
                         />
                     </div>
-                </div>
-                <div className="imageBox-aiView-strength">
-                    <span className="imageBox-aiView-similarity" style={{ color: SnappingManager.userColor }}>
-                        Similarity
-                    </span>
-                    <Slider
-                        className="imageBox-aiView-slider"
-                        sx={{
-                            '& .MuiSlider-track': { color: SettingsManager.userColor },
-                            '& .MuiSlider-rail': { color: SettingsManager.userBackgroundColor },
-                            '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } },
-                        }}
-                        min={0}
-                        max={100}
-                        step={1}
-                        size="small"
-                        value={this._fireflyRefStrength}
-                        onChange={action((e, val) => this._canInteract && (this._fireflyRefStrength = val as number))}
-                        valueLabelDisplay="auto"
-                    />
+                    <div>
+                        <NumberDropdown
+                            color={SnappingManager.userColor}
+                            background={SnappingManager.userBackgroundColor}
+                            numberDropdownType="slider"
+                            showPlusMinus={false}
+                            formLabel="similarity"
+                            tooltip="structure similarity of created images to current image"
+                            type={Type.PRIM}
+                            width={75}
+                            min={0}
+                            max={100}
+                            number={this._fireflyRefStrength}
+                            size={Size.XXSMALL}
+                            setNumber={undoable(
+                                action(val => this._canInteract && (this._fireflyRefStrength = val as number)),
+                                `${this.Document.title} button set from list`
+                            )}
+                            fillWidth
+                        />
+                    </div>
                 </div>
             </div>
         );
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 85bd95d15..198b8e713 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -281,11 +281,14 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         try {
             const canvasOriginalImg = ImageUtility.getCanvasImg(img);
             if (!canvasOriginalImg) return;
-            const canvasMask = ImageUtility.getCanvasMask(canvas, canvasOriginalImg);
+            const canvasMask = ImageUtility.getCanvasMask(canvas, canvas);
             if (!canvasMask) return;
             const maskBlob = await ImageUtility.canvasToBlob(canvasMask);
             const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg);
             const res = await ImageUtility.getEdit(imgBlob, maskBlob, input || 'Fill in the image in the same style', 2);
+            if ((res as any).status == 'error') {
+                alert((res as any).message);
+            }
 
             // create first image
             if (!newCollectionRef.current) {
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts
index 1c6a38a24..d6093c6eb 100644
--- a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts
@@ -75,7 +75,7 @@ export class ImageUtility {
         fd.append('mask', maskBlob, 'mask.png');
         fd.append('prompt', prompt);
         fd.append('size', '1024x1024');
-        fd.append('n', n ? JSON.stringify(n) : '1');
+        fd.append('n', n ? n + '' : '1');
         fd.append('response_format', 'b64_json');
 
         try {
@@ -268,14 +268,14 @@ export class ImageUtility {
             ctx.drawImage(img, xOffset, 0, width, height);
 
             // draw reflected image padding
-            this.drawHorizontalReflection(ctx, canvas, xOffset);
+            // this.drawHorizontalReflection(ctx, canvas, xOffset);
         } else {
             // vertical padding, y offset
             const yOffset = Math.floor((canvasSize - height) / 2);
             ctx.drawImage(img, 0, yOffset, width, height);
 
             // draw reflected image padding
-            this.drawVerticalReflection(ctx, canvas, yOffset);
+            //  this.drawVerticalReflection(ctx, canvas, yOffset);
         }
         return canvas;
     };
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index bb43291ee..f6fa45221 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -7,6 +7,11 @@ $highlightedText: #82e0ff;
 $inputHeight: 60px;
 $headingHeight: 32px;
 
+.gptPopup-sortBox {
+    display: block;
+    max-height: calc(100% - 45px); // leave room for input
+}
+
 .gptPopup-summary-box {
     position: fixed;
     padding-left: 10px;
@@ -87,6 +92,7 @@ $headingHeight: 32px;
     }
     .btns-wrapper-gpt {
         height: 100%;
+        width: 100%;
         display: flex;
         justify-content: center;
         align-items: center;
@@ -97,7 +103,6 @@ $headingHeight: 32px;
             flex-direction: column;
             width: 100%;
             height: 100%;
-            overflow-y: auto;
             padding-right: 5px;
         }
 
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index c45d8e052..9fbae5c90 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -93,7 +93,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
                     this.onGptResponse = (sortResult: string, questionType: GPTDocCommand, args?: string) => this.processGptResponse(selDoc, this._textToDocMap, sortResult, questionType, args);
                     this.onQuizRandom = () => this.randomlyChooseDoc(selDoc.Document, hasChildDocs());
                     this._documentDescriptions = Promise.all(hasChildDocs().map(doc =>
-                            Doc.getDescription(doc).then(text => this._textToDocMap.set(text.trim(), doc) && `${DescriptionSeperator}${text}${DescriptionSeperator}`)
+                            Doc.getDescription(doc).then(text => this._textToDocMap.set(text.replace(/\n/g, ' ').trim(), doc) && `${DescriptionSeperator}${text}${DescriptionSeperator}`)
                         )).then(docDescriptions => docDescriptions.join()); // prettier-ignore
                 }
             },
@@ -406,73 +406,80 @@ export class GPTPopup extends ObservableReactComponent<object> {
     scrollToBottom = () => setTimeout(() => this._messagesEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }), 50);
 
     gptMenu = () => (
-        <div className="btns-wrapper-gpt">
-            <Button
-                tooltip="Ask Firefly to create images"
-                text="Ask Firefly"
-                onClick={() => this.setMode(GPTPopupMode.FIREFLY)}
-                color={SettingsManager.userColor}
-                background={SettingsManager.userVariantColor}
-                type={Type.TERT}
-                style={{
-                    width: '100%',
-                    height: '40%',
-                    textAlign: 'center',
-                    color: '#ffffff',
-                    fontSize: '16px',
-                    marginBottom: '10px',
-                }}
-            />
-            <Button
-                tooltip="Ask GPT to sort, tag, define, or filter your Docs!"
-                text="Ask GPT"
-                onClick={() => this.setMode(GPTPopupMode.USER_PROMPT)}
-                color={SettingsManager.userColor}
-                background={SettingsManager.userVariantColor}
-                type={Type.TERT}
-                style={{
-                    width: '100%',
-                    height: '40%',
-                    textAlign: 'center',
-                    color: '#ffffff',
-                    fontSize: '16px',
-                    marginBottom: '10px',
-                }}
-            />
-            <Button
-                tooltip="Test your knowledge by verifying answers with ChatGPT"
-                text="Take Quiz"
-                onClick={() => {
-                    this._conversationArray = ['Define the selected card!'];
-                    this.setMode(GPTPopupMode.QUIZ_RESPONSE);
-                    this.onQuizRandom?.();
-                }}
-                color={SettingsManager.userColor}
-                background={SettingsManager.userVariantColor}
-                type={Type.TERT}
-                style={{
-                    width: '100%',
-                    height: '40%',
-                    textAlign: 'center',
-                    color: '#ffffff',
-                    fontSize: '16px',
-                }}
-            />
+        <div style={{ display: 'flex', maxHeight: 'calc(100% - 32px)', overflow: 'auto' }}>
+            <div className="btns-wrapper-gpt">
+                <Button
+                    tooltip="Ask Firefly to create images"
+                    text="Ask Firefly"
+                    onClick={() => this.setMode(GPTPopupMode.FIREFLY)}
+                    color={SettingsManager.userColor}
+                    background={SettingsManager.userVariantColor}
+                    type={Type.TERT}
+                    style={{
+                        width: '100%',
+                        height: '40%',
+                        textAlign: 'center',
+                        color: '#ffffff',
+                        fontSize: '16px',
+                        marginBottom: '10px',
+                    }}
+                />
+                <Button
+                    tooltip="Ask GPT to sort, tag, define, or filter your Docs!"
+                    text="Ask GPT"
+                    onClick={() => this.setMode(GPTPopupMode.USER_PROMPT)}
+                    color={SettingsManager.userColor}
+                    background={SettingsManager.userVariantColor}
+                    type={Type.TERT}
+                    style={{
+                        width: '100%',
+                        height: '40%',
+                        textAlign: 'center',
+                        color: '#ffffff',
+                        fontSize: '16px',
+                        marginBottom: '10px',
+                    }}
+                />
+                <Button
+                    tooltip="Test your knowledge by verifying answers with ChatGPT"
+                    text="Take Quiz"
+                    onClick={() => {
+                        this._conversationArray = ['Define the selected card!'];
+                        this.setMode(GPTPopupMode.QUIZ_RESPONSE);
+                        this.onQuizRandom?.();
+                    }}
+                    color={SettingsManager.userColor}
+                    background={SettingsManager.userVariantColor}
+                    type={Type.TERT}
+                    style={{
+                        width: '100%',
+                        height: '40%',
+                        textAlign: 'center',
+                        color: '#ffffff',
+                        fontSize: '16px',
+                    }}
+                />
+            </div>
         </div>
     );
 
     callGpt = action((mode: GPTPopupMode) => {
         this.setGptProcessing(true);
+        const reset = action(() => {
+            this.setGptProcessing(false);
+            this._userPrompt = '';
+            this._quizAnswer = '';
+        });
         switch (mode) {
             case GPTPopupMode.FIREFLY:
                 this._fireflyArray.push(this._userPrompt);
-                return this.generateFireflyImage(this._userPrompt).then(action(() => (this._userPrompt = '')));
+                return this.generateFireflyImage(this._userPrompt).then(reset);
             case GPTPopupMode.USER_PROMPT:
                 this._conversationArray.push(this._userPrompt);
-                return this.generateUserPromptResponse(this._userPrompt).then(action(() => (this._userPrompt = '')));
+                return this.generateUserPromptResponse(this._userPrompt).then(reset);
             case GPTPopupMode.QUIZ_RESPONSE:
                 this._conversationArray.push(this._quizAnswer);
-                return this.generateQuizAnswerAnalysis(DocumentView.SelectedDocs().lastElement(), this._quizAnswer).then(action(() => (this._quizAnswer = '')));
+                return this.generateQuizAnswerAnalysis(DocumentView.SelectedDocs().lastElement(), this._quizAnswer).then(reset);
         }
     });
 
@@ -490,18 +497,20 @@ export class GPTPopup extends ObservableReactComponent<object> {
     };
 
     gptUserInput = () => (
-        <div className="btns-wrapper-gpt">
-            <div className="chat-wrapper">
-                <div className="chat-bubbles">
-                    {(this._mode === GPTPopupMode.FIREFLY ? this._fireflyArray : this._conversationArray).map((message, index) => (
-                        <div key={index} className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`}>
-                            {message}
-                        </div>
-                    ))}
-                    {this._gptProcessing && <div className="chat-bubble chat-message">...</div>}
-                </div>
+        <div style={{ display: 'flex', maxHeight: 'calc(100% - 32px)', overflow: 'auto' }}>
+            <div className="btns-wrapper-gpt">
+                <div className="chat-wrapper">
+                    <div className="chat-bubbles">
+                        {(this._mode === GPTPopupMode.FIREFLY ? this._fireflyArray : this._conversationArray).map((message, index) => (
+                            <div key={index} className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`}>
+                                {message}
+                            </div>
+                        ))}
+                        {this._gptProcessing && <div className="chat-bubble chat-message">...</div>}
+                    </div>
 
-                <div ref={this._messagesEndRef} style={{ height: '100px' }} />
+                    <div ref={this._messagesEndRef} style={{ height: '40px' }} />
+                </div>
             </div>
         </div>
     );
@@ -520,7 +529,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
                     onChange={e => onChange(e.target.value)}
                     onKeyDown={e => this.handleKeyPress(e, this._mode)}
                     type="text"
-                    style={{ color: SnappingManager.userColor }}
+                    style={{ color: 'black' }}
                     placeholder={placeholder}
                 />
                 <Button //
@@ -744,7 +753,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
                         onClick={() => this._collectionContext && Doc.setDocFilter(this._collectionContext, 'tags', GPTPopup.ChatTag, 'remove')}
                     />
                     {[GPTPopupMode.USER_PROMPT, GPTPopupMode.QUIZ_RESPONSE, GPTPopupMode.FIREFLY].includes(this._mode) && (
-                        <IconButton color={SettingsManager.userVariantColor} background={SettingsManager.userColor} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} />
+                        <IconButton color={SettingsManager.userVariantColor} background={SettingsManager.userColor} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={action(() => (this._mode = GPTPopupMode.GPT_MENU))} />
                     )}
                 </>
             )}
@@ -753,7 +762,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
 
     render() {
         return (
-            <div className="gptPopup-summary-box" style={{ background: SnappingManager.userColor, color: SnappingManager.userBackgroundColor, display: SnappingManager.ChatVisible ? 'flex' : 'none', overflow: 'auto' }}>
+            <div className="gptPopup-summary-box" style={{ background: SnappingManager.userColor, color: SnappingManager.userBackgroundColor, display: SnappingManager.ChatVisible ? 'flex' : 'none' }}>
                 {(() => {
                     //prettier-ignore
                     switch (this._mode) {
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index 4f0cd3978..a6e221f3e 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -398,7 +398,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
      */
     colorWithGPT = async (drawing: Doc) => {
         const img = await DocumentView.GetDocImage(drawing);
-        const { href } = ImageCast(img).url;
+        const { href } = ImageCast(img)?.url ?? { href: '' };
         const hrefParts = href.split('.');
         const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
         try {
-- 
cgit v1.2.3-70-g09d2


From 299398cb7e21259de3bf7597995840b84e7e0590 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 9 May 2025 23:01:36 -0400
Subject: added user templates to template_user global field. fixed schemas to
 not erase field values when setting a new column name. fixed templates to
 render properly when switching by fixing contentsRef to be observable. fixed
 templates with fields in stacks to autoHeight update correctly by .  fixed
 images in templates to set their nativeDim after rendering.  fixed text views
 of descriptions to auto-updated by setting the modification date of the
 description field.

---
 src/client/documents/DocUtils.ts                   | 20 +++++++++----------
 src/client/util/DropConverter.ts                   |  4 +++-
 .../CollectionStackingViewFieldColumn.tsx          |  6 ++++--
 src/client/views/collections/CollectionSubView.tsx | 13 ++++++++++--
 .../collectionSchema/CollectionSchemaView.tsx      |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  6 +++---
 src/client/views/nodes/ImageBox.tsx                |  5 +++--
 .../views/nodes/formattedText/FormattedTextBox.tsx |  3 ++-
 src/fields/Doc.ts                                  | 23 +++++++++++++++++++---
 src/server/SharedMediaTypes.ts                     |  3 +++
 10 files changed, 60 insertions(+), 25 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index 9135899d7..36e03daed 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -642,8 +642,15 @@ export namespace DocUtils {
         return dd;
     }
 
-    export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) {
+    export function assignUploadInfo(result: Upload.FileInformation, protoIn: Doc) {
         const proto = protoIn;
+
+        if (Upload.isTextInformation(result)) {
+            proto.text = result.rawText;
+        }
+        if (Upload.isVideoInformation(result)) {
+            proto.data_duration = result.duration;
+        }
         if (Upload.isImageInformation(result)) {
             const maxNativeDim = Math.max(result.nativeHeight, result.nativeWidth);
             const exifRotation = StrCast(result.exifData?.data?.Orientation).toLowerCase();
@@ -677,15 +684,8 @@ export namespace DocUtils {
         const pathname = result.accessPaths.agnostic.client;
         const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc);
         if (doc) {
-            const proto = Doc.GetProto(doc);
-            proto.text = result.rawText;
-            !(result instanceof Error) && DocUtils.assignImageInfo(result, proto);
-            if (Upload.isVideoInformation(result)) {
-                proto.data_duration = result.duration;
-            }
-            if (overwriteDoc) {
-                Doc.removeCurrentlyLoading(overwriteDoc);
-            }
+            DocUtils.assignUploadInfo(result, Doc.GetProto(doc));
+            overwriteDoc && Doc.removeCurrentlyLoading(overwriteDoc);
             generatedDocuments.push(doc);
         }
         return doc;
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index b6b111930..1d4779d8a 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -41,7 +41,7 @@ function makeTemplate(doc: Doc, first: boolean = true): boolean {
     if (first && !docs.length) {
         // bcz: feels hacky : if the root level document has items, it's not a field template
         isTemplate = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData], true) || isTemplate;
-    } else if (docData instanceof RichTextField || docData instanceof ImageField) {
+    } else if (docData instanceof RichTextField || docData instanceof ImageField || (docData === undefined && doc.type === DocumentType.IMG)) {
         if (!StrCast(layoutDoc.title).startsWith('-')) {
             isTemplate = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData], true);
         }
@@ -86,6 +86,8 @@ export function makeUserTemplateButtonOrImage(doc: Doc, image?: string) {
     dbox.dragFactory = layoutDoc;
     dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined;
     dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory)');
+    const userTemplatesDoc = DocCast(Doc.UserDoc().template_user);
+    userTemplatesDoc && Doc.AddDocToList(userTemplatesDoc, 'data', layoutDoc);
     return dbox;
 }
 
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 994669734..345f60e75 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -71,6 +71,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
     }
 
     _ele: HTMLElement | null = null;
+    _eleMasonrySingle = React.createRef<HTMLDivElement>();
 
     protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) => {
         const dragData = de.complete.docDragData;
@@ -92,13 +93,13 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
     createColumnDropRef = (ele: HTMLDivElement | null) => {
         this.dropDisposer?.();
         if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Doc, this.onInternalPreDrop.bind(this));
-        else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
+        else if (this._eleMasonrySingle.current) this.props.refList.splice(this.props.refList.indexOf(this._eleMasonrySingle.current), 1);
         this._ele = ele;
     };
 
     @action
     componentDidMount() {
-        this._ele && this.props.refList.push(this._ele);
+        this._eleMasonrySingle.current && this.props.refList.push(this._eleMasonrySingle.current);
         this._disposers.collapser = reaction(
             () => this._props.headingObject?.collapsed,
             collapsed => { this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false; }, // prettier-ignore
@@ -363,6 +364,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
                         }}>
                         <div
                             key={`${heading}-stack`}
+                            ref={this._eleMasonrySingle}
                             className="collectionStackingView-masonrySingle"
                             style={{
                                 padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e79d0a76d..4ac755b9d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -9,7 +9,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
+import { BoolCast, Cast, DateCast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
@@ -561,7 +561,16 @@ export function CollectionSubView<X>() {
                             }
                             const loading = Docs.Create.LoadingDocument(file, options);
                             Doc.addCurrentlyLoading(loading);
-                            DocUtils.uploadFileToDoc(file, {}, loading);
+                            DocUtils.uploadFileToDoc(file, {}, loading).then(d => {
+                                if (d && d?.type === DocumentType.IMG) {
+                                    const imgTemplate = DocListCast(DocCast(Doc.UserDoc().template_user)?.data).find(d => d.title === 'shower');
+                                    if (imgTemplate) {
+                                        d.layout_fieldKey = 'layout_shower';
+                                        d.layout_shower = imgTemplate;
+                                    }
+                                }
+                            });
+
                             return loading;
                         })
                     ))
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index c06391f35..6442385c0 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -359,7 +359,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     @action
     addNewKey = (key: string, defaultVal: FieldType | undefined) => {
         this.childDocs.forEach(doc => {
-            doc[DocData][key] = defaultVal;
+            if (doc[DocData][key] === undefined) doc[DocData][key] = defaultVal;
         });
     };
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bdb97d7bb..f9c21451e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -740,7 +740,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
     }
     widgetOverlayFunc = () => (this.widgetDecorations ? this.widgetOverlay : null);
     viewingAiEditor = () => (this._props.showAIEditor && this._componentView?.componentAIView?.() !== undefined ? this.aiEditor : null);
-    _contentsRef = React.createRef<DocumentContentsView>();
+    @observable _contentsRef: DocumentContentsView | undefined = undefined;
     @computed get viewBoxContents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
@@ -756,7 +756,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                     }}>
                     <DocumentContentsView
                         {...this._props}
-                        ref={this._contentsRef}
+                        ref={action((r: DocumentContentsView) => (this._contentsRef = r))}
                         layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
                         pointerEvents={this.contentPointerEvents}
                         setContentViewBox={this.setContentView}
@@ -1168,7 +1168,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
      * @returns boolean whether sub-component Doc is in synch with the layoutDoc that this view thinks its rendering
      */
     IsInvalid = (renderDoc?: Doc): boolean => {
-        const docContents = this._docViewInternal?._contentsRef.current;
+        const docContents = this._docViewInternal?._contentsRef;
         return !(
             (!renderDoc ||
                 (docContents?.layoutDoc === renderDoc && //
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index b648f2adb..f7ad5c7e2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -152,7 +152,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._disposers.path = reaction(
             () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width), height: this.layoutDoc._height }),
             ({ nativeSize, width, height }) => {
-                if (!this.layoutDoc._layout_nativeDimEditable || !height) {
+                if (!this.layoutDoc._layout_nativeDimEditable || !height || this.layoutDoc.layout_resetNativeDim) {
+                    this.layoutDoc.layout_resetNativeDim = undefined; // template images need to reset their dimensions when they are rendered with content.  afterwards, remove this flag.
                     this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
                 }
             },
@@ -1048,7 +1049,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     alert('Error uploading files - possibly due to unsupported file types');
                 } else {
                     this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client);
-                    !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc);
+                    !(result instanceof Error) && DocUtils.assignUploadInfo(result, this.dataDoc);
                 }
                 disposer();
             } else {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 824ac97da..57720baae 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1224,13 +1224,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             incomingValue => {
                 if (this.EditorView && this.ApplyingChange !== this.fieldKey) {
                     if (incomingValue?.data) {
-                        const updatedState = JSON.parse(incomingValue.data.Data);
+                        const updatedState = JSON.parse(incomingValue.data.Data.replace(/\n/g, ''));
                         if (JSON.stringify(this.EditorView.state.toJSON()) !== JSON.stringify(updatedState)) {
                             this.EditorView.updateState(EditorState.fromJSON(this.config, updatedState));
                             this.tryUpdateScrollHeight();
                         }
                     } else if (this.EditorView.state.doc.textContent !== (incomingValue?.str ?? '')) {
                         selectAll(this.EditorView.state, tx => this.EditorView?.dispatch(tx.insertText(incomingValue?.str ?? '')));
+                        this.tryUpdateScrollHeight();
                     }
                 }
             },
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ba94f0504..990b6606f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -25,6 +25,7 @@ import { ComputedField, ScriptField } from './ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, ImageCastWithSuffix, NumCast, RTFCast, StrCast, ToConstructor, toList } from './Types';
 import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util';
 import { gptImageLabel } from '../client/apis/gpt/GPT';
+import { DateField } from './DateField';
 
 export let ObjGetRefField: (id: string, force?: boolean) => Promise<Doc | undefined>;
 export let ObjGetRefFields: (ids: string[]) => Promise<Map<string, Doc | undefined>>;
@@ -1109,6 +1110,10 @@ export namespace Doc {
             Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc));
             Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue);
         }
+        if (templateField.type === DocumentType.IMG) {
+            // bcz: should be a better way .. but, if the image is a template, then we can't expect to know the aspect ratio. When the image is replaced by data and rendered, we want to recomputed the native dimensions.
+            templateField[DocData].layout_resetNativeDim = true;
+        }
         // get the layout string that the template uses to specify its layout
         const templateFieldLayoutString = StrCast(Doc.LayoutField(templateField[DocLayout]));
 
@@ -1184,13 +1189,15 @@ export namespace Doc {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
     }
     export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
-        return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0));
+        // if this is a field template, then don't use the doc's nativeWidth/height
+        return !doc ? 0 : NumCast(doc.isTemplateForField ? undefined : doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], !doc.isTemplateForField && useWidth ? NumCast(doc._width) : 0));
     }
     export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
         if (!doc) return 0;
         const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) / NumCast(doc._width)) * NumCast(doc._height); // divide before multiply to avoid floating point errrorin case nativewidth = width
         const dheight = NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0);
-        return NumCast(doc._nativeHeight, nheight || dheight);
+        // if this is a field template, then don't use the doc's nativeWidth/height
+        return NumCast(doc.isTemplateForField ? undefined : doc._nativeHeight, nheight || dheight);
     }
 
     export function OutpaintingWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
@@ -1504,7 +1511,13 @@ export namespace Doc {
                 case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutDataKey(tdoc)])?.Text ?? StrCast(tdoc[Doc.LayoutDataKey(tdoc)]);
                 default:               return StrCast(tdoc.title).startsWith("Untitled") ? "" : StrCast(tdoc.title);
         }}); // prettier-ignore
-        return docText(doc).then(text => (doc['$' + Doc.LayoutDataKey(doc) + '_description'] = text));
+        return docText(doc).then(
+            action(text => {
+                // set the time when the date changes.  This also allows a live textbox view to react to the update, otherwise, it wouldn't take effect until the next time the view is rerendered.
+                doc['$' + Doc.LayoutDataKey(doc) + '_description_modificationDate'] = new DateField();
+                return (doc['$' + Doc.LayoutDataKey(doc) + '_description'] = text);
+            })
+        );
     }
 
     // prettier-ignore
@@ -1820,3 +1833,7 @@ ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, ran
 ScriptingGlobals.add(function toJavascriptString(str: string) {
     return Field.toJavascriptString(str as FieldType);
 });
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function getDescription(doc: Doc) {
+    return Doc.getDescription(doc);
+});
diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts
index 9aa4b120f..43a9ce963 100644
--- a/src/server/SharedMediaTypes.ts
+++ b/src/server/SharedMediaTypes.ts
@@ -19,6 +19,9 @@ export enum AudioAnnoState {
 }
 
 export namespace Upload {
+    export function isTextInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation {
+        return 'rawText' in uploadResponse;
+    }
     export function isImageInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation {
         return 'nativeWidth' in uploadResponse;
     }
-- 
cgit v1.2.3-70-g09d2


From 534532aebbab49b00dc76ad6a0cf6c20368d818c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 10 May 2025 00:01:39 -0400
Subject: added an imageTemplate ui button.

---
 src/client/util/CurrentUserUtils.ts                |  1 +
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 src/client/views/global/globalScripts.ts           |  5 +++
 src/client/views/nodes/DocumentView.tsx            | 38 +++++++++++++---------
 src/fields/Doc.ts                                  |  4 ---
 5 files changed, 30 insertions(+), 20 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d460cd0d7..ea3b8d146 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -841,6 +841,7 @@ pie title Minerals in my tap water
             { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,  expertMode: false,                                                                                                                             width: 30,  scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
             { title: "Header",  icon: "heading",       toolTip: "Doc Titlebar Color",   btnType: ButtonType.ColorButton,  expertMode: false,                                                                                                          ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'} },
             { title: "Template",icon: "scroll",        toolTip: "Default Note Template",btnType: ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.RTF,                                                                                                              scripts: { onClick: '{ return setDefaultTemplate(_readOnly_); }'} },
+            { title: "Img Temp",icon: "portrait",      toolTip: "Default Image Template",btnType:ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.IMG,                                                                                                              scripts: { onClick: '{ return setDefaultImageTemplate(_readOnly_); }'} },
             { title: "Fill",    icon: "fill-drip",     toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton,  expertMode: false,                                                                                                          ignoreClick: true, width: 30,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'} }, // Only when a document is selected
             { title: "Border",  icon: "border-style",  toolTip: "Border Color",         btnType: ButtonType.ColorButton,  expertMode: false,                                                                                                          ignoreClick: true, width: 30,  scripts: { script:  'return setBorderColor(value, _readOnly_)'} }, // Only when a document is selected
             { title: "B.Width",                        toolTip: "Border width",         btnType: ButtonType.NumberSliderButton,                                                                                                                       ignoreClick: true,             scripts: {script: '{ return setBorderWidth(value, _readOnly_);}'}, numBtnMin: 0, linearView_btnWidth:40},
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 4ac755b9d..cafe367b7 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -563,7 +563,7 @@ export function CollectionSubView<X>() {
                             Doc.addCurrentlyLoading(loading);
                             DocUtils.uploadFileToDoc(file, {}, loading).then(d => {
                                 if (d && d?.type === DocumentType.IMG) {
-                                    const imgTemplate = DocListCast(DocCast(Doc.UserDoc().template_user)?.data).find(d => d.title === 'shower');
+                                    const imgTemplate = DocCast(Doc.UserDoc().defaultImageLayout);
                                     if (imgTemplate) {
                                         d.layout_fieldKey = 'layout_shower';
                                         d.layout_shower = imgTemplate;
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index f4beb1004..cb3adae10 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -156,6 +156,11 @@ ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) {
 });
 // toggle: Set overlay status of selected document
 // eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function setDefaultImageTemplate(checkResult?: boolean) {
+    return DocumentView.setDefaultImageTemplate(checkResult);
+});
+// toggle: Set overlay status of selected document
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
     if (checkResult) {
         return DocumentView.Selected().length ? StrCast(DocumentView.SelectedDocs().lastElement().layout_headingColor) : Doc.SharingDoc()?.headingColor;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f9c21451e..05706fe6b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1389,28 +1389,36 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined);
     }, 'set custom view');
 
-    public static setDefaultTemplate(checkResult?: boolean) {
-        if (checkResult) {
-            return Doc.UserDoc().defaultTextLayout;
+    private static getTemplate(view: DocumentView | undefined) {
+        if (view) {
+            if (!view.layoutDoc.isTemplateDoc) {
+                MakeTemplate(view.Document);
+                Doc.AddDocToList(Doc.UserDoc(), 'template_user', view.Document);
+                Doc.AddDocToList(DocListCast(Doc.MyTools?.data)[1], 'data', makeUserTemplateButtonOrImage(view.Document));
+                DocCast(Doc.UserDoc().template_user) && view.Document && Doc.AddDocToList(DocCast(Doc.UserDoc().template_user)!, 'data', view.Document);
+                return view.Document;
+            }
+            return DocCast(Doc.LayoutField(view.Document)) ?? view.Document;
         }
+    }
+    public static setDefaultTemplate(checkResult?: boolean) {
+        if (checkResult) return Doc.UserDoc().defaultTextLayout;
         const view = DocumentView.Selected()[0]?._props.renderDepth > 0 ? DocumentView.Selected()[0] : undefined;
         undoable(() => {
-            let tempDoc: Opt<Doc>;
-            if (view) {
-                if (!view.layoutDoc.isTemplateDoc) {
-                    tempDoc = view.Document;
-                    MakeTemplate(tempDoc);
-                    Doc.AddDocToList(Doc.UserDoc(), 'template_user', tempDoc);
-                    Doc.AddDocToList(DocListCast(Doc.MyTools?.data)[1], 'data', makeUserTemplateButtonOrImage(tempDoc));
-                    DocCast(Doc.UserDoc().template_user) && tempDoc && Doc.AddDocToList(DocCast(Doc.UserDoc().template_user)!, 'data', tempDoc);
-                } else {
-                    tempDoc = DocCast(Doc.LayoutField(view.Document));
-                }
-            }
+            const tempDoc = DocumentView.getTemplate(view);
             Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
         }, 'set default template')();
         return undefined;
     }
+    public static setDefaultImageTemplate(checkResult?: boolean) {
+        if (checkResult) return Doc.UserDoc().defaultImageLayout;
+        const view = DocumentView.Selected()[0]?._props.renderDepth > 0 ? DocumentView.Selected()[0] : undefined;
+        undoable(() => {
+            const tempDoc = DocumentView.getTemplate(view);
+            Doc.UserDoc().defaultImageLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
+        }, 'set default image template')();
+        return undefined;
+    }
 
     /**
      * This switches between the current view of a Doc and a specified alternate layout view.
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 990b6606f..0d11b9743 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1833,7 +1833,3 @@ ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, ran
 ScriptingGlobals.add(function toJavascriptString(str: string) {
     return Field.toJavascriptString(str as FieldType);
 });
-// eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function getDescription(doc: Doc) {
-    return Doc.getDescription(doc);
-});
-- 
cgit v1.2.3-70-g09d2


From 87484331f805f583c98226c5a8c6b1ec5fc88925 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 13 May 2025 13:37:54 -0400
Subject: fixed setting up link anchors for video to be added to Doc. fixed
 imageTemplate to work with templates in tabs.  fixed link following from link
 menu.  cleaned up getView with sidebars to open them when needed properly..

---
 src/client/documents/DocUtils.ts                   |  2 +-
 src/client/util/DocumentManager.ts                 |  2 +-
 src/client/views/MarqueeAnnotator.tsx              |  3 +-
 src/client/views/SidebarAnnos.tsx                  | 29 +++++++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  7 +--
 src/client/views/linking/LinkMenuItem.tsx          | 18 +++----
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/MapBox/MapBox.tsx           | 59 +++++++---------------
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   | 51 ++++++++++---------
 src/client/views/nodes/PDFBox.tsx                  | 17 +++----
 src/client/views/nodes/VideoBox.tsx                | 17 ++++---
 src/client/views/nodes/WebBox.tsx                  | 26 +++++-----
 .../views/nodes/formattedText/FormattedTextBox.tsx | 17 +++----
 13 files changed, 124 insertions(+), 126 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index 36e03daed..41a61b154 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -724,7 +724,7 @@ export namespace DocUtils {
                                 _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200,
                                 _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35,
                                 _layout_autoHeight: true,
-                                backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor),
+                                backgroundColor: backgroundColor ?? StrCast(Doc.UserDoc().textBackgroundColor),
                                 borderColor: Doc.UserDoc().borderColor as string,
                                 borderWidth: Doc.UserDoc().borderWidth as number,
                                 text_centered: BoolCast(Doc.UserDoc().textCentered),
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index fc8f22cf3..422613968 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -250,7 +250,7 @@ export class DocumentManager {
         const options = optionsIn;
         Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
         const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
-        if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
+        if (docContextPath.some(doc => doc !== targetDoc && doc.hidden)) options.toggleTarget = false;
         let activatedTab = false;
         if (DocumentView.activateTabView(docContextPath[0])) {
             options.toggleTarget = false;
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index b2e42652d..d354dd2c0 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -194,14 +194,13 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => {
             e.preventDefault();
             e.stopPropagation();
-            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, annotationOn, 'yellow');
                 target.layout_fitWidth = true;
                 DocumentView.SetSelectOnLoad(target);
                 return target;
             };
+            const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: dragEv => {
                     if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 573c28ccf..71b479a22 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ClientUtils, returnFalse, returnOne, returnZero } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
-import { Doc, DocListCast, Field, FieldResult, FieldType, StrListCast } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldResult, FieldType, Opt, StrListCast } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
@@ -19,6 +19,7 @@ import { StyleProp } from './StyleProp';
 import { CollectionStackingView } from './collections/CollectionStackingView';
 import { DocumentView } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
+import { FocusViewOptions } from './nodes/FocusViewOptions';
 
 interface ExtraProps {
     fieldKey: string;
@@ -149,15 +150,33 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
     };
     makeDocUnfiltered = (doc: Doc) => {
         if (DocListCast(this._props.Doc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
-            if (this.childFilters()) {
+            if (this.childFilters().length) {
                 // if any child filters exist, get rid of them
                 this._props.layoutDoc._childFilters = new List<string>();
+                return true;
             }
-            return true;
         }
         return false;
     };
 
+    public static getView(sidebar: SidebarAnnos | null, sidebarShown: boolean, toggleSidebar: () => void, doc: Doc, options: FocusViewOptions) {
+        if (!sidebarShown) {
+            options.didMove = true;
+            toggleSidebar();
+        }
+        options.didMove = sidebar?.makeDocUnfiltered(doc) || options.didMove;
+
+        if (!doc.hidden) {
+            if (!options.didMove && options.toggleTarget) {
+                options.toggleTarget = false;
+                options.didMove = doc.hidden = true;
+            }
+        } else {
+            options.didMove = !(doc.hidden = false);
+        }
+        return new Promise<Opt<DocumentView>>(res => DocumentView.addViewRenderedCb(doc, res));
+    }
+
     get sidebarKey() {
         return this._props.fieldKey + '_sidebar';
     }
@@ -181,8 +200,8 @@ export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & Extr
     layout_showTitle = () => 'title';
     setHeightCallback = (height: number) => this._props.setHeight?.(height + this.filtersHeight());
     sortByLinkAnchorY = (a: Doc, b: Doc) => {
-        const ay = Doc.Links(a).length && DocCast(Doc.Links(a)[0].link_anchor_1).y;
-        const by = Doc.Links(b).length && DocCast(Doc.Links(b)[0].link_anchor_1).y;
+        const ay = Doc.Links(a).length && DocCast(Doc.Links(a)[0].link_anchor_1)?.y;
+        const by = Doc.Links(b).length && DocCast(Doc.Links(b)[0].link_anchor_1)?.y;
         return NumCast(ay) - NumCast(by);
     };
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bb3c59eae..77615c650 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1738,10 +1738,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         );
 
         if (addAsAnnotation) {
-            if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
-                Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), [])?.push(anchor);
+            const fieldKey = this._props.isAnnotationOverlay ? this._props.fieldKey : this._props.fieldKey + '_annotations';
+            if (Cast(this.dataDoc[fieldKey], listSpec(Doc), null) !== undefined) {
+                Cast(this.dataDoc[fieldKey], listSpec(Doc), [])?.push(anchor);
             } else {
-                this.dataDoc[this._props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
+                this.dataDoc[fieldKey] = new List<Doc>([anchor]);
             }
         }
         return anchor;
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index c984a7053..6c1ad568d 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -105,11 +105,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                     LinkManager.Instance.currentLinkAnchor = LinkManager.Instance.currentLink ? this.sourceAnchor : undefined;
 
                     if ((SnappingManager.PropertiesWidth ?? 0) < 100) {
-                        setTimeout(
-                            action(() => {
-                                SnappingManager.SetPropertiesWidth(250);
-                            })
-                        );
+                        setTimeout(action(() => SnappingManager.SetPropertiesWidth(250)));
                     }
                 }
             })
@@ -136,14 +132,14 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
                     this._props.itemHandler?.(this._props.linkDoc);
                 } else {
                     const focusDoc =
-                        Cast(this._props.linkDoc.link_anchor_1, Doc, null)?.annotationOn === this._props.sourceDoc
-                            ? Cast(this._props.linkDoc.link_anchor_1, Doc, null)
-                            : Cast(this._props.linkDoc.link_anchor_2, Doc, null)?.annotationOn === this._props.sourceDoc
-                              ? Cast(this._props.linkDoc.link_anchor_12, Doc, null)
-                              : undefined;
+                        DocCast(this._props.linkDoc.link_anchor_1)?.annotationOn === this._props.sourceDoc
+                            ? DocCast(this._props.linkDoc.link_anchor_1)
+                      : DocCast(this._props.linkDoc.link_anchor_2)?.annotationOn === this._props.sourceDoc
+                            ? DocCast(this._props.linkDoc.link_anchor_2)
+                      : undefined; // prettier-ignore
 
                     if (focusDoc) this._props.docView._props.focus(focusDoc, { instant: true });
-                    DocumentView.FollowLink(this._props.linkDoc, this._props.sourceDoc, false);
+                    DocumentView.FollowLink(this._props.linkDoc, focusDoc ?? this._props.sourceDoc, false);
                 }
             }
         );
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 05706fe6b..b0415ef18 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1412,7 +1412,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     }
     public static setDefaultImageTemplate(checkResult?: boolean) {
         if (checkResult) return Doc.UserDoc().defaultImageLayout;
-        const view = DocumentView.Selected()[0]?._props.renderDepth > 0 ? DocumentView.Selected()[0] : undefined;
+        const view = DocumentView.Selected()[0]?._props.renderDepth > 0 || DocumentView.Selected()[0]?.Document.isTemplateDoc ? DocumentView.Selected()[0] : undefined;
         undoable(() => {
             const tempDoc = DocumentView.getTemplate(view);
             Doc.UserDoc().defaultImageLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index a563b7c1b..a279ccc48 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -13,7 +13,7 @@ import { CirclePicker, ColorResult } from 'react-color';
 import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl/mapbox';
 import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils';
 import { emptyFunction } from '../../../../Utils';
-import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types';
@@ -111,12 +111,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     // this list contains pushpins and configs
     @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } // prettier-ignore
-    @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } // prettier-ignore
+    @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.sidebarKey]); } // prettier-ignore
     @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } // prettier-ignore
     @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } // prettier-ignore
     @computed get SidebarShown() { return !!this.layoutDoc._layout_showSidebar; } // prettier-ignore
     @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } // prettier-ignore
-    @computed get SidebarKey() {  return this.fieldKey + '_sidebar'; } // prettier-ignore
+    @computed get sidebarKey() {  return this.fieldKey + '_sidebar'; } // prettier-ignore
     @computed get sidebarColor() {
         return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
     }
@@ -260,7 +260,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => {
         this.allAnnotations
-            .filter(anno => toList(doc).includes(DocCast(anno.mapPin)))
+            .filter(anno => toList(doc).includes(DocCast(anno.mapPin)!))
             .forEach(anno => {
                 anno.mapPin = undefined;
             });
@@ -339,27 +339,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     startAnchorDrag = (e: PointerEvent, ele: HTMLElement) => {
         e.preventDefault();
         e.stopPropagation();
-
-        const sourceAnchorCreator = action(() => {
-            const note = this.getAnchor(true);
-            if (note && this._selectedPinOrRoute) {
-                note.latitude = this._selectedPinOrRoute.latitude;
-                note.longitude = this._selectedPinOrRoute.longitude;
-                note.map = this._selectedPinOrRoute.map;
-            }
-            return note as Doc;
-        });
-
         const targetCreator = (annotationOn: Doc | undefined) => {
             const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
+            target.layout_fitWidth = true;
             DocumentView.SetSelectOnLoad(target);
             return target;
         };
+
+        const sourceAnchorCreator = () => this.getAnchor(true);
         const docView = this.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: dragEv => {
-                    if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
+                    if (!dragEv.aborted && dragEv.annoDragData?.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
                         dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.Document;
                         dragEv.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
@@ -368,17 +360,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     createNoteAnnotation = () => {
-        const createFunc = undoable(
-            action(() => {
-                const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]);
-                if (note && this._selectedPinOrRoute) {
-                    note.latitude = this._selectedPinOrRoute.latitude;
-                    note.longitude = this._selectedPinOrRoute.longitude;
-                    note.map = this._selectedPinOrRoute.map;
-                }
-            }),
-            'create note annotation'
-        );
+        const createFunc = undoable(() => this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]), 'create note annotation');
         if (!this.layoutDoc.layout_showSidebar) {
             this.toggleSidebar();
             setTimeout(createFunc);
@@ -428,14 +410,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
     };
 
-    getView = (doc: Doc, options: FocusViewOptions) => {
-        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
-            this.toggleSidebar();
-            options.didMove = true;
+    getView = async (doc: Doc, options: FocusViewOptions) => {
+        if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+            SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, this.toggleSidebar, doc, options);
         }
-        return new Promise<Opt<DocumentView>>(res => {
-            DocumentView.addViewRenderedCb(doc, dv => res(dv));
-        });
+        return undefined;
     };
 
     /*
@@ -476,13 +455,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @action
     deleteSelectedPinOrRoute = undoable(() => {
-        if (this._selectedPinOrRoute) {
+        const selPin = DocCast(this._selectedPinOrRoute);
+        if (selPin) {
             // Removes filter
-            Doc.setDocFilter(this.Document, 'latitude', NumCast(this._selectedPinOrRoute.latitude), 'remove');
-            Doc.setDocFilter(this.Document, 'longitude', NumCast(this._selectedPinOrRoute.longitude), 'remove');
-            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this._selectedPinOrRoute))}`, 'remove');
+            Doc.setDocFilter(this.Document, 'latitude', NumCast(selPin.latitude), 'remove');
+            Doc.setDocFilter(this.Document, 'longitude', NumCast(selPin.longitude), 'remove');
+            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(selPin)}`, 'remove');
 
-            this.removePushpinOrRoute(this._selectedPinOrRoute);
+            this.removePushpinOrRoute(selPin);
         }
         MapAnchorMenu.Instance.fadeOut(true);
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -1299,7 +1279,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     <SidebarAnnos
                         ref={this._sidebarRef}
                         {...this._props}
-                        fieldKey={this.fieldKey}
                         Doc={this.Document}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index e0efab576..0beefcb67 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -145,7 +145,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
     removeMapDocument = (docsIn: Doc | Doc[], annotationKey?: string) => {
         const docs = toList(docsIn);
         this.allAnnotations
-            .filter(anno => docs.includes(DocCast(anno.mapPin)))
+            .filter(anno => docs.includes(DocCast(anno.mapPin)!))
             .forEach(anno => {
                 anno.mapPin = undefined;
             });
@@ -224,6 +224,12 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
     startAnchorDrag = (e: PointerEvent, ele: HTMLElement) => {
         e.preventDefault();
         e.stopPropagation();
+        const targetCreator = (annotationOn: Doc | undefined) => {
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
+            target.layout_fitWidth = true;
+            DocumentView.SetSelectOnLoad(target);
+            return target;
+        };
 
         const sourceAnchorCreator = action(() => {
             const note = this.getAnchor(true);
@@ -235,11 +241,6 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
             return note as Doc;
         });
 
-        const targetCreator = (annotationOn: Doc | undefined) => {
-            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
-            DocumentView.SetSelectOnLoad(target);
-            return target;
-        };
         const docView = this.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
@@ -362,22 +363,22 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
 
     @action
     deselectPin = () => {
-        if (this.selectedPin) {
+        const selPin = DocCast(this.selectedPin);
+        if (selPin) {
             // Removes filter
-            Doc.setDocFilter(this.Document, 'latitude', NumCast(this.selectedPin.latitude), 'remove');
-            Doc.setDocFilter(this.Document, 'longitude', NumCast(this.selectedPin.longitude), 'remove');
-            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+            Doc.setDocFilter(this.Document, 'latitude', NumCast(selPin.latitude), 'remove');
+            Doc.setDocFilter(this.Document, 'longitude', NumCast(selPin.longitude), 'remove');
+            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(selPin)}`, 'remove');
 
-            const temp = this.selectedPin;
             if (!this._unmounting) {
-                this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp));
+                this._bingMap.current.entities.remove(this.map_docToPinMap.get(selPin));
             }
-            const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude));
-            this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(temp as Doc));
+            const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(selPin.latitude, selPin.longitude));
+            this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(selPin));
             if (!this._unmounting) {
                 this._bingMap.current.entities.push(newpin);
             }
-            this.map_docToPinMap.set(temp, newpin);
+            this.map_docToPinMap.set(selPin, newpin);
             this.selectedPin = undefined;
             this.bingSearchBarContents = this.Document.map;
         }
@@ -388,9 +389,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
             this.toggleSidebar();
             options.didMove = true;
         }
-        return new Promise<Opt<DocumentView>>(res => {
-            DocumentView.addViewRenderedCb(doc, dv => res(dv));
-        });
+        return new Promise<Opt<DocumentView>>(res => DocumentView.addViewRenderedCb(doc, res));
     };
     /*
      * Pushpin onclick
@@ -535,13 +534,14 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
 
     @action
     deleteSelectedPin = undoable(() => {
-        if (this.selectedPin) {
+        const selPin = this.selectedPin;
+        if (selPin) {
             // Removes filter
-            Doc.setDocFilter(this.Document, 'latitude', NumCast(this.selectedPin.latitude), 'remove');
-            Doc.setDocFilter(this.Document, 'longitude', NumCast(this.selectedPin.longitude), 'remove');
-            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+            Doc.setDocFilter(this.Document, 'latitude', NumCast(selPin.latitude), 'remove');
+            Doc.setDocFilter(this.Document, 'longitude', NumCast(selPin.longitude), 'remove');
+            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(selPin)}`, 'remove');
 
-            this.removePushpin(this.selectedPin);
+            this.removePushpin(selPin);
         }
         MapAnchorMenu.Instance.fadeOut(true);
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -638,7 +638,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>
         this._disposers.highlight = reaction(
             () => this.allAnnotations.map(doc => doc[Highlight]),
             () => {
-                const allConfigPins = this.allAnnotations.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin);
+                const allConfigPins = this.allAnnotations
+                    .map(doc => ({ doc, pushpin: DocCast(doc.mapPin) }))
+                    .filter(pair => pair.pushpin)
+                    .map(pair => ({ doc: pair.doc, pushpin: pair.pushpin! }));
                 allConfigPins.forEach(({ pushpin }) => {
                     if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) {
                         this.recolorPin(pushpin);
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 55e6d5596..e4050a3c3 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -10,7 +10,7 @@ import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, FieldValue, NumCast, StrCast, toList } from '../../../fields/Types';
+import { Cast, DocCast, FieldValue, NumCast, StrCast, toList } from '../../../fields/Types';
 import { ImageField, PdfField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction } from '../../../Utils';
@@ -27,7 +27,6 @@ import { Colors } from '../global/globalEnums';
 import { PDFViewer } from '../pdf/PDFViewer';
 import { PinDocView, PinProps } from '../PinFuncs';
 import { SidebarAnnos } from '../SidebarAnnos';
-import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FocusViewOptions } from './FocusViewOptions';
 import { ImageBox } from './ImageBox';
@@ -56,6 +55,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy> = undefined;
     @observable private _pageControls = false;
 
+    @computed get sidebarKey() {
+        return this.fieldKey + '_sidebar';
+    }
     @computed get pdfUrl() {
         return Cast(this.dataDoc[this._props.fieldKey], PdfField);
     }
@@ -232,14 +234,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.config_scrollTop)), options);
     };
 
-    getView = (doc: Doc, options: FocusViewOptions) => {
-        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
-            options.didMove = true;
-            this.toggleSidebar(false);
+    getView = async (doc: Doc, options: FocusViewOptions) => {
+        if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+            SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, () => this.toggleSidebar(false), doc, options);
         }
-        return new Promise<Opt<DocumentView>>(res => {
-            DocumentView.addViewRenderedCb(doc, dv => res(dv));
-        });
+        return undefined;
     };
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index fa099178c..b3cb0e1db 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -338,12 +338,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         const timecode = Cast(this.layoutDoc._layout_currentTimecode, 'number', null);
         const marquee = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation);
+        const docAnchor = () =>
+            Docs.Create.ConfigDocument({
+                title: '#' + timecode,
+                _timecodeToShow: timecode,
+                annotationOn: this.Document,
+            });
         if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent';
-        const anchor =
-            addAsAnnotation && marquee
-                ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode || undefined, undefined, marquee, addAsAnnotation) || this.Document
-                : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.Document });
+        const visibleAnchor = () => addAsAnnotation && marquee && (CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode || undefined, undefined, marquee, addAsAnnotation) || this.Document);
+        const anchor = visibleAnchor() || docAnchor();
         PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true, pannable: true } }, this.Document);
+        addAsAnnotation && this.addDocument(anchor);
         return anchor;
     };
 
@@ -376,9 +381,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             }
             return this._stackedTimeline.getView(doc, options);
         }
-        return new Promise<Opt<DocumentView>>(res => {
-            DocumentView.addViewRenderedCb(doc, dv => res(dv));
-        });
+        return new Promise<Opt<DocumentView>>(res => DocumentView.addViewRenderedCb(doc, res));
     };
 
     // extracts video thumbnails and saves them as field of doc
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 838dbea9d..779ccab40 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -13,7 +13,7 @@ import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { RefField } from '../../../fields/RefField';
 import { listSpec } from '../../../fields/Schema';
-import { Cast, NumCast, StrCast, toList, WebCast } from '../../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast, toList, WebCast } from '../../../fields/Types';
 import { ImageField, WebField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, stringHash } from '../../../Utils';
@@ -104,6 +104,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @computed get webField() {
         return Cast(this.Document[this._props.fieldKey], WebField)?.url;
     }
+    @computed get sidebarKey() {
+        return this.fieldKey + '_sidebar';
+    }
 
     constructor(props: FieldViewProps) {
         super(props);
@@ -308,18 +311,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     @action
-    getView = (doc: Doc /* , options: FocusViewOptions */) => {
-        if (Doc.AreProtosEqual(doc, this.Document))
-            return new Promise<Opt<DocumentView>>(res => {
-                res(this.DocumentView?.());
-            });
+    getView = async (doc: Doc, options: FocusViewOptions) => {
+        if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this.DocumentView?.()));
+
         if (this.Document.layout_fieldKey === 'layout_icon') this.DocumentView?.().iconify();
         const webUrl = WebCast(doc.config_data)?.url;
         if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href);
-        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false);
-        return new Promise<Opt<DocumentView>>(res => {
-            DocumentView.addViewRenderedCb(doc, dv => res(dv));
-        });
+
+        if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+            return SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, () => this.toggleSidebar(false), doc, options);
+        }
+        return undefined;
     };
 
     sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
@@ -454,7 +456,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     iframeDown = (e: PointerEvent) => {
         this._textAnnotationCreator = undefined;
         const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
-        if (sel?.empty && !(e.target as any).textContent)
+        if (sel?.empty && !(e.target as HTMLElement).textContent)
             sel.empty(); // Chrome
         else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
 
@@ -565,7 +567,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 'click',
                 undoable(
                     action((e: MouseEvent) => {
-                        let eleHref = (e.target as any)?.outerHTML?.split('"="')[1]?.split('"')[0];
+                        let eleHref = (e.target as HTMLElement)?.outerHTML?.split('"="')[1]?.split('"')[0];
                         for (let ele = e.target as HTMLElement | Element | null; ele; ele = ele.parentElement) {
                             if ('href' in ele) {
                                 eleHref = (typeof ele.href === 'string' ? ele.href : eleHref) || (ele.parentElement && 'href' in ele.parentElement ? (ele.parentElement.href as string) : eleHref);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 57720baae..bab97f681 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -270,13 +270,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             e.preventDefault();
             e.stopPropagation();
             const targetCreator = (annotationOn?: Doc) => {
-                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn);
+                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
+                target.layout_fitWidth = true;
                 DocumentView.SetSelectOnLoad(target);
                 return target;
             };
 
+            const sourceAnchorCreator = () => this.getAnchor(true);
+
             const docView = this.DocumentView?.();
-            docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
+            docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY);
         });
 
         AnchorMenu.Instance.AddDrawingAnnotation = (drawing: Doc) => {
@@ -1070,15 +1073,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     getView = (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);
-                options.didMove = true;
-            }
-            setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc));
+            return SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, () => this.toggleSidebar(false), doc, options);
         }
-        return new Promise<Opt<DocumentView>>(res => {
-            DocumentView.addViewRenderedCb(doc, dv => res(dv));
-        });
+        return new Promise<Opt<DocumentView>>(res => DocumentView.addViewRenderedCb(doc, res));
     };
     focus = (textAnchor: Doc, options: FocusViewOptions) => {
         const focusSpeed = options.zoomTime ?? 500;
-- 
cgit v1.2.3-70-g09d2


From 2c26d7c96e56344a1a73a7c6f6dec3d6b0f0ee7e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 16 May 2025 11:46:22 -0400
Subject: changed isGroup to freeform_isGroup to fix issues with groups that
 are changed into other collection types.  fixed toggleTarget links to work
 when opening in a light box (previously they flickered on, then off).  fixed
 text boxes with a number field as data to act like a number input box.

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/DocumentManager.ts                 |  1 +
 src/client/views/DocumentDecorations.tsx           |  8 ++--
 src/client/views/PropertiesButtons.tsx             |  4 +-
 src/client/views/PropertiesView.tsx                |  6 +--
 src/client/views/StyleProvider.tsx                 |  8 ++--
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 42 +++++++++--------
 .../collections/collectionFreeForm/MarqueeView.tsx |  4 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |  1 -
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/KeyValueBox.tsx             |  8 ++--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 12 ++---
 src/client/views/nodes/trails/PresBox.tsx          |  2 +-
 src/fields/Doc.ts                                  | 54 +++++++++++++---------
 15 files changed, 85 insertions(+), 71 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2df6f3e23..9aa1d53c2 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -361,7 +361,6 @@ export class DocumentOptions {
     isBaseProto?: BOOLt = new BoolInfo('is doc a base level prototype for data documents as opposed to data documents which are prototypes for layout documents.  base protos are not cloned during a deep');
     isTemplateForField?: string; // the field key for which the containing document is a rendering template
     isTemplateDoc?: BOOLt = new BoolInfo('is the document a template for creating other documents');
-    isGroup?: BOOLt = new BoolInfo('should collection use a grouping UI behavior');
     isFolder?: BOOLt = new BoolInfo('is document a tree view folder');
     _isTimelineLabel?: BOOLt = new BoolInfo('is document a timeline label');
     isLightbox?: BOOLt = new BoolInfo('whether a collection acts as a lightbox by opening lightbox links by hiding all other documents in collection besides link target');
@@ -401,6 +400,7 @@ export class DocumentOptions {
 
     // freeform properties
     freeform?: STRt = new StrInfo('');
+    freeform_isGroup?: BOOLt = new BoolInfo('should collection use a grouping UI behavior');
     _freeform_backgroundGrid?: BOOLt = new BoolInfo('whether background grid is shown on freeform collections');
     _freeform_scale_min?: NUMt = new NumInfo('how far out a view can zoom (used by image/videoBoxes that are clipped');
     _freeform_scale_max?: NUMt = new NumInfo('how far in a view can zoom (used by sidebar freeform views');
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 422613968..1c2ffab1b 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -274,6 +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 compView = this.getDocumentView(DocCast(target.embedContainer))?.ComponentView;
+            options.didMove = target.hidden || options.didMove ? true : false;
             if ((compView?.addDocTab ?? compView?._props.addDocTab)?.(target, options.openLocation)) {
                 await new Promise<void>(waitres => {
                     setTimeout(() => waitres());
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index ab665e984..1e7f4fb52 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -524,7 +524,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     resizeViewForOutpainting = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; shiftKey: boolean }) => {
         const doc = docView.Document;
 
-        if (doc.isGroup) {
+        if (Doc.IsFreeformGroup(doc)) {
             DocListCast(doc.data)
                 .map(member => DocumentView.getDocumentView(member, docView)!)
                 .forEach(member => this.resizeViewForOutpainting(member, refPt, scale, opts));
@@ -598,14 +598,14 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     //
     // determines if anything being dragged directly or via a group has a fixed aspect ratio (in which case we resize uniformly)
     //
-    hasFixedAspect = (doc: Doc): boolean => (doc.isGroup ? DocListCast(doc.data).some(this.hasFixedAspect) : !BoolCast(doc._layout_nativeDimEditable));
+    hasFixedAspect = (doc: Doc): boolean => (Doc.IsFreeformGroup(doc) ? DocListCast(doc.data).some(this.hasFixedAspect) : !BoolCast(doc._layout_nativeDimEditable));
 
     //
     // resize a single DocumentView about the specified reference point, possibly setting/updating the native dimensions of the Doc
     //
     resizeView = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; freezeNativeDims: boolean }) => {
         const doc = docView.Document;
-        if (doc.isGroup) {
+        if (Doc.IsFreeformGroup(doc)) {
             DocListCast(doc.data)
                 .map(member => DocumentView.getDocumentView(member, docView)!)
                 .forEach(member => this.resizeView(member, refPt, scale, opts));
@@ -737,7 +737,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             hideDecorations ||
             seldocview._props.hideOpenButton ||
             seldocview.Document.layout_hideOpenButton ||
-            DocumentView.Selected().some(docView => docView.Document._dragOnlyWithinContainer || docView.Document.isGroup || docView.Document.layout_hideOpenButton) ||
+            DocumentView.Selected().some(docView => docView.Document._dragOnlyWithinContainer || docView.Document.layout_hideOpenButton) ||
             this._isRounding ||
             this._isRotating;
         const hideDeleteButton =
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index ded342df0..2c84d7fe7 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -238,8 +238,8 @@ export class PropertiesButtons extends React.Component {
     //         on => `Display collection as a Group`,
     //         on => 'object-group',
     //         (dv, doc) => {
-    //             doc.isGroup = !doc.isGroup;
-    //             doc.forceActive = doc.isGroup;
+    //             docfreeform_isGroup = !docfreeform_isGroup;
+    //             doc.forceActive = docfreeform_isGroup;
     //         }
     //     );
     // }
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index acf6f928a..06463b2a2 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -81,7 +81,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
 
     @computed get selectedDoc() {
-        return DocumentView.SelectedSchemaDoc() || this.selectedDocumentView?.Document || Doc.ActiveDashboard;
+        return DocumentView.SelectedSchemaDoc() ?? this.selectedDocumentView?.Document ?? Doc.ActiveDashboard;
     }
 
     @computed get selectedLink() {
@@ -89,7 +89,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
 
     @computed get selectedLayoutDoc() {
-        return DocumentView.SelectedSchemaDoc() || this.selectedDocumentView?.layoutDoc || Doc.ActiveDashboard;
+        return DocumentView.SelectedSchemaDoc() ?? this.selectedDocumentView?.layoutDoc ?? Doc.ActiveDashboard;
     }
     @computed get selectedDocumentView() {
         return DocumentView.Selected().lastElement();
@@ -149,7 +149,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         return this.selectedDoc?.type === DocumentType.INK;
     }
     @computed get isGroup() {
-        return this.selectedDoc?.isGroup;
+        return Doc.IsFreeformGroup(this.selectedDoc);
     }
     @computed get isStack() {
         return [
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 9110e198f..b52f17102 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -154,7 +154,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
                 if (highlightIndex) {
                     return {
-                        highlightStyle: doc.isGroup ? "dotted": highlightStyle,
+                        highlightStyle: Doc.IsFreeformGroup(doc) ? "dotted": highlightStyle,
                         highlightColor,
                         highlightIndex,
                         highlightStroke: BoolCast(layoutDoc?.layout_isSvg),
@@ -284,7 +284,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                             ? SnappingManager.userBackgroundColor
                             : doc?.annotationOn
                             ? '#00000010' // faint interior for collections on PDFs, images, etc
-                            : doc?.isGroup
+                            : doc && Doc.IsFreeformGroup(doc)
                             ? undefined
                             : doc?._type_collection === CollectionViewType.Stacking ?
                             (Colors.DARK_GRAY)
@@ -308,7 +308,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 || LayoutTemplateString
+                            : lockedPosition() || (doc && Doc.IsFreeformGroup(doc)) || 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')}`
                     );
@@ -338,7 +338,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             if (SnappingManager.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
             if (pointerEvents?.() === 'none') return 'none';
             if (opacity() === 0) return 'none';
-            if (isGroupActive?.() ) return isInk()  ? 'visiblePainted':  (doc?.isGroup ) ? undefined: 'all';
+            if (isGroupActive?.()) return isInk()  ? 'visiblePainted':  doc && Doc.IsFreeformGroup(doc) ? undefined: 'all';
             if (isDocumentActive?.()) return isInk() ? 'visiblePainted' : 'all';
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations: {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index eb9caf29d..72c8c3f8c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -113,7 +113,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
     };
 
     setupViewTypes(category: string, func: (type_collection: CollectionViewType) => Doc) {
-        if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking && !this.dataDoc.isGroup && !this.Document.annotationOn) {
+        if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking && !this.Document.annotationOn) {
             // prettier-ignore
             const subItems: ContextMenuProps[] = [
                 { description: 'Freeform',    event: () => func(CollectionViewType.Freeform), icon: 'signature' },
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 77615c650..3571dab1a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -176,7 +176,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return renderableEles;
     }
     @computed get fitContentsToBox() {
-        return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
+        return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox || this.Document.freeform_isGroup) && !this.isAnnotationOverlay;
     }
     @computed get nativeWidth() {
         return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document);
@@ -342,7 +342,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
      */
     focusOnPoint = (options: FocusViewOptions) => {
         const { pointFocus, zoomTime, didMove } = options;
-        if (!this.Document.isGroup && pointFocus && !didMove) {
+        if (!this.Document.freeform_isGroup && pointFocus && !didMove) {
             const dfltScale = this.isAnnotationOverlay ? 1 : 0.25;
             if (this.layoutDoc[this.scaleFieldKey] !== dfltScale) {
                 this.zoomSmoothlyAboutPt(this.screenToFreeformContentsXf.transformPoint(pointFocus.X, pointFocus.Y), dfltScale, zoomTime);
@@ -380,7 +380,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
      * @returns
      */
     focus = (anchor: Doc, options: FocusViewOptions) => {
-        if (anchor.isGroup && !options.docTransform && options.contextPath?.length) {
+        if (Doc.IsFreeformGroup(anchor) && !options.docTransform && options.contextPath?.length) {
             // don't focus on group if there's a context path because we're about to focus on a group item
             // which will override any group focus. (If we allowed the group to focus, it would mark didMove even if there were no net movement)
             return undefined;
@@ -395,7 +395,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
         const xfToCollection = options?.docTransform ?? Transform.Identity();
         const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
-        const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !DocumentView.LightboxDoc());
+        const cantTransform = this.fitContentsToBox || ((this.Document.freeform_isGroup || this.layoutDoc._lockedTransform) && !DocumentView.LightboxDoc());
         const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? (options?.zoomScale ?? 0.75) : undefined);
 
         // focus on the document in the collection
@@ -514,7 +514,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         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()) {
-            if (!this.Document.isGroup) {
+            if (!this.Document.freeform_isGroup) {
                 // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
                 // prettier-ignore
                 const hit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
@@ -1271,7 +1271,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     zoom = (pointX: number, pointY: number, deltaY: number): void => {
-        if (this.Document.isGroup || this.Document[(this._props.viewField ?? '_') + 'freeform_noZoom']) return;
+        if (this.Document.freeform_isGroup || this.Document[(this._props.viewField ?? '_') + 'freeform_noZoom']) return;
         let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
         if (deltaScale < 0) deltaScale = -deltaScale;
         const [x, y] = this.screenToFreeformContentsXf.transformPoint(pointX, pointY);
@@ -1298,7 +1298,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (this.Document.isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
+        if (this.Document.freeform_isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
         SnappingManager.TriggerUserPanned();
         if (this.layoutDoc._Transform || this.Document.treeView_OutlineMode === TreeViewType.outline) return;
         e.stopPropagation();
@@ -1422,7 +1422,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
-        if (this.Document.isGroup) return;
+        if (this.Document.freeform_isGroup) return;
         this.setPanZoomTransition(transitionTime);
         const screenXY = this.screenToFreeformContentsXf.inverse().transformPoint(docpt[0], docpt[1]);
         this.layoutDoc[this.scaleFieldKey] = scale;
@@ -1505,7 +1505,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     removeDocument = (docs: Doc | Doc[], annotationKey?: string | undefined) => {
         const ret = !!this._props.removeDocument?.(docs, annotationKey);
         // if this is a group and we have fewer than 2 Docs, then just promote what's left to our parent and get rid of the group.
-        if (ret && DocListCast(this.dataDoc[annotationKey ?? this.fieldKey]).length < 2 && this.Document.isGroup) {
+        if (ret && DocListCast(this.dataDoc[annotationKey ?? this.fieldKey]).length < 2 && this.Document.freeform_isGroup) {
             this.promoteCollection();
         }
         return ret;
@@ -1548,7 +1548,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 searchFilterDocs={this.searchFilterDocs}
                 isDocumentActive={childLayout.pointerEvents === 'none' ? returnFalse : this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this.isContentActive}
                 isContentActive={this.childContentsActive}
-                focus={this.Document.isGroup ? this.groupFocus : this.isAnnotationOverlay ? this._props.focus : this.focus}
+                focus={this.Document.freeform_isGroup ? this.groupFocus : this.isAnnotationOverlay ? this._props.focus : this.focus}
                 addDocTab={this.addDocTab}
                 addDocument={this._props.addDocument}
                 removeDocument={this.removeDocument}
@@ -1733,7 +1733,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         });
         PinDocView(
             anchor,
-            { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ? { ...pinProps.pinData, poslayoutview: pinProps.pinData.dataview } : {}), pannable: !this.Document.isGroup, collectionType: true, filters: true } },
+            { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ? { ...pinProps.pinData, poslayoutview: pinProps.pinData.dataview } : {}), pannable: !this.Document.freeform_isGroup, collectionType: true, filters: true } },
             this.Document
         );
 
@@ -1767,7 +1767,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 this._firstRender = false;
                 this._disposers.groupBounds = reaction(
                     () => {
-                        if (this.Document.isGroup && this.childDocs.length === this.childDocList?.length) {
+                        if (this.Document.freeform_isGroup && this.childDocs.length === this.childDocList?.length) {
                             const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: NumCast(cd._width), height: NumCast(cd._height) }));
                             return aggregateBounds(clist, NumCast(this.layoutDoc._xMargin), NumCast(this.layoutDoc._yMargin));
                         }
@@ -1929,8 +1929,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         const appearance = ContextMenu.Instance.findByDescription('Appearance...');
         const appearanceItems = 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' });
+        !this.Document.freeform_isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
+        !this.Document.freeform_isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
         if (this._props.setContentViewBox === emptyFunction) {
             !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
             return;
@@ -1948,7 +1948,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.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' });
+        this.Document.freeform_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._clusters.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null;
@@ -2007,7 +2007,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     transcribeStrokes = undoable(() => {
-        if (this.Document.isGroup && this.Document.transcription) {
+        if (this.Document.freeform_isGroup && this.Document.transcription) {
             const text = StrCast(this.Document.transcription);
             const lines = text.split('\n');
             const height = 30 + 15 * lines.length;
@@ -2025,7 +2025,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean = true, visited = new Set<Doc>()) => {
         if (visited.has(this.Document)) return;
         visited.add(this.Document);
-        showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document.isGroup));
+        showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document.freeform_isGroup));
         const activeDocs = this.getActiveDocuments();
         const size = this.screenToFreeformContentsXf.transformDirection(this._props.PanelWidth(), this._props.PanelHeight());
         const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
@@ -2033,13 +2033,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
 
         const snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
-        activeDocs.filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc)).forEach(doc => DocumentView.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited));
+        activeDocs
+            .filter(doc => Doc.IsFreeformGroup(doc) && SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc))
+            .forEach(doc => DocumentView.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited));
 
         const horizLines: number[] = [];
         const vertLines: number[] = [];
         const invXf = this.screenToFreeformContentsXf.inverse();
         snappableDocs
-            .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc))))
+            .filter(doc => !Doc.IsFreeformGroup(doc) && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc))))
             .forEach(doc => {
                 const { left, top, width, height } = docDims(doc);
                 const topLeftInScreen = invXf.transformPoint(left, top);
@@ -2131,7 +2133,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 {...this._props}
                 ref={this._marqueeViewRef}
                 Doc={this.Document}
-                ungroup={this.Document.isGroup ? this.promoteCollection : undefined}
+                ungroup={this.Document.freeform_isGroup ? this.promoteCollection : undefined}
                 nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge}
                 addDocTab={this.addDocTab}
                 slowLoadDocuments={this.slowLoadDocuments}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 3cc7c0f2d..37959a5f0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -372,7 +372,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             ? creator(selected, { title: 'nested stack' })
             : ((doc: Doc) => {
                   doc.$data = new List<Doc>(selected);
-                  doc.$isGroup = makeGroup;
+                  doc.$freeform_isGroup = makeGroup;
                   doc.$title = makeGroup ? 'grouping' : 'nested freeform';
                   doc._freeform_panX = doc._freeform_panY = 0;
                   return doc;
@@ -508,7 +508,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             _layout_showSidebar: true,
             title: 'overview',
         });
-        const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
+        const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, freeform_isGroup: true, backgroundColor: 'transparent' });
         DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' });
 
         portal.hidden = true;
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index c35a329c9..d30f00829 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -38,7 +38,6 @@ export class DocButtonState {
     // eslint-disable-next-line no-use-before-define
     public static _instance: DocButtonState | undefined;
     public static get Instance() {
-        // eslint-disable-next-line no-return-assign
         return DocButtonState._instance ?? (DocButtonState._instance = new DocButtonState());
     }
     constructor() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b0415ef18..9b73cc073 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -744,7 +744,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
     @computed get viewBoxContents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
-        const noBackground = this.Document.isGroup && !this._componentView?.isUnstyledView?.() && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+        const noBackground = Doc.IsFreeformGroup(this.Document) && !this._componentView?.isUnstyledView?.() && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
         return (
             <>
                 <div
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index aa66b5ba9..606f63d6d 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -273,15 +273,15 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
-        const metaKey = row._props.keyName;
-        const fieldTempDoc = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
-        fieldTempDoc.title = metaKey;
+        const keyName = row._props.keyName;
+        const fieldTempDoc = Doc.IsDelegateField(templateDoc, keyName) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
+        fieldTempDoc.title = keyName;
         fieldTempDoc.layout_fitWidth = true;
         fieldTempDoc._xMargin = 10;
         fieldTempDoc._yMargin = 10;
         fieldTempDoc._width = 100;
         fieldTempDoc._height = 40;
-        fieldTempDoc.layout = this.inferType(templateDoc[metaKey], metaKey);
+        fieldTempDoc.layout = this.inferType(templateDoc[keyName], keyName);
         return fieldTempDoc;
     };
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bab97f681..4189fcc0c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -428,10 +428,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         const newAutoLinks = new Set<Doc>();
         const oldAutoLinks = Doc.Links(this.Document).filter(
             link => 
-                ((!Doc.isTemplateForField(this.Document) &&
-                    ((DocCast(link.link_anchor_1) && !Doc.isTemplateForField(DocCast(link.link_anchor_1)!)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
-                    ((DocCast(link.link_anchor_2) && !Doc.isTemplateForField(DocCast(link.link_anchor_2)!)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) ||
-                 (Doc.isTemplateForField(this.Document) && (link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document))) &&
+                ((!Doc.IsTemplateForField(this.Document) &&
+                    ((DocCast(link.link_anchor_1) && !Doc.IsTemplateForField(DocCast(link.link_anchor_1)!)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) &&
+                    ((DocCast(link.link_anchor_2) && !Doc.IsTemplateForField(DocCast(link.link_anchor_2)!)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_2), this.Document))) ||
+                 (Doc.IsTemplateForField(this.Document) && (link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document))) &&
                 link.link_relationship === LinkManager.AutoKeywords
         ); // prettier-ignore
         if (this.EditorView?.state.doc.textContent) {
@@ -1216,7 +1216,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? (DateCast(DocCast(this.dataDoc.proto)?.[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0) : 0;
                 const recentData = dataTime >= layoutTime ? (protoTime >= dataTime ? protoData : dataData) : layoutTime >= protoTime ? layoutData : protoData;
                 const whichData = recentData ?? (this.layoutDoc.isTemplateDoc ? layoutData : protoData) ?? protoData;
-                return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) };
+                return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? NumCast(whichData)?.toString() ?? StrCast(whichData)) };
             },
             incomingValue => {
                 if (this.EditorView && this.ApplyingChange !== this.fieldKey) {
@@ -1767,7 +1767,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         if (this.ProseRef?.children[0] !== e.nativeEvent.target) return;
         if (!(this.EditorView?.state.selection instanceof NodeSelection) || this.EditorView.state.selection.node.type !== this.EditorView.state.schema.nodes.footnote) {
             const stordMarks = this.EditorView?.state.storedMarks?.slice();
-            if (!(this.EditorView?.state.selection instanceof NodeSelection)) {
+            if (!(this.EditorView?.state.selection instanceof NodeSelection) && typeof this.dataDoc[this.fieldKey] !== 'number') {
                 this.autoLink();
                 if (this.EditorView?.state.tr) {
                     const tr = stordMarks?.reduce((tr2, m) => {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 11f35b8ef..cb2a1f13f 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -702,7 +702,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 transTime + 10
             );
         }
-        if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.config_viewBounds !== undefined || activeItem.config_panX !== undefined || activeItem.config_viewScale !== undefined))) && !bestTarget.isGroup) {
+        if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.config_viewBounds !== undefined || activeItem.config_panX !== undefined || activeItem.config_viewScale !== undefined))) && !Doc.IsFreeformGroup(bestTarget)) {
             const contentBounds = Cast(activeItem.config_viewBounds, listSpec('number'));
             if (contentBounds) {
                 const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] };
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 0d11b9743..e110550e6 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -536,23 +536,35 @@ export namespace Doc {
     export function GetT<T extends FieldType>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
         return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
     }
-    export function isTemplateDoc(doc: Doc) {
-        return GetT(doc, 'isTemplateDoc', 'boolean', true);
-    }
-    export function isTemplateForField(doc: Doc) {
-        return GetT(doc, 'isTemplateForField', 'string', true);
-    }
-    export function IsDataProto(doc: Doc) {
-        return GetT(doc, 'isDataDoc', 'boolean', true);
-    }
-    export function IsBaseProto(doc: Doc) {
-        return GetT(doc, 'isBaseProto', 'boolean', true);
-    }
-    export function IsSystem(doc: Doc) {
-        return GetT(doc, 'isSystem', 'boolean', true);
-    }
-    export function IsDelegateField(doc: Doc, fieldKey: string) {
-        return doc && Get(doc, fieldKey, true) !== undefined;
+    /**
+     * Tests whether the Doc is flagged as being a template.
+     * Templates can be set as a layout for another target Doc. When rendered by the target Doc, the template
+     * creates an instance of itself that holds rendering data specific to the target
+     * @param doc
+     */
+    export function IsTemplateDoc(doc: Doc)      { return GetT(doc, 'isTemplateDoc', 'boolean', true); } // prettier-ignore
+    /**
+     * Tests whether the Doc is flagged as being a template for rendering a specific field  of a Doc
+     * When flagged as field template and rendered, the template will redirect its componentView to write to the
+     * specified template field.  In general, a compound Doc template will contain multiple field templates, one for each of the
+     * data fields rendered by the compound template.
+     * @param doc
+     * @returns
+     */
+    export function IsTemplateForField(doc: Doc) { return GetT(doc, 'isTemplateForField', 'string', true); } // prettier-ignore
+    export function IsDataProto(doc: Doc)        { return GetT(doc, 'isDataDoc', 'boolean', true); } // prettier-ignore
+    export function IsBaseProto(doc: Doc)        { return GetT(doc, 'isBaseProto', 'boolean', true); } // prettier-ignore
+    export function IsSystem(doc: Doc)           { return GetT(doc, 'isSystem', 'boolean', true); } // prettier-ignore
+    export function IsDelegateField(doc: Doc, fieldKey: string) { return doc && Get(doc, fieldKey, true) !== undefined; } // prettier-ignore
+    /**
+     * Tests whether a doc is a freeform collection that renders as a group.
+     * The group variant of a collection automatically resizes so that none of its contents
+     * are ever hidden.
+     * @param doc doc to test for being a freeform group
+     * @returns boolean
+     */
+    export function IsFreeformGroup(doc: Doc) {
+        return doc.freeform_isGroup && doc.type_collection === CollectionViewType.Freeform;
     }
     //
     // this will write the value to the key on either the data doc or the embedding doc.  The choice
@@ -734,7 +746,7 @@ export namespace Doc {
     const FindDocsInRTF = new RegExp(/(audioId|textId|anchorId|docId)"\s*:\s*"(.*?)"/g);
 
     export function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<string, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], pruneDocs: Doc[], cloneLinks: boolean, cloneTemplates: boolean): Doc {
-        if (Doc.IsBaseProto(doc) || ((Doc.isTemplateDoc(doc) || Doc.isTemplateForField(doc)) && !cloneTemplates)) {
+        if (Doc.IsBaseProto(doc) || ((Doc.IsTemplateDoc(doc) || Doc.IsTemplateForField(doc)) && !cloneTemplates)) {
             return doc;
         }
         if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
@@ -808,7 +820,7 @@ export namespace Doc {
                 const docAtKey = DocCast(clone[key]);
                 if (docAtKey && !Doc.IsSystem(docAtKey)) {
                     if (!Array.from(cloneMap.values()).includes(docAtKey)) {
-                        clone[key] = !cloneTemplates && (Doc.isTemplateDoc(docAtKey) || Doc.isTemplateForField(docAtKey)) ? docAtKey : cloneMap.get(docAtKey[Id]);
+                        clone[key] = !cloneTemplates && (Doc.IsTemplateDoc(docAtKey) || Doc.IsTemplateForField(docAtKey)) ? docAtKey : cloneMap.get(docAtKey[Id]);
                     } else {
                         repairClone(docAtKey, cloneMap, cloneTemplates, visited);
                     }
@@ -859,7 +871,7 @@ export namespace Doc {
      */
     export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, layoutFieldKey?: string) {
         // nothing to do if the layout isn't a template or we don't have a target that's different than the template
-        if (!targetDoc || templateLayoutDoc === targetDoc || (!Doc.isTemplateForField(templateLayoutDoc) && !Doc.isTemplateDoc(templateLayoutDoc))) {
+        if (!targetDoc || templateLayoutDoc === targetDoc || (!Doc.IsTemplateForField(templateLayoutDoc) && !Doc.IsTemplateDoc(templateLayoutDoc))) {
             return templateLayoutDoc;
         }
 
@@ -922,7 +934,7 @@ export namespace Doc {
             console.log('Warning: GetLayoutDataDocPair childDoc not defined');
             return { layout: childDoc, data: childDoc };
         }
-        const data = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.isTemplateDoc(childDoc) && !Doc.isTemplateForField(childDoc)) ? undefined : containerDataDoc;
+        const data = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.IsTemplateDoc(childDoc) && !Doc.IsTemplateForField(childDoc)) ? undefined : containerDataDoc;
         const templateRoot = DocCast(containerDoc?.rootDocument);
         return { layout: Doc.expandTemplateLayout(childDoc, templateRoot, layoutFieldKey), data };
     }
-- 
cgit v1.2.3-70-g09d2

